Current section
Files
Jump to
Current section
Files
src/gleam@http.erl
-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.