-module(mist@http). -compile(no_auto_import). -export([from_header/1, parse_headers/3, read_data/3, parse_request/2, read_body/1, new_state/0, handler/2, handler_func/1, upgrade_socket/1, upgrade/2]). -export_type([packet_type/0, http_uri/0, http_packet/0, decoded_packet/0, decode_error/0, buffer/0, body/0, handler_error/0, state/0, http_response_body/0, handler_response/0]). -type packet_type() :: http | httph_bin | http_bin. -type http_uri() :: {abs_path, bitstring()}. -type http_packet() :: {http_request, gleam@dynamic:dynamic(), http_uri(), {integer(), integer()}} | {http_header, integer(), gleam@erlang@atom:atom_(), bitstring(), bitstring()}. -type decoded_packet() :: {binary_data, http_packet(), bitstring()} | {end_of_headers, bitstring()} | {more_data, gleam@option:option(integer())}. -type decode_error() :: malformed_request | invalid_method | invalid_path | unknown_header | unknown_method | invalid_body | discard_packet. -type buffer() :: {buffer, integer(), bitstring()}. -opaque body() :: {unread, bitstring(), glisten@tcp:socket()} | {read, bitstring()}. -type handler_error() :: {invalid_request, decode_error()} | not_found. -type state() :: {state, gleam@option:option(gleam@otp@process:timer()), gleam@option:option(mist@websocket:websocket_handler())}. -type http_response_body() :: {bit_builder_body, gleam@bit_builder:bit_builder()} | {file_body, mist@file:file_descriptor(), binary(), integer(), integer()}. -type handler_response() :: {response, gleam@http@response:response(http_response_body())} | {upgrade, mist@websocket:websocket_handler()}. -spec from_header(bitstring()) -> binary(). from_header(Value) -> {ok, Value@2} = case gleam@bit_string:to_string(Value) of {ok, Value@1} -> {ok, Value@1}; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/http"/utf8>>, function => <<"from_header"/utf8>>, line => 66}) end, gleam@string:lowercase(Value@2). -spec parse_headers( bitstring(), glisten@tcp:socket(), gleam@map:map_(binary(), binary()) ) -> {ok, {gleam@map:map_(binary(), binary()), bitstring()}} | {error, decode_error()}. parse_headers(Bs, Socket, Headers) -> case http_ffi:decode_packet(httph_bin, Bs, []) of {ok, {binary_data, {http_header, _@1, _@2, Field, Value}, Rest}} -> Field@1 = from_header(Field), {ok, Value@2} = case gleam@bit_string:to_string(Value) of {ok, Value@1} -> {ok, Value@1}; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/http"/utf8>>, function => <<"parse_headers"/utf8>>, line => 83}) end, _pipe = Headers, _pipe@1 = gleam@map:insert(_pipe, Field@1, Value@2), parse_headers(Rest, Socket, _pipe@1); {ok, {end_of_headers, Rest@1}} -> {ok, {Headers, Rest@1}}; {ok, {more_data, Size}} -> Amount_to_read = gleam@option:unwrap(Size, 0), case read_data(Socket, {buffer, Amount_to_read, Bs}, unknown_header) of {error, _try@1} -> {error, _try@1}; {ok, Next} -> parse_headers(Next, Socket, Headers) end; _@3 -> {error, unknown_header} end. -spec read_data(glisten@tcp:socket(), buffer(), decode_error()) -> {ok, bitstring()} | {error, decode_error()}. read_data(Socket, Buffer, Error) -> To_read = gleam@int:min(erlang:element(2, Buffer), 1000000), Timeout = 15000, case begin _pipe = Socket, _pipe@1 = glisten@tcp:receive_timeout(_pipe, To_read, Timeout), gleam@result:replace_error(_pipe@1, Error) end of {error, _try} -> {error, _try}; {ok, Data} -> Next_buffer = {buffer, erlang:element(2, Buffer) - To_read, <<(erlang:element(3, Buffer))/bitstring, Data/bitstring>>}, case erlang:element(2, Next_buffer) > 0 of true -> read_data(Socket, Next_buffer, Error); false -> {ok, erlang:element(3, Next_buffer)} end end. -spec read_chunk( glisten@tcp:socket(), buffer(), gleam@bit_builder:bit_builder() ) -> {ok, gleam@bit_builder:bit_builder()} | {error, decode_error()}. read_chunk(Socket, Buffer, Body) -> case {erlang:element(3, Buffer), http_ffi:binary_match( erlang:element(3, Buffer), <<13/integer, 10/integer>> )} of {_@1, {ok, {Offset, _@2}}} -> <> = case erlang:element( 3, Buffer ) of <> -> <>; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/http"/utf8>>, function => <<"read_chunk"/utf8>>, line => 140}) end, case begin _pipe = Chunk@1, _pipe@1 = gleam@bit_string:to_string(_pipe), _pipe@2 = gleam@result:map( _pipe@1, fun gleam@erlang@charlist:from_string/1 ), gleam@result:replace_error(_pipe@2, invalid_body) end of {error, _try@1} -> {error, _try@1}; {ok, Chunk_size} -> case begin _pipe@3 = http_ffi:string_to_int(Chunk_size, 16), gleam@result:replace_error(_pipe@3, invalid_body) end of {error, _try@2} -> {error, _try@2}; {ok, Size} -> case Size of 0 -> {ok, Body}; Size@1 -> case Rest@1 of <> -> read_chunk( Socket, {buffer, 0, Rest@2}, gleam@bit_builder:append( Body, Next_chunk ) ); _@7 -> case read_data( Socket, {buffer, 0, erlang:element(3, Buffer)}, invalid_body ) of {error, _try@3} -> {error, _try@3}; {ok, Next} -> read_chunk( Socket, {buffer, 0, Next}, Body ) end end end end end; {<<>>, _@8} -> case read_data( Socket, {buffer, 0, erlang:element(3, Buffer)}, invalid_body ) of {error, _try@4} -> {error, _try@4}; {ok, Next@1} -> read_chunk(Socket, {buffer, 0, Next@1}, Body) end; {_@9, {error, nil}} -> {error, invalid_body} end. -spec decode_atom(gleam@dynamic:dynamic()) -> {ok, gleam@erlang@atom:atom_()} | {error, list(gleam@dynamic:decode_error())}. decode_atom(Value) -> case erlang:is_atom(Value) of true -> {ok, gleam@dynamic:unsafe_coerce(Value)}; false -> {error, [{decode_error, <<"Atom"/utf8>>, gleam@dynamic:classify(Value), []}]} end. -spec parse_request(bitstring(), glisten@tcp:socket()) -> {ok, gleam@http@request:request(body())} | {error, decode_error()}. parse_request(Bs, Socket) -> case http_ffi:decode_packet(http_bin, Bs, []) of {ok, {binary_data, {http_request, Http_method, {abs_path, Path}, _@1}, Rest}} -> case begin _pipe = Http_method, _pipe@1 = decode_atom(_pipe), _pipe@2 = gleam@result:map( _pipe@1, fun gleam@erlang@atom:to_string/1 ), _pipe@3 = gleam@result:'or'( _pipe@2, gleam@dynamic:string(Http_method) ), _pipe@4 = gleam@result:replace_error(_pipe@3, nil), _pipe@5 = gleam@result:then( _pipe@4, fun gleam@http:parse_method/1 ), gleam@result:replace_error(_pipe@5, unknown_method) end of {error, _try} -> {error, _try}; {ok, Method} -> case parse_headers(Rest, Socket, gleam@map:new()) of {error, _try@1} -> {error, _try@1}; {ok, {Headers, Rest@1}} -> case begin _pipe@6 = Path, _pipe@7 = gleam@bit_string:to_string(_pipe@6), gleam@result:replace_error( _pipe@7, invalid_path ) end of {error, _try@2} -> {error, _try@2}; {ok, Path@1} -> {Path@4, Query@1} = case gleam@string:split( Path@1, <<"?"/utf8>> ) of [Path@2] -> {Path@2, []}; [Path@3, Query_string] -> Query = begin _pipe@8 = Query_string, _pipe@9 = gleam@uri:parse_query( _pipe@8 ), gleam@result:unwrap(_pipe@9, []) end, {Path@3, Query} end, Req = begin _pipe@10 = gleam@http@request:new(), _pipe@11 = gleam@http@request:set_body( _pipe@10, {unread, Rest@1, Socket} ), _pipe@12 = gleam@http@request:set_method( _pipe@11, Method ), _pipe@13 = gleam@http@request:set_path( _pipe@12, Path@4 ), gleam@http@request:set_query( _pipe@13, Query@1 ) end, {ok, erlang:setelement( 3, Req, gleam@map:to_list(Headers) )} end end end; _@2 -> {error, discard_packet} end. -spec read_body(gleam@http@request:request(body())) -> {ok, gleam@http@request:request(bitstring())} | {error, decode_error()}. read_body(Req) -> case {gleam@http@request:get_header(Req, <<"transfer-encoding"/utf8>>), erlang:element(4, Req)} of {{ok, <<"chunked"/utf8>>}, {unread, Rest, Socket}} -> case read_chunk(Socket, {buffer, 0, Rest}, gleam@bit_builder:new()) of {error, _try} -> {error, _try}; {ok, Chunk} -> {ok, gleam@http@request:set_body( Req, gleam@bit_builder:to_bit_string(Chunk) )} end; {_@1, {unread, Rest@1, Socket@1}} -> Body_size = begin _pipe = erlang:element(3, Req), _pipe@1 = gleam@list:find( _pipe, fun(Tup) -> gleam@pair:first(Tup) =:= <<"content-length"/utf8>> end ), _pipe@2 = gleam@result:map(_pipe@1, fun gleam@pair:second/1), _pipe@3 = gleam@result:then(_pipe@2, fun gleam@int:parse/1), gleam@result:unwrap(_pipe@3, 0) end, Remaining = Body_size - gleam@bit_string:byte_size(Rest@1), _pipe@4 = case {Body_size, Remaining} of {0, 0} -> {ok, <<>>}; {0, _@2} -> {ok, Rest@1}; {_@3, 0} -> {ok, Rest@1}; {_@4, _@5} -> read_data( Socket@1, {buffer, Remaining, Rest@1}, invalid_body ) end, _pipe@5 = gleam@result:map( _pipe@4, fun(_capture) -> gleam@http@request:set_body(Req, _capture) end ), gleam@result:replace_error(_pipe@5, invalid_body); {_@6, {read, _@7}} -> {error, invalid_body} end. -spec new_state() -> state(). new_state() -> {state, none, none}. -spec handler( fun((gleam@http@request:request(bitstring())) -> gleam@http@response:response(gleam@bit_builder:bit_builder())), integer() ) -> fun((glisten@tcp:handler_message(), glisten@tcp:loop_state(state())) -> gleam@otp@actor:next(glisten@tcp:loop_state(state()))). handler(Handler, Max_body_limit) -> Bad_request = begin _pipe = gleam@http@response:new(400), gleam@http@response:set_body(_pipe, gleam@bit_builder:new()) end, handler_func( fun(Req) -> _pipe@14 = case {gleam@http@request:get_header( Req, <<"content-length"/utf8>> ), gleam@http@request:get_header( Req, <<"transfer-encoding"/utf8>> )} of {{ok, <<"0"/utf8>>}, _@1} -> _pipe@1 = Req, _pipe@2 = gleam@http@request:set_body(_pipe@1, <<>>), Handler(_pipe@2); {{error, nil}, {error, nil}} -> _pipe@1 = Req, _pipe@2 = gleam@http@request:set_body(_pipe@1, <<>>), Handler(_pipe@2); {_@2, {ok, <<"chunked"/utf8>>}} -> _pipe@3 = Req, _pipe@4 = read_body(_pipe@3), _pipe@5 = gleam@result:map(_pipe@4, Handler), gleam@result:unwrap(_pipe@5, Bad_request); {{ok, Size}, _@3} -> _pipe@6 = Size, _pipe@7 = gleam@int:parse(_pipe@6), _pipe@13 = gleam@result:map( _pipe@7, fun(Size@1) -> case Size@1 > Max_body_limit of true -> _pipe@8 = gleam@http@response:new(413), _pipe@9 = gleam@http@response:set_body( _pipe@8, gleam@bit_builder:new() ), gleam@http@response:prepend_header( _pipe@9, <<"connection"/utf8>>, <<"close"/utf8>> ); false -> _pipe@10 = Req, _pipe@11 = read_body(_pipe@10), _pipe@12 = gleam@result:map( _pipe@11, Handler ), gleam@result:unwrap(_pipe@12, Bad_request) end end ), gleam@result:unwrap(_pipe@13, Bad_request) end, _pipe@15 = gleam@http@response:map( _pipe@14, fun(A) -> {bit_builder_body, A} end ), {response, _pipe@15} end ). -spec handler_func( fun((gleam@http@request:request(body())) -> handler_response()) ) -> fun((glisten@tcp:handler_message(), glisten@tcp:loop_state(state())) -> gleam@otp@actor:next(glisten@tcp:loop_state(state()))). handler_func(Handler) -> glisten@tcp:handler( fun(Msg, Socket_state) -> {loop_state, Socket, Sender, State} = Socket_state, case erlang:element(3, State) of {some, Ws_handler} -> case mist@websocket:frame_from_message(Socket, Msg) of {ok, {ping_frame, _@1, _@2}} -> {ok, _@4} = case glisten@tcp:send( Socket, mist@websocket:frame_to_bit_builder( {pong_frame, 0, <<>>} ) ) of {ok, _@3} -> {ok, _@3}; _try -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try, module => <<"mist/http"/utf8>>, function => <<"handler_func"/utf8>>, line => 355}) end, {continue, Socket_state}; {ok, {close_frame, _@5, _@6} = Frame} -> {ok, _@8} = case glisten@tcp:send( Socket, mist@websocket:frame_to_bit_builder(Frame) ) of {ok, _@7} -> {ok, _@7}; _try@1 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try@1, module => <<"mist/http"/utf8>>, function => <<"handler_func"/utf8>>, line => 363}) end, _@10 = case erlang:element(2, Ws_handler) of {some, Func} -> Func(Sender); _@9 -> nil end, {stop, normal}; {ok, {pong_frame, _@11, _@12}} -> {stop, normal}; {ok, Frame@1} -> _pipe = case Frame@1 of {text_frame, _@13, Payload} -> {ok, Msg@2} = case gleam@bit_string:to_string( Payload ) of {ok, Msg@1} -> {ok, Msg@1}; _try@2 -> erlang:error( #{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _try@2, module => <<"mist/http"/utf8>>, function => <<"handler_func"/utf8>>, line => 375} ) end, {text_message, Msg@2}; _@14 -> {binary_message, erlang:element(3, Frame@1)} end, _pipe@1 = (fun(Ws_msg) -> gleam_erlang_ffi:rescue( fun() -> (erlang:element(4, Ws_handler))( Ws_msg, Sender ) end ) end)(_pipe), _pipe@2 = gleam@result:replace( _pipe@1, {continue, Socket_state} ), _pipe@3 = gleam@result:map_error( _pipe@2, fun(Err) -> mist@logger:error(Err), _@16 = case erlang:element(2, Ws_handler) of {some, Func@1} -> Func@1(Sender); _@15 -> nil end, Err end ), _pipe@4 = gleam@result:replace_error( _pipe@3, {stop, normal} ), gleam@result:unwrap_both(_pipe@4); {error, _@17} -> _@19 = case erlang:element(2, Ws_handler) of {some, Func@2} -> Func@2(Sender); _@18 -> nil end, {stop, normal} end; none -> _@21 = case erlang:element(2, State) of {some, T} -> gleam@otp@process:cancel_timer(T); _@20 -> timer_not_found end, _pipe@5 = Msg, _pipe@6 = parse_request(_pipe@5, Socket), _pipe@7 = gleam@result:map_error( _pipe@6, fun(Err@1) -> case Err@1 of discard_packet -> nil; _@22 -> mist@logger:error(Err@1), glisten@tcp:close(Socket), nil end end ), _pipe@8 = gleam@result:replace_error( _pipe@7, {stop, normal} ), _pipe@33 = gleam@result:map( _pipe@8, fun(Req) -> case gleam_erlang_ffi:rescue( fun() -> Handler(Req) end ) of {ok, {response, {response, _@23, _@24, {bit_builder_body, Body}} = Resp}} -> _pipe@9 = Resp, _pipe@10 = gleam@http@response:set_body( _pipe@9, Body ), _pipe@11 = mist@encoder:to_bit_builder( _pipe@10 ), _pipe@12 = glisten@tcp:send( Socket, _pipe@11 ), _pipe@13 = gleam@result:map( _pipe@12, fun(_) -> case gleam@http@response:get_header( Resp, <<"connection"/utf8>> ) of {ok, <<"close"/utf8>>} -> glisten@tcp:close(Socket), {stop, normal}; _@25 -> Timer = gleam@otp@process:send_after( Sender, 10000, close ), {continue, erlang:setelement( 4, Socket_state, erlang:setelement( 2, State, {some, Timer} ) )} end end ), _pipe@14 = gleam@result:replace_error( _pipe@13, {stop, normal} ), gleam@result:unwrap_both(_pipe@14); {ok, {response, {response, _@26, _@27, {file_body, File_descriptor, Content_type, Offset, Length}} = Resp@1}} -> _pipe@15 = Resp@1, _pipe@16 = gleam@http@response:prepend_header( _pipe@15, <<"content-length"/utf8>>, gleam@int:to_string(Length - Offset) ), _pipe@17 = gleam@http@response:prepend_header( _pipe@16, <<"content-type"/utf8>>, Content_type ), _pipe@18 = gleam@http@response:set_body( _pipe@17, gleam@bit_builder:new() ), _pipe@19 = (fun(R) -> mist@encoder:response_builder( erlang:element(2, Resp@1), erlang:element(3, R) ) end)(_pipe@18), _pipe@20 = glisten@tcp:send( Socket, _pipe@19 ), _pipe@21 = gleam@result:map( _pipe@20, fun(_) -> mist@file:sendfile( File_descriptor, Socket, Offset, Length, [] ) end ), _pipe@22 = gleam@result:replace( _pipe@21, {continue, Socket_state} ), _pipe@23 = gleam@result:replace_error( _pipe@22, {stop, normal} ), gleam@result:unwrap_both(_pipe@23); {ok, {upgrade, With_handler}} -> _pipe@24 = Req, _pipe@25 = upgrade(Socket, _pipe@24), _pipe@26 = gleam@result:map( _pipe@25, fun(_) -> _@29 = case erlang:element( 3, With_handler ) of {some, Func@3} -> Func@3(Sender); _@28 -> nil end end ), _pipe@27 = gleam@result:replace( _pipe@26, {continue, erlang:setelement( 4, Socket_state, erlang:setelement( 3, State, {some, With_handler} ) )} ), _pipe@28 = gleam@result:replace_error( _pipe@27, {stop, normal} ), gleam@result:unwrap_both(_pipe@28); {error, {exited, Msg@3} = Err@2} -> mist@logger:error(Err@2), _pipe@29 = gleam@http@response:new(500), _pipe@30 = gleam@http@response:set_body( _pipe@29, gleam@bit_builder:from_bit_string( <<"Internal Server Error"/utf8>> ) ), _pipe@31 = gleam@http@response:prepend_header( _pipe@30, <<"content-length"/utf8>>, <<"21"/utf8>> ), _pipe@32 = mist@encoder:to_bit_builder( _pipe@31 ), glisten@tcp:send(Socket, _pipe@32), glisten@tcp:close(Socket), {stop, {abnormal, Msg@3}}; {error, {thrown, Msg@3} = Err@2} -> mist@logger:error(Err@2), _pipe@29 = gleam@http@response:new(500), _pipe@30 = gleam@http@response:set_body( _pipe@29, gleam@bit_builder:from_bit_string( <<"Internal Server Error"/utf8>> ) ), _pipe@31 = gleam@http@response:prepend_header( _pipe@30, <<"content-length"/utf8>>, <<"21"/utf8>> ), _pipe@32 = mist@encoder:to_bit_builder( _pipe@31 ), glisten@tcp:send(Socket, _pipe@32), glisten@tcp:close(Socket), {stop, {abnormal, Msg@3}}; {error, {errored, Msg@3} = Err@2} -> mist@logger:error(Err@2), _pipe@29 = gleam@http@response:new(500), _pipe@30 = gleam@http@response:set_body( _pipe@29, gleam@bit_builder:from_bit_string( <<"Internal Server Error"/utf8>> ) ), _pipe@31 = gleam@http@response:prepend_header( _pipe@30, <<"content-length"/utf8>>, <<"21"/utf8>> ), _pipe@32 = mist@encoder:to_bit_builder( _pipe@31 ), glisten@tcp:send(Socket, _pipe@32), glisten@tcp:close(Socket), {stop, {abnormal, Msg@3}} end end ), gleam@result:unwrap_both(_pipe@33) end end ). -spec upgrade_socket(gleam@http@request:request(body())) -> {ok, gleam@http@response:response(gleam@bit_builder:bit_builder())} | {error, gleam@http@request:request(body())}. upgrade_socket(Req) -> case begin _pipe = gleam@http@request:get_header(Req, <<"upgrade"/utf8>>), gleam@result:replace_error(_pipe, Req) end of {error, _try} -> {error, _try}; {ok, _@1} -> case begin _pipe@1 = gleam@http@request:get_header( Req, <<"sec-websocket-key"/utf8>> ), gleam@result:replace_error(_pipe@1, Req) end of {error, _try@1} -> {error, _try@1}; {ok, Key} -> case begin _pipe@2 = gleam@http@request:get_header( Req, <<"sec-websocket-version"/utf8>> ), gleam@result:replace_error(_pipe@2, Req) end of {error, _try@2} -> {error, _try@2}; {ok, _@2} -> Accept_key = mist@websocket:parse_key(Key), _pipe@3 = gleam@http@response:new(101), _pipe@4 = gleam@http@response:set_body( _pipe@3, gleam@bit_builder:from_bit_string(<<""/utf8>>) ), _pipe@5 = gleam@http@response:prepend_header( _pipe@4, <<"Upgrade"/utf8>>, <<"websocket"/utf8>> ), _pipe@6 = gleam@http@response:prepend_header( _pipe@5, <<"Connection"/utf8>>, <<"Upgrade"/utf8>> ), _pipe@7 = gleam@http@response:prepend_header( _pipe@6, <<"Sec-WebSocket-Accept"/utf8>>, Accept_key ), {ok, _pipe@7} end end end. -spec upgrade(glisten@tcp:socket(), gleam@http@request:request(body())) -> {ok, nil} | {error, nil}. upgrade(Socket, Req) -> case begin _pipe = upgrade_socket(Req), gleam@result:replace_error(_pipe, nil) end of {error, _try} -> {error, _try}; {ok, Resp} -> case begin _pipe@1 = Resp, _pipe@2 = mist@encoder:to_bit_builder(_pipe@1), _pipe@3 = glisten@tcp:send(Socket, _pipe@2), gleam@result:replace_error(_pipe@3, nil) end of {error, _try@1} -> {error, _try@1}; {ok, _@1} -> {ok, nil} end end.