-module(ewe@internal@http). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/ewe/internal/http.gleam"). -export([transform_connection/1, parse_body_chunk/1, read_from_socket/5, parse_request/2, read_chunked_body/4, read_body/1]). -export_type([parse_error/0, connection/0, http_version/0, parsed_request/0, body_chunk/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -type parse_error() :: invalid_method | invalid_target | invalid_version | invalid_headers | missing_host | invalid_body | packet_loss. -type connection() :: {connection, glisten@transport:transport(), glisten@socket:socket(), bitstring()}. -type http_version() :: http10 | http11. -type parsed_request() :: {parsed_request, gleam@http@request:request(connection()), http_version()}. -type body_chunk() :: done | incomplete | {chunk, bitstring(), bitstring()}. -file("src/ewe/internal/http.gleam", 42). ?DOC(false). -spec transform_connection(glisten:connection(any())) -> connection(). transform_connection(Connection) -> {connection, erlang:element(3, Connection), erlang:element(2, Connection), <<>>}. -file("src/ewe/internal/http.gleam", 290). ?DOC(false). -spec parse_body_chunk(bitstring()) -> {ok, body_chunk()} | {error, parse_error()}. parse_body_chunk(Buffer) -> case binary:split(Buffer, <<"\r\n"/utf8>>, []) of [<<"0"/utf8>>, _] -> {ok, done}; [Chunk_size, Rest] -> gleam@result:'try'( begin _pipe = gleam@bit_array:to_string(Chunk_size), _pipe@1 = gleam@result:'try'( _pipe, fun(_capture) -> gleam@int:base_parse(_capture, 16) end ), gleam@result:replace_error(_pipe@1, invalid_body) end, fun(Size) -> case binary:split(Rest, <<"\r\n"/utf8>>, []) of [Chunk, Rest@1] -> case erlang:byte_size(Chunk) =:= Size of true -> {ok, {chunk, Chunk, Rest@1}}; false -> {error, invalid_body} end; _ -> {ok, incomplete} end end ); _ -> {ok, incomplete} end. -file("src/ewe/internal/http.gleam", 56). ?DOC(false). -spec read_from_socket( glisten@transport:transport(), glisten@socket:socket(), integer(), bitstring(), parse_error() ) -> {ok, bitstring()} | {error, parse_error()}. read_from_socket(Transport, Socket, Amount, Buffer, On_error) -> Read_size = gleam@int:min(Amount, 1000000), gleam@result:'try'( begin _pipe = glisten@transport:receive_timeout( Transport, Socket, Read_size, 10000 ), gleam@result:replace_error(_pipe, On_error) end, fun(Data) -> Amount@1 = Amount - Read_size, Buffer@1 = <>, case Amount@1 > 0 of true -> read_from_socket( Transport, Socket, Amount@1, Buffer@1, On_error ); false -> {ok, Buffer@1} end end ). -file("src/ewe/internal/http.gleam", 170). ?DOC(false). -spec parse_headers( glisten@transport:transport(), glisten@socket:socket(), bitstring(), gleam@dict:dict(binary(), binary()) ) -> {ok, {gleam@dict:dict(binary(), binary()), bitstring()}} | {error, parse_error()}. parse_headers(Transport, Socket, Buffer, Headers) -> case ewe_ffi:decode_packet(httph_bin, Buffer, []) of {ok, {packet, http_eoh, Rest}} -> {ok, {Headers, Rest}}; {ok, {packet, {http_header, _, _, Field, Value}, Rest@1}} -> gleam@result:'try'( begin _pipe = gleam@bit_array:to_string(Field), _pipe@1 = gleam@result:map(_pipe, fun string:lowercase/1), gleam@result:replace_error(_pipe@1, invalid_headers) end, fun(Field@1) -> gleam@result:'try'( begin _pipe@2 = gleam@bit_array:to_string(Value), _pipe@3 = gleam@result:map( _pipe@2, fun gleam@string:trim/1 ), gleam@result:replace_error(_pipe@3, invalid_headers) end, fun(Value@1) -> _pipe@4 = gleam@dict:insert( Headers, Field@1, Value@1 ), parse_headers(Transport, Socket, Rest@1, _pipe@4) end ) end ); {ok, {more, Size}} -> Read_size = gleam@option:unwrap(Size, 0), gleam@result:'try'( read_from_socket( Transport, Socket, Read_size, Buffer, invalid_headers ), fun(Buffer@1) -> parse_headers(Transport, Socket, Buffer@1, Headers) end ); _ -> {error, invalid_headers} end. -file("src/ewe/internal/http.gleam", 88). ?DOC(false). -spec parse_request(connection(), bitstring()) -> {ok, parsed_request()} | {error, parse_error()}. parse_request(Connection, Buffer) -> case ewe_ffi:decode_packet(http_bin, Buffer, []) of {ok, {packet, {http_request, Atom_method, {abs_path, Target}, Version}, Rest}} -> gleam@result:'try'( begin _pipe = ewe@internal@decoder:decode_method(Atom_method), gleam@result:replace_error(_pipe, invalid_method) end, fun(Method) -> gleam@result:'try'( begin _pipe@1 = gleam@bit_array:to_string(Target), _pipe@2 = gleam@result:'try'( _pipe@1, fun gleam_stdlib:uri_parse/1 ), gleam@result:replace_error(_pipe@2, invalid_target) end, fun(Uri) -> gleam@result:'try'(case Version of {1, 0} -> {ok, http10}; {1, 1} -> {ok, http11}; _ -> {error, invalid_version} end, fun(Version@1) -> gleam@result:'try'( parse_headers( erlang:element(2, Connection), erlang:element(3, Connection), Rest, maps:new() ), fun(_use0) -> {Headers, Rest@1} = _use0, Scheme = case erlang:element( 2, Connection ) of tcp -> http; ssl -> https end, gleam@result:'try'( begin _pipe@3 = gleam_stdlib:map_get( Headers, <<"host"/utf8>> ), _pipe@4 = gleam@result:'try'( _pipe@3, fun(_capture) -> gleam@string:split_once( _capture, <<":"/utf8>> ) end ), gleam@result:replace_error( _pipe@4, missing_host ) end, fun(_use0@1) -> {Host, Port} = _use0@1, Port@1 = begin _pipe@5 = gleam_stdlib:parse_int( Port ), gleam@result:unwrap( _pipe@5, case Scheme of http -> 80; https -> 443 end ) end, {ok, {parsed_request, {request, Method, maps:to_list( Headers ), {connection, erlang:element( 2, Connection ), erlang:element( 3, Connection ), Rest@1}, Scheme, Host, {some, Port@1}, erlang:element( 6, Uri ), erlang:element( 7, Uri )}, Version@1}} end ) end ) end) end ) end ); {ok, {more, Size}} -> Read_size = gleam@option:unwrap(Size, 0), gleam@result:'try'( read_from_socket( erlang:element(2, Connection), erlang:element(3, Connection), Read_size, erlang:element(4, Connection), packet_loss ), fun(Buffer@1) -> parse_request(Connection, Buffer@1) end ); _ -> {error, packet_loss} end. -file("src/ewe/internal/http.gleam", 256). ?DOC(false). -spec read_chunked_body( glisten@transport:transport(), glisten@socket:socket(), bitstring(), bitstring() ) -> {ok, bitstring()} | {error, parse_error()}. read_chunked_body(Transport, Socket, Buffer, Body) -> case parse_body_chunk(Buffer) of {ok, done} -> {ok, Body}; {ok, incomplete} -> gleam@result:'try'( read_from_socket(Transport, Socket, 0, Buffer, invalid_body), fun(Buffer@1) -> read_chunked_body(Transport, Socket, Buffer@1, Body) end ); {ok, {chunk, Chunk, Rest}} -> Body@1 = <>, read_chunked_body(Transport, Socket, Rest, Body@1); {error, Error} -> {error, Error} end. -file("src/ewe/internal/http.gleam", 211). ?DOC(false). -spec read_body(gleam@http@request:request(connection())) -> {ok, gleam@http@request:request(bitstring())} | {error, parse_error()}. read_body(Req) -> Transfer_encoding = begin _pipe = gleam@http@request:get_header(Req, <<"transfer-encoding"/utf8>>), gleam@result:map(_pipe, fun string:lowercase/1) end, case Transfer_encoding of {ok, <<"chunked"/utf8>>} -> gleam@result:'try'( read_chunked_body( erlang:element(2, erlang:element(4, Req)), erlang:element(3, erlang:element(4, Req)), erlang:element(4, erlang:element(4, Req)), <<>> ), fun(Body) -> {ok, gleam@http@request:set_body(Req, Body)} end ); _ -> Content_length = begin _pipe@1 = gleam@http@request:get_header( Req, <<"content-length"/utf8>> ), _pipe@2 = gleam@result:'try'( _pipe@1, fun gleam_stdlib:parse_int/1 ), gleam@result:unwrap(_pipe@2, 0) end, Left = Content_length - erlang:byte_size( erlang:element(4, erlang:element(4, Req)) ), _pipe@3 = case {Content_length, Left} of {0, 0} -> {ok, <<>>}; {0, _} -> {ok, erlang:element(4, erlang:element(4, Req))}; {_, 0} -> {ok, erlang:element(4, erlang:element(4, Req))}; {_, _} -> read_from_socket( erlang:element(2, erlang:element(4, Req)), erlang:element(3, erlang:element(4, Req)), Left, erlang:element(4, erlang:element(4, Req)), invalid_body ) end, gleam@result:map( _pipe@3, fun(_capture) -> gleam@http@request:set_body(Req, _capture) end ) end.