diff options
Diffstat (limited to 'aoc2023/build/packages/gleam_http/src/gleam@http.erl')
-rw-r--r-- | aoc2023/build/packages/gleam_http/src/gleam@http.erl | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/aoc2023/build/packages/gleam_http/src/gleam@http.erl b/aoc2023/build/packages/gleam_http/src/gleam@http.erl new file mode 100644 index 0000000..91ee6e8 --- /dev/null +++ b/aoc2023/build/packages/gleam_http/src/gleam@http.erl @@ -0,0 +1,626 @@ +-module(gleam@http). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). + +-export([parse_method/1, method_to_string/1, scheme_to_string/1, scheme_from_string/1, parse_content_disposition/1, parse_multipart_body/2, method_from_dynamic/1, parse_multipart_headers/2]). +-export_type([method/0, scheme/0, multipart_headers/0, multipart_body/0, content_disposition/0]). + +-type method() :: get | + post | + head | + put | + delete | + trace | + connect | + options | + patch | + {other, binary()}. + +-type scheme() :: http | https. + +-type multipart_headers() :: {multipart_headers, + list({binary(), binary()}), + bitstring()} | + {more_required_for_headers, + fun((bitstring()) -> {ok, multipart_headers()} | {error, nil})}. + +-type multipart_body() :: {multipart_body, bitstring(), boolean(), bitstring()} | + {more_required_for_body, + bitstring(), + fun((bitstring()) -> {ok, multipart_body()} | {error, nil})}. + +-type content_disposition() :: {content_disposition, + binary(), + list({binary(), binary()})}. + +-spec parse_method(binary()) -> {ok, method()} | {error, nil}. +parse_method(S) -> + case gleam@string:lowercase(S) of + <<"connect"/utf8>> -> + {ok, connect}; + + <<"delete"/utf8>> -> + {ok, delete}; + + <<"get"/utf8>> -> + {ok, get}; + + <<"head"/utf8>> -> + {ok, head}; + + <<"options"/utf8>> -> + {ok, options}; + + <<"patch"/utf8>> -> + {ok, patch}; + + <<"post"/utf8>> -> + {ok, post}; + + <<"put"/utf8>> -> + {ok, put}; + + <<"trace"/utf8>> -> + {ok, trace}; + + _ -> + {error, nil} + end. + +-spec method_to_string(method()) -> binary(). +method_to_string(Method) -> + case Method of + connect -> + <<"connect"/utf8>>; + + delete -> + <<"delete"/utf8>>; + + get -> + <<"get"/utf8>>; + + head -> + <<"head"/utf8>>; + + options -> + <<"options"/utf8>>; + + patch -> + <<"patch"/utf8>>; + + post -> + <<"post"/utf8>>; + + put -> + <<"put"/utf8>>; + + trace -> + <<"trace"/utf8>>; + + {other, S} -> + S + end. + +-spec scheme_to_string(scheme()) -> binary(). +scheme_to_string(Scheme) -> + case Scheme of + http -> + <<"http"/utf8>>; + + https -> + <<"https"/utf8>> + end. + +-spec scheme_from_string(binary()) -> {ok, scheme()} | {error, nil}. +scheme_from_string(Scheme) -> + case gleam@string:lowercase(Scheme) of + <<"http"/utf8>> -> + {ok, http}; + + <<"https"/utf8>> -> + {ok, https}; + + _ -> + {error, nil} + end. + +-spec skip_whitespace(bitstring()) -> bitstring(). +skip_whitespace(Data) -> + case Data of + <<32, Data@1/binary>> -> + skip_whitespace(Data@1); + + <<9, Data@1/binary>> -> + skip_whitespace(Data@1); + + _ -> + Data + end. + +-spec more_please_headers( + fun((bitstring()) -> {ok, multipart_headers()} | {error, nil}), + bitstring() +) -> {ok, multipart_headers()} | {error, nil}. +more_please_headers(Continuation, Existing) -> + {ok, + {more_required_for_headers, + fun(More) -> + gleam@bool:guard( + More =:= <<>>, + {error, nil}, + fun() -> + Continuation(<<Existing/bitstring, More/bitstring>>) + end + ) + end}}. + +-spec parse_rfc_2045_parameter_quoted_value(binary(), binary(), binary()) -> {ok, + {{binary(), binary()}, binary()}} | + {error, nil}. +parse_rfc_2045_parameter_quoted_value(Header, Name, Value) -> + case gleam@string:pop_grapheme(Header) of + {error, nil} -> + {error, nil}; + + {ok, {<<"\""/utf8>>, Rest}} -> + {ok, {{Name, Value}, Rest}}; + + {ok, {<<"\\"/utf8>>, Rest@1}} -> + gleam@result:'try'( + gleam@string:pop_grapheme(Rest@1), + fun(_use0) -> + {Grapheme, Rest@2} = _use0, + parse_rfc_2045_parameter_quoted_value( + Rest@2, + Name, + <<Value/binary, Grapheme/binary>> + ) + end + ); + + {ok, {Grapheme@1, Rest@3}} -> + parse_rfc_2045_parameter_quoted_value( + Rest@3, + Name, + <<Value/binary, Grapheme@1/binary>> + ) + end. + +-spec parse_rfc_2045_parameter_unquoted_value(binary(), binary(), binary()) -> {{binary(), + binary()}, + binary()}. +parse_rfc_2045_parameter_unquoted_value(Header, Name, Value) -> + case gleam@string:pop_grapheme(Header) of + {error, nil} -> + {{Name, Value}, Header}; + + {ok, {<<";"/utf8>>, Rest}} -> + {{Name, Value}, Rest}; + + {ok, {<<" "/utf8>>, Rest}} -> + {{Name, Value}, Rest}; + + {ok, {<<"\t"/utf8>>, Rest}} -> + {{Name, Value}, Rest}; + + {ok, {Grapheme, Rest@1}} -> + parse_rfc_2045_parameter_unquoted_value( + Rest@1, + Name, + <<Value/binary, Grapheme/binary>> + ) + end. + +-spec parse_rfc_2045_parameter_value(binary(), binary()) -> {ok, + {{binary(), binary()}, binary()}} | + {error, nil}. +parse_rfc_2045_parameter_value(Header, Name) -> + case gleam@string:pop_grapheme(Header) of + {error, nil} -> + {error, nil}; + + {ok, {<<"\""/utf8>>, Rest}} -> + parse_rfc_2045_parameter_quoted_value(Rest, Name, <<""/utf8>>); + + {ok, {Grapheme, Rest@1}} -> + {ok, + parse_rfc_2045_parameter_unquoted_value(Rest@1, Name, Grapheme)} + end. + +-spec parse_rfc_2045_parameter(binary(), binary()) -> {ok, + {{binary(), binary()}, binary()}} | + {error, nil}. +parse_rfc_2045_parameter(Header, Name) -> + gleam@result:'try'( + gleam@string:pop_grapheme(Header), + fun(_use0) -> + {Grapheme, Rest} = _use0, + case Grapheme of + <<"="/utf8>> -> + parse_rfc_2045_parameter_value(Rest, Name); + + _ -> + parse_rfc_2045_parameter( + Rest, + <<Name/binary, + (gleam@string:lowercase(Grapheme))/binary>> + ) + end + end + ). + +-spec parse_rfc_2045_parameters(binary(), list({binary(), binary()})) -> {ok, + list({binary(), binary()})} | + {error, nil}. +parse_rfc_2045_parameters(Header, Parameters) -> + case gleam@string:pop_grapheme(Header) of + {error, nil} -> + {ok, gleam@list:reverse(Parameters)}; + + {ok, {<<";"/utf8>>, Rest}} -> + parse_rfc_2045_parameters(Rest, Parameters); + + {ok, {<<" "/utf8>>, Rest}} -> + parse_rfc_2045_parameters(Rest, Parameters); + + {ok, {<<"\t"/utf8>>, Rest}} -> + parse_rfc_2045_parameters(Rest, Parameters); + + {ok, {Grapheme, Rest@1}} -> + Acc = gleam@string:lowercase(Grapheme), + gleam@result:'try'( + parse_rfc_2045_parameter(Rest@1, Acc), + fun(_use0) -> + {Parameter, Rest@2} = _use0, + parse_rfc_2045_parameters(Rest@2, [Parameter | Parameters]) + end + ) + end. + +-spec parse_content_disposition_type(binary(), binary()) -> {ok, + content_disposition()} | + {error, nil}. +parse_content_disposition_type(Header, Name) -> + case gleam@string:pop_grapheme(Header) of + {error, nil} -> + {ok, {content_disposition, Name, []}}; + + {ok, {<<" "/utf8>>, Rest}} -> + Result = parse_rfc_2045_parameters(Rest, []), + gleam@result:map( + Result, + fun(Parameters) -> {content_disposition, Name, Parameters} end + ); + + {ok, {<<"\t"/utf8>>, Rest}} -> + Result = parse_rfc_2045_parameters(Rest, []), + gleam@result:map( + Result, + fun(Parameters) -> {content_disposition, Name, Parameters} end + ); + + {ok, {<<";"/utf8>>, Rest}} -> + Result = parse_rfc_2045_parameters(Rest, []), + gleam@result:map( + Result, + fun(Parameters) -> {content_disposition, Name, Parameters} end + ); + + {ok, {Grapheme, Rest@1}} -> + parse_content_disposition_type( + Rest@1, + <<Name/binary, (gleam@string:lowercase(Grapheme))/binary>> + ) + end. + +-spec parse_content_disposition(binary()) -> {ok, content_disposition()} | + {error, nil}. +parse_content_disposition(Header) -> + parse_content_disposition_type(Header, <<""/utf8>>). + +-spec more_please_body( + fun((bitstring()) -> {ok, multipart_body()} | {error, nil}), + bitstring(), + bitstring() +) -> {ok, multipart_body()} | {error, nil}. +more_please_body(Continuation, Chunk, Existing) -> + _pipe = fun(More) -> + gleam@bool:guard( + More =:= <<>>, + {error, nil}, + fun() -> Continuation(<<Existing/bitstring, More/bitstring>>) end + ) + end, + _pipe@1 = {more_required_for_body, Chunk, _pipe}, + {ok, _pipe@1}. + +-spec parse_body_loop(bitstring(), bitstring(), bitstring()) -> {ok, + multipart_body()} | + {error, nil}. +parse_body_loop(Data, Boundary, Body) -> + Dsize = erlang:byte_size(Data), + Bsize = erlang:byte_size(Boundary), + Required = 6 + Bsize, + case Data of + _ when Dsize < Required -> + more_please_body( + fun(_capture) -> parse_body_loop(_capture, Boundary, <<>>) end, + Body, + Data + ); + + <<13, 10, Data@1/binary>> -> + Desired = <<45, 45, Boundary/bitstring>>, + Size = erlang:byte_size(Desired), + Dsize@1 = erlang:byte_size(Data@1), + Prefix = gleam_stdlib:bit_array_slice(Data@1, 0, Size), + Rest = gleam_stdlib:bit_array_slice(Data@1, Size, Dsize@1 - Size), + case {Prefix =:= {ok, Desired}, Rest} of + {true, {ok, <<13, 10, _/binary>>}} -> + {ok, {multipart_body, Body, false, Data@1}}; + + {true, {ok, <<45, 45, Data@2/binary>>}} -> + {ok, {multipart_body, Body, true, Data@2}}; + + {false, _} -> + parse_body_loop( + Data@1, + Boundary, + <<Body/bitstring, 13, 10>> + ); + + {_, _} -> + {error, nil} + end; + + <<Char, Data@3/binary>> -> + parse_body_loop(Data@3, Boundary, <<Body/bitstring, Char>>) + end. + +-spec parse_body_with_bit_array(bitstring(), bitstring()) -> {ok, + multipart_body()} | + {error, nil}. +parse_body_with_bit_array(Data, Boundary) -> + Bsize = erlang:byte_size(Boundary), + Prefix = gleam_stdlib:bit_array_slice(Data, 0, 2 + Bsize), + case Prefix =:= {ok, <<45, 45, Boundary/bitstring>>} of + true -> + {ok, {multipart_body, <<>>, false, Data}}; + + false -> + parse_body_loop(Data, Boundary, <<>>) + end. + +-spec parse_multipart_body(bitstring(), binary()) -> {ok, multipart_body()} | + {error, nil}. +parse_multipart_body(Data, Boundary) -> + _pipe = Boundary, + _pipe@1 = gleam_stdlib:identity(_pipe), + parse_body_with_bit_array(Data, _pipe@1). + +-spec method_from_dynamic(gleam@dynamic:dynamic_()) -> {ok, method()} | + {error, list(gleam@dynamic:decode_error())}. +method_from_dynamic(Value) -> + case gleam_http_native:decode_method(Value) of + {ok, Method} -> + {ok, Method}; + + {error, _} -> + {error, + [{decode_error, + <<"HTTP method"/utf8>>, + gleam@dynamic:classify(Value), + []}]} + end. + +-spec parse_header_value( + bitstring(), + list({binary(), binary()}), + bitstring(), + bitstring() +) -> {ok, multipart_headers()} | {error, nil}. +parse_header_value(Data, Headers, Name, Value) -> + Size = erlang:byte_size(Data), + case Data of + _ when Size < 4 -> + _pipe@2 = fun(Data@1) -> _pipe = Data@1, + _pipe@1 = skip_whitespace(_pipe), + parse_header_value(_pipe@1, Headers, Name, Value) end, + more_please_headers(_pipe@2, Data); + + <<13, 10, 13, 10, Data@2/binary>> -> + gleam@result:'try'( + gleam@bit_array:to_string(Name), + fun(Name@1) -> + gleam@result:map( + gleam@bit_array:to_string(Value), + fun(Value@1) -> + Headers@1 = gleam@list:reverse( + [{gleam@string:lowercase(Name@1), Value@1} | + Headers] + ), + {multipart_headers, Headers@1, Data@2} + end + ) + end + ); + + <<13, 10, 32, Data@3/binary>> -> + parse_header_value(Data@3, Headers, Name, Value); + + <<13, 10, 9, Data@3/binary>> -> + parse_header_value(Data@3, Headers, Name, Value); + + <<13, 10, Data@4/binary>> -> + gleam@result:'try'( + gleam@bit_array:to_string(Name), + fun(Name@2) -> + gleam@result:'try'( + gleam@bit_array:to_string(Value), + fun(Value@2) -> + Headers@2 = [{gleam@string:lowercase(Name@2), + Value@2} | + Headers], + parse_header_name(Data@4, Headers@2, <<>>) + end + ) + end + ); + + <<Char, Rest/binary>> -> + Value@3 = <<Value/bitstring, Char>>, + parse_header_value(Rest, Headers, Name, Value@3); + + _ -> + {error, nil} + end. + +-spec parse_header_name(bitstring(), list({binary(), binary()}), bitstring()) -> {ok, + multipart_headers()} | + {error, nil}. +parse_header_name(Data, Headers, Name) -> + case skip_whitespace(Data) of + <<58, Data@1/binary>> -> + _pipe = Data@1, + _pipe@1 = skip_whitespace(_pipe), + parse_header_value(_pipe@1, Headers, Name, <<>>); + + <<Char, Data@2/binary>> -> + parse_header_name(Data@2, Headers, <<Name/bitstring, Char>>); + + <<>> -> + more_please_headers( + fun(_capture) -> parse_header_name(_capture, Headers, Name) end, + Data + ) + end. + +-spec do_parse_headers(bitstring()) -> {ok, multipart_headers()} | {error, nil}. +do_parse_headers(Data) -> + case Data of + <<13, 10, 13, 10, Data@1/binary>> -> + {ok, {multipart_headers, [], Data@1}}; + + <<13, 10, Data@2/binary>> -> + parse_header_name(Data@2, [], <<>>); + + <<13>> -> + more_please_headers(fun do_parse_headers/1, Data); + + <<>> -> + more_please_headers(fun do_parse_headers/1, Data); + + _ -> + {error, nil} + end. + +-spec parse_headers_after_prelude(bitstring(), bitstring()) -> {ok, + multipart_headers()} | + {error, nil}. +parse_headers_after_prelude(Data, Boundary) -> + Dsize = erlang:byte_size(Data), + Bsize = erlang:byte_size(Boundary), + Required_size = Bsize + 4, + gleam@bool:guard( + Dsize < Required_size, + more_please_headers( + fun(_capture) -> parse_headers_after_prelude(_capture, Boundary) end, + Data + ), + fun() -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice(Data, 0, Required_size - 2), + fun(Prefix) -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice(Data, 2 + Bsize, 2), + fun(Second) -> + Desired = <<45, 45, Boundary/bitstring>>, + gleam@bool:guard( + Prefix /= Desired, + {error, nil}, + fun() -> case Second =:= <<45, 45>> of + true -> + Rest_size = Dsize - Required_size, + gleam@result:map( + gleam_stdlib:bit_array_slice( + Data, + Required_size, + Rest_size + ), + fun(Data@1) -> + {multipart_headers, + [], + Data@1} + end + ); + + false -> + Start = Required_size - 2, + Rest_size@1 = (Dsize - Required_size) + + 2, + gleam@result:'try'( + gleam_stdlib:bit_array_slice( + Data, + Start, + Rest_size@1 + ), + fun(Data@2) -> + do_parse_headers(Data@2) + end + ) + end end + ) + end + ) + end + ) + end + ). + +-spec skip_preamble(bitstring(), bitstring()) -> {ok, multipart_headers()} | + {error, nil}. +skip_preamble(Data, Boundary) -> + Data_size = erlang:byte_size(Data), + Boundary_size = erlang:byte_size(Boundary), + Required = Boundary_size + 4, + case Data of + _ when Data_size < Required -> + more_please_headers( + fun(_capture) -> skip_preamble(_capture, Boundary) end, + Data + ); + + <<13, 10, 45, 45, Data@1/binary>> -> + case gleam_stdlib:bit_array_slice(Data@1, 0, Boundary_size) of + {ok, Prefix} when Prefix =:= Boundary -> + Start = Boundary_size, + Length = erlang:byte_size(Data@1) - Boundary_size, + gleam@result:'try'( + gleam_stdlib:bit_array_slice(Data@1, Start, Length), + fun(Rest) -> do_parse_headers(Rest) end + ); + + {ok, _} -> + skip_preamble(Data@1, Boundary); + + {error, _} -> + {error, nil} + end; + + <<_, Data@2/binary>> -> + skip_preamble(Data@2, Boundary) + end. + +-spec parse_multipart_headers(bitstring(), binary()) -> {ok, + multipart_headers()} | + {error, nil}. +parse_multipart_headers(Data, Boundary) -> + Boundary@1 = gleam_stdlib:identity(Boundary), + Prefix = <<45, 45, Boundary@1/bitstring>>, + case gleam_stdlib:bit_array_slice(Data, 0, erlang:byte_size(Prefix)) =:= {ok, + Prefix} of + true -> + parse_headers_after_prelude(Data, Boundary@1); + + false -> + skip_preamble(Data, Boundary@1) + end. |