-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, upgrade_websocket/3, append_default_headers/2, parse_request/2, read_body/2]). -export_type([response_body/0, parse_error/0, connection/0, http_version/0, body_chunk/0, upgrade_websocket_error/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 response_body() :: {text_data, binary()} | {bytes_data, gleam@bytes_tree:bytes_tree()} | {bits_data, bitstring()} | {string_tree_data, gleam@string_tree:string_tree()} | {websocket_connection, gleam@erlang@process:selector(gleam@erlang@process:down())} | empty. -type parse_error() :: invalid_method | invalid_target | invalid_version | invalid_headers | missing_host | invalid_body | body_too_large | malformed_request | packet_discard. -type connection() :: {connection, glisten@transport:transport(), glisten@socket:socket(), bitstring(), gleam@option:option(http_version())}. -type http_version() :: http10 | http11. -type body_chunk() :: {done, bitstring()} | incomplete | {chunk, bitstring(), bitstring()}. -type upgrade_websocket_error() :: version_not11_or_greater | method_not_get | missing_connection_header | invalid_connection_header | missing_upgrade_header | invalid_upgrade_header | missing_websocket_version | missing_websocket_key. -file("src/ewe/internal/http.gleam", 73). ?DOC(false). -spec transform_connection(glisten:connection(any())) -> connection(). transform_connection(Connection) -> {connection, erlang:element(3, Connection), erlang:element(2, Connection), <<>>, none}. -file("src/ewe/internal/http.gleam", 259). ?DOC(false). -spec available_cookie_key(gleam@dict:dict(binary(), binary()), integer()) -> binary(). available_cookie_key(Headers, Idx) -> Key = case Idx of 0 -> <<"set-cookie"/utf8>>; N -> <<"set-cookie-"/utf8, (erlang:integer_to_binary(N))/binary>> end, case gleam@dict:has_key(Headers, Key) of true -> available_cookie_key(Headers, Idx + 1); false -> Key end. -file("src/ewe/internal/http.gleam", 242). ?DOC(false). -spec insert_header(gleam@dict:dict(binary(), binary()), binary(), binary()) -> gleam@dict:dict(binary(), binary()). insert_header(Headers, Field, Value) -> case Field /= <<"set-cookie"/utf8>> of true -> gleam@dict:upsert(Headers, Field, fun(Target) -> case Target of {some, Existing} -> <<<>/binary, Value/binary>>; none -> Value end end); false -> gleam@dict:insert(Headers, available_cookie_key(Headers, 0), Value) end. -file("src/ewe/internal/http.gleam", 338). ?DOC(false). -spec handle_continue(gleam@http@request:request(connection())) -> {ok, nil} | {error, parse_error()}. handle_continue(Req) -> Expect = begin _pipe = erlang:element(3, Req), gleam@list:find( _pipe, fun(Tupple) -> (erlang:element(1, Tupple) =:= <<"expect"/utf8>>) andalso (string:lowercase( erlang:element(2, Tupple) ) =:= <<"100-continue"/utf8>>) end ) end, case Expect of {ok, _} -> _pipe@1 = gleam@http@response:new(100), _pipe@2 = gleam@http@response:set_body( _pipe@1, gleam@bytes_tree:new() ), _pipe@3 = ewe@internal@encoder:encode_response(_pipe@2), _pipe@4 = glisten@transport:send( erlang:element(2, erlang:element(4, Req)), erlang:element(3, erlang:element(4, Req)), _pipe@3 ), gleam@result:replace_error(_pipe@4, malformed_request); {error, nil} -> {ok, nil} end. -file("src/ewe/internal/http.gleam", 458). ?DOC(false). -spec is_forbidden_trailer(binary()) -> boolean(). is_forbidden_trailer(Field) -> case string:lowercase(Field) of <<"transfer-encoding"/utf8>> -> true; <<"content-length"/utf8>> -> true; <<"host"/utf8>> -> true; <<"cache-control"/utf8>> -> true; <<"expect"/utf8>> -> true; <<"max-forwards"/utf8>> -> true; <<"pragma"/utf8>> -> true; <<"range"/utf8>> -> true; <<"te"/utf8>> -> true; _ -> false end. -file("src/ewe/internal/http.gleam", 420). ?DOC(false). -spec handle_trailers( gleam@http@request:request(bitstring()), gleam@set:set(binary()), bitstring() ) -> gleam@http@request:request(bitstring()). handle_trailers(Req, Set, Rest) -> case ewe_ffi:decode_packet(httph_bin, Rest, []) of {ok, {packet, http_eoh, _}} -> Req; {ok, {packet, {http_header, Idx, Field, Value}, Rest@1}} -> Field@2 = case ewe@internal@decoder:formatted_field_by_idx(Idx) of {ok, Field@1} -> {ok, Field@1}; {error, nil} -> _pipe = gleam@bit_array:to_string(Field), gleam@result:map(_pipe, fun string:lowercase/1) end, case Field@2 of {ok, Field@3} -> case gleam@set:contains(Set, Field@3) andalso not is_forbidden_trailer( Field@3 ) of true -> case gleam@bit_array:to_string(Value) of {ok, Value@1} -> _pipe@1 = gleam@http@request:set_header( Req, Field@3, Value@1 ), handle_trailers(_pipe@1, Set, Rest@1); {error, nil} -> handle_trailers(Req, Set, Rest@1) end; false -> handle_trailers(Req, Set, Rest@1) end; {error, nil} -> handle_trailers(Req, Set, Rest@1) end; _ -> Req end. -file("src/ewe/internal/http.gleam", 484). ?DOC(false). -spec upgrade_websocket( gleam@http@request:request(connection()), glisten@transport:transport(), glisten@socket:socket() ) -> {ok, nil} | {error, upgrade_websocket_error()}. upgrade_websocket(Req, Transport, Socket) -> Http_version@1 = case erlang:element(5, erlang:element(4, Req)) of {some, Http_version} -> Http_version; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"ewe/internal/http"/utf8>>, function => <<"upgrade_websocket"/utf8>>, line => 489, value => _assert_fail, start => 11492, 'end' => 11552, pattern_start => 11503, pattern_end => 11528}) end, gleam@bool:guard( Http_version@1 /= http11, {error, version_not11_or_greater}, fun() -> gleam@bool:guard( erlang:element(2, Req) /= get, {error, method_not_get}, fun() -> gleam@result:'try'( case gleam@http@request:get_header( Req, <<"connection"/utf8>> ) of {ok, <<"Upgrade"/utf8>>} -> {ok, nil}; {ok, _} -> {error, invalid_connection_header}; {error, _} -> {error, missing_connection_header} end, fun(_) -> gleam@result:'try'( case gleam@http@request:get_header( Req, <<"upgrade"/utf8>> ) of {ok, <<"websocket"/utf8>>} -> {ok, nil}; {ok, _} -> {error, invalid_upgrade_header}; {error, _} -> {error, missing_upgrade_header} end, fun(_) -> gleam@bool:guard( gleam@http@request:get_header( Req, <<"sec-websocket-version"/utf8>> ) =:= {error, nil}, {error, missing_websocket_version}, fun() -> gleam@result:'try'( begin _pipe = gleam@http@request:get_header( Req, <<"sec-websocket-key"/utf8>> ), gleam@result:replace_error( _pipe, missing_websocket_key ) end, fun(Key) -> Accept_key = gramps@websocket:parse_websocket_key( Key ), _ = begin _pipe@1 = gleam@http@request:get_header( Req, <<"sec-websocket-extensions"/utf8>> ), _pipe@2 = gleam@result:map( _pipe@1, fun(_capture) -> gleam@string:split( _capture, <<";"/utf8>> ) end ), gleam@result:unwrap( _pipe@2, [] ) end, _ = begin _pipe@3 = gleam@http@response:new( 101 ), _pipe@4 = gleam@http@response:set_body( _pipe@3, gleam@bytes_tree:new( ) ), _pipe@5 = gleam@http@response:set_header( _pipe@4, <<"connection"/utf8>>, <<"upgrade"/utf8>> ), _pipe@6 = gleam@http@response:set_header( _pipe@5, <<"upgrade"/utf8>>, <<"websocket"/utf8>> ), _pipe@7 = gleam@http@response:set_header( _pipe@6, <<"sec-websocket-accept"/utf8>>, Accept_key ), _pipe@8 = gleam@http@response:set_header( _pipe@7, <<"sec-websocket-version"/utf8>>, <<"13"/utf8>> ), _pipe@9 = ewe@internal@encoder:encode_response( _pipe@8 ), glisten@transport:send( Transport, Socket, _pipe@9 ) end, {ok, nil} end ) end ) end ) end ) end ) end ). -file("src/ewe/internal/http.gleam", 537). ?DOC(false). -spec append_default_headers( gleam@http@response:response(gleam@bytes_tree:bytes_tree()), http_version() ) -> gleam@http@response:response(gleam@bytes_tree:bytes_tree()). append_default_headers(Resp, Version) -> Body_size = erlang:iolist_size(erlang:element(4, Resp)), Resp@1 = case gleam@http@response:get_header( Resp, <<"content-length"/utf8>> ) of {ok, _} -> Resp; {error, nil} -> gleam@http@response:set_header( Resp, <<"content-length"/utf8>>, erlang:integer_to_binary(Body_size) ) end, case Version of http10 -> gleam@http@response:set_header( Resp@1, <<"connection"/utf8>>, <<"close"/utf8>> ); http11 -> case gleam@http@response:get_header(Resp@1, <<"connection"/utf8>>) of {ok, _} -> Resp@1; {error, nil} -> gleam@http@response:set_header( Resp@1, <<"connection"/utf8>>, <<"keep-alive"/utf8>> ) end end. -file("src/ewe/internal/http.gleam", 396). ?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>>, Rest] -> {ok, {done, Rest}}; [Chunk_size, Rest@1] -> 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@1, <<"\r\n"/utf8>>, []) of [Chunk, Rest@2] -> case erlang:byte_size(Chunk) =:= Size of true -> {ok, {chunk, Chunk, Rest@2}}; false -> {error, invalid_body} end; _ -> {ok, incomplete} end end ); _ -> {ok, incomplete} end. -file("src/ewe/internal/http.gleam", 85). ?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", 199). ?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, Idx, Field, Value}, Rest@1}} -> gleam@result:'try'( case ewe@internal@decoder:formatted_field_by_idx(Idx) of {ok, Field@1} -> {ok, Field@1}; {error, nil} -> _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@2) -> gleam@result:'try'( begin _pipe@2 = gleam@bit_array:to_string(Value), gleam@result:replace_error(_pipe@2, invalid_headers) end, fun(Value@1) -> _pipe@3 = insert_header(Headers, Field@2, Value@1), parse_headers(Transport, Socket, Rest@1, _pipe@3) 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", 113). ?DOC(false). -spec parse_request(connection(), bitstring()) -> {ok, gleam@http@request:request(connection())} | {error, parse_error()}. parse_request(Connection, Buffer) -> Transport = erlang:element(2, Connection), Socket = erlang:element(3, Connection), 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( Transport, Socket, Rest, maps:new() ), fun(_use0) -> {Headers, Rest@1} = _use0, Scheme = case Transport 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, {request, Method, maps:to_list( Headers ), {connection, erlang:element( 2, Connection ), erlang:element( 3, Connection ), Rest@1, {some, Version@1}}, Scheme, Host, {some, Port@1}, erlang:element( 6, Uri ), erlang:element( 7, Uri )}} end ) end ) end) end ) end ); {ok, {more, Size}} -> Read_size = gleam@option:unwrap(Size, 0), gleam@result:'try'( read_from_socket( Transport, Socket, Read_size, erlang:element(4, Connection), malformed_request ), fun(Buffer@1) -> parse_request(Connection, Buffer@1) end ); _ -> {error, packet_discard} end. -file("src/ewe/internal/http.gleam", 357). ?DOC(false). -spec read_chunked_body( glisten@transport:transport(), glisten@socket:socket(), bitstring(), bitstring(), integer(), integer() ) -> {ok, {bitstring(), bitstring()}} | {error, parse_error()}. read_chunked_body(Transport, Socket, Buffer, Body, Size_limit, Total_size) -> gleam@bool:guard( Total_size > Size_limit, {error, body_too_large}, fun() -> case parse_body_chunk(Buffer) of {ok, {done, Rest}} -> {ok, {Body, Rest}}; {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, Size_limit, Total_size ) end ); {ok, {chunk, Chunk, Rest@1}} -> Body@1 = <>, Total_size@1 = Total_size + erlang:byte_size(Chunk), read_chunked_body( Transport, Socket, Rest@1, Body@1, Size_limit, Total_size@1 ); {error, Error} -> {error, Error} end end ). -file("src/ewe/internal/http.gleam", 271). ?DOC(false). -spec read_body(gleam@http@request:request(connection()), integer()) -> {ok, gleam@http@request:request(bitstring())} | {error, parse_error()}. read_body(Req, Size_limit) -> gleam@result:'try'( handle_continue(Req), fun(_) -> Transport = erlang:element(2, erlang:element(4, Req)), Socket = erlang:element(3, erlang:element(4, 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( Transport, Socket, erlang:element(4, erlang:element(4, Req)), <<>>, Size_limit, 0 ), fun(_use0) -> {Body, Rest} = _use0, Req@1 = gleam@http@request:set_body(Req, Body), case gleam@list:key_find( erlang:element(3, Req@1), <<"trailer"/utf8>> ) of {ok, Trailer} -> Set@1 = begin _pipe@1 = Trailer, _pipe@2 = gleam@string:split( _pipe@1, <<","/utf8>> ), gleam@list:fold( _pipe@2, gleam@set:new(), fun(Set, Field) -> gleam@set:insert( Set, begin _pipe@3 = gleam@string:trim( Field ), string:lowercase( _pipe@3 ) end ) end ) end, {ok, handle_trailers(Req@1, Set@1, Rest)}; {error, nil} -> {ok, Req@1} end end ); _ -> Content_length = begin _pipe@4 = gleam@http@request:get_header( Req, <<"content-length"/utf8>> ), _pipe@5 = gleam@result:'try'( _pipe@4, fun gleam_stdlib:parse_int/1 ), gleam@result:unwrap(_pipe@5, 0) end, gleam@bool:guard( Content_length > Size_limit, {error, body_too_large}, fun() -> Left = Content_length - erlang:byte_size( erlang:element(4, erlang:element(4, Req)) ), _pipe@6 = 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( Transport, Socket, Left, erlang:element( 4, erlang:element(4, Req) ), invalid_body ) end, gleam@result:map( _pipe@6, fun(_capture) -> gleam@http@request:set_body(Req, _capture) end ) end ) end end ).