fewd/src/qhl.erl

1099 lines
38 KiB
Erlang

%%% @doc
%%% The Quick HTTP Library
%%% This is not an HTTP server, but rather a library from which a fast HTTP server
%%% can be built. Building out a server with QHL requires minimum effort but maximum
%%% knowledge. Calling code needs to maintain the socket, own the process loop, etc.
%%%
%%% QHL does not help you form the return response body -- that is entirely up to you.
%%% @end
-module(qhl).
-vsn("0.2.0").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("MIT").
-export([parse/2, parse/3,
recv/2, resp/2,
percent_encode/1, percent_decode/1, join/1,
ridiculous_web_date/0, ridiculous_web_date/1,
slogan/1,
break_nl/1, unbreak_nl/1, clean/1,
bin_to_int/1]).
-include("http.hrl").
-spec parse(Socket, Received) -> Result
when Socket :: gen_tcp:socket(),
Received :: binary(),
Result :: {ok, #request{}} | {error, Reason},
Reason :: term(). % FIXME
%% @doc
%% @equiv parse(Socket, Received, #request{})
parse(Socket, Received) ->
parse(Socket, Received, #request{}).
-spec parse(Socket, Received, Request) -> Result
when Socket :: gen_tcp:socket(),
Received :: binary(),
Request :: #request{},
Result :: {ok, #request{}} | {error, Reason},
Reason :: term(). % FIXME
%5 @doc
%% Pass this function the connection socket and whatever the receive loop has acquired,
%% and receive back a parsed #request{} record. This requires that you have imported
%% the `http.hrl' and set `{mode, binary}' and `{active, once}' options on the
%% socket.
parse(Socket, Received, M = #request{method = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_method(Socket, Received) of
{ok, Method, Rest} -> parse(Socket, Rest, M#request{method = Method});
Error -> Error
end;
parse(Socket, Received, M = #request{path = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_path(Socket, Received) of
{ok, Path, Rest} -> parse(Socket, Rest, M#request{path = Path});
Error -> Error
end;
parse(Socket, Received, M = #request{qargs = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_qargs(Socket, Received) of
{ok, Qargs, Rest} -> parse(Socket, Rest, M#request{qargs = Qargs});
Error -> Error
end;
parse(Socket, Received, M = #request{fragment = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_fragment(Socket, Received) of
{ok, Fragment, Rest} -> parse(Socket, Rest, M#request{fragment = Fragment});
Error -> Error
end;
parse(Socket, Received, M = #request{version = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_version(Socket, Received) of
{ok, Version, Rest} -> parse(Socket, Rest, M#request{version = Version});
Error -> Error
end;
parse(Socket, Received, M = #request{headers = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_headers(Socket, Received) of
{ok, Headers, Rest} -> parse(Socket, Rest, M#request{headers = Headers});
Error -> Error
end;
parse(Socket, Received, M = #request{enctype = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_enctype(M) of
{ok, Enctype} -> parse(Socket, Received, M#request{enctype = Enctype});
Error -> Error
end;
parse(Socket, Received, M = #request{cookies = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_cookies(M) of
{ok, Cookies} -> parse(Socket, Received, M#request{cookies = Cookies});
Error -> Error
end;
parse(Socket, Received, M = #request{size = undefined}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_size(M) of
{ok, 0} -> {ok, M#request{size = 0}, none};
{ok, Size} -> parse(Socket, Received, M#request{size = Size});
Error -> Error
end;
parse(Socket, Received, M = #request{method = get, body = undefined, size = Size}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_body(Received, Size) of
{ok, Body} -> {ok, M#request{body = Body}, none};
{ok, Body, Next} -> {ok, M#request{body = Body}, Next};
{incomplete, Body} -> accumulate(Socket, M#request{body = Body});
Error -> Error
end;
parse(Socket,
Received,
M = #request{body = undefined,
method = post,
enctype = urlencoded,
size = Size}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_body(Received, Size) of
{ok, Body} ->
{ok, M#request{body = parts_to_map(posted(Body))}, none};
{ok, Body, Next} ->
{ok, M#request{body = parts_to_map(posted(Body))}, Next};
{incomplete, Body} ->
case accumulate(Socket, M#request{body = Body}) of
{ok, NewM = #request{body = NewBody}} ->
{ok, NewM#request{body = parts_to_map(posted(NewBody))}, none};
{ok, NewM = #request{body = NewBody}, More} ->
{ok, NewM#request{body = parts_to_map(posted(NewBody))}, More};
Error ->
Error
end;
Error ->
Error
end;
parse(Socket,
Received,
M = #request{body = undefined,
method = post,
enctype = {multipart, Boundary},
size = Size}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_multipart(Socket, Received, Boundary, Size) of
{ok, Parts} -> {ok, M#request{body = parts_to_map(Parts)}, none};
{ok, Parts, Next} -> {ok, M#request{body = parts_to_map(Parts)}, Next};
Error -> Error
end;
parse(Socket,
Received,
M = #request{body = undefined,
method = post,
enctype = json,
size = Size}) ->
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_body(Received, Size) of
{ok, Body} -> read_json(M#request{body = Body}, none);
{ok, Body, Next} -> read_json(M#request{body = Body}, Next);
{incomplete, Body} ->
io:format("~p {incomplete, ~p}~n", [?LINE, Body]),
case accumulate(Socket, M#request{body = Body}) of
{ok, NewM = #request{body = NewBody}} ->
read_json(NewM#request{body = NewBody}, none);
{ok, NewM = #request{body = NewBody}, More} ->
read_json(NewM#request{body = NewBody}, More);
Error ->
Error
end;
Error ->
Error
end.
read_json(R = #request{body = Text}, Next) ->
case zj:decode(Text) of
{ok, JSON} -> {ok, R#request{body = JSON}, Next};
Error -> Error
end.
% FIXME: The map version of body elements is inferior to the proplist version.
% When checking input elements it is much nicer to have a general return
% over a fold that can contain errors and use this as a universal idiom
% and then build "unfuck your input please" type error pages for the user
% based on that than matching map keys and then writing exhaustive drop-through
% checks when all this could happen in a single pass more simply.
parts_to_map(Parts) ->
lists:foldl(fun parts_to_map/2, #{}, Parts).
parts_to_map({Key, Value}, Map) ->
AddPart = fun(Values) -> [Value | Values] end,
maps:update_with(Key, AddPart, [Value], Map).
read_method(_, <<"GET ", Rest/binary>>) ->
{ok, get, Rest};
read_method(_, <<"POST ", Rest/binary>>) ->
{ok, post, Rest};
read_method(_, <<"OPTIONS ", Rest/binary>>) ->
{ok, options, Rest};
read_method(Socket, Received) when byte_size(Received) < 7 ->
case inet:setopts(Socket, [{active, once}]) of
ok ->
receive
{tcp, Socket, Bin} ->
read_method(Socket, <<Received/binary, Bin/binary>>);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
{error, Reason} ->
{error, {inet, Reason}}
end;
read_method(_, _) ->
{error, method}.
read_path(Socket, Received) ->
read_path(Socket, Received, <<>>).
read_path(_, <<" ", _/binary>>, <<>>) ->
{error, path};
read_path(_, <<"\r", _/binary>>, _) ->
{error, path};
read_path(_, <<"\n", _/binary>>, _) ->
{error, path};
read_path(_, <<" ", Rest/binary>>, Path) ->
{ok, Path, Rest};
read_path(_, Rest = <<"?", _/binary>>, Path) ->
{ok, Path, Rest};
read_path(Socket, <<>>, Acc) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_path(Socket, Bin, Acc);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
read_path(Socket, <<Char, Rest/binary>>, Acc) ->
read_path(Socket, Rest, <<Acc/binary, Char>>).
read_qargs(_, <<"? ", Other/binary>>) ->
{ok, #{}, Other};
read_qargs(Socket, <<"?", QString/binary>>) ->
read_qargs(Socket, QString, #{});
read_qargs(_, Other) ->
{ok, #{}, Other}.
read_qargs(Socket, Bin, Qargs) ->
read_qkey(Socket, Bin, <<>>, Qargs).
read_qkey(_, <<" ", _/binary>>, _, _) ->
{error, qargs};
read_qkey(_, <<"\r", _/binary>>, _, _) ->
{error, qargs};
read_qkey(_, <<"\n", _/binary>>, _, _) ->
{error, qargs};
read_qkey(_, <<"=", _/binary>>, <<>>, _) ->
{error, qargs};
read_qkey(Socket, <<"=", Rest/binary>>, Key, Qargs) ->
read_qval(Socket, Rest, <<>>, Key, Qargs);
read_qkey(Socket, <<Char, Rest/binary>>, Acc, Qargs) ->
read_qkey(Socket, Rest, <<Acc/binary, Char>>, Qargs);
read_qkey(Socket, <<>>, Acc, Qargs) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_qkey(Socket, Bin, Acc, Qargs);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end.
read_qval(_, <<" ", _/binary>>, <<>>, _, _) ->
{error, qargs};
read_qval(_, <<" ", Rest/binary>>, Val, Key, Qargs) ->
{ok, maps:put(Key, Val, Qargs), Rest};
read_qval(_, <<"&", _/binary>>, <<>>, _, _) ->
{error, qargs};
read_qval(Socket, <<"&", Rest/binary>>, Val, Key, Qargs) ->
read_qargs(Socket, Rest, maps:put(Key, Val, Qargs));
read_qval(_, <<"?", _/binary>>, _, _, _) ->
{error, qargs};
read_qval(_, <<"\r", _/binary>>, _, _, _) ->
{error, qargs};
read_qval(_, <<"\n", _/binary>>, _, _, _) ->
{error, qargs};
read_qval(Socket, <<Char, Rest/binary>>, Acc, Key, Qargs) ->
read_qval(Socket, Rest, <<Acc/binary, Char>>, Key, Qargs);
read_qval(Socket, <<>>, Acc, Key, Qargs) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_qval(Socket, Bin, Acc, Key, Qargs);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end.
read_fragment(Socket, <<"#", Rest/binary>>) ->
read_fragment(Socket, Rest, <<>>);
read_fragment(_, Rest) ->
{ok, none, Rest}.
read_fragment(_, <<" ", Rest/binary>>, <<>>) ->
{ok, none, Rest};
read_fragment(_, <<" ", Rest/binary>>, Fragment) ->
{ok, Fragment, Rest};
read_fragment(_, <<"\r", _/binary>>, _) ->
{error, fragment};
read_fragment(_, <<"\n", _/binary>>, _) ->
{error, fragment};
read_fragment(Socket, <<Char, Rest/binary>>, Acc) ->
read_fragment(Socket, Rest, <<Acc/binary, Char>>);
read_fragment(Socket, <<>>, Acc) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_fragment(Socket, Bin, Acc);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end.
read_version(_, <<"HTTP/1.0\r\n", Rest/binary>>) when byte_size(Rest) > 0 ->
{ok, http10, Rest};
read_version(_, <<"HTTP/1.1\r\n", Rest/binary>>) when byte_size(Rest) > 0 ->
{ok, http11, Rest};
read_version(_, <<"HTTP/2.0\r\n", Rest/binary>>) when byte_size(Rest) > 0 ->
{ok, http20, Rest};
read_version(Socket, Acc) when byte_size(Acc) =< 10 ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_version(Socket, <<Acc/binary, Bin/binary>>);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
read_version(_, _) ->
{error, version}.
read_headers(Socket, <<"\r">>) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_headers(Socket, <<"\r", Bin/binary>>);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
read_headers(_, <<"\r\n", _/binary>>) ->
{error, headers};
read_headers(Socket, Received) ->
read_hkey(Socket, Received, <<>>, #{}).
read_hkey(Socket, <<Char, Rest/binary>>, Acc, Headers)
when $A =< Char, Char =< $Z ->
read_hkey(Socket, Rest, <<Acc/binary, (Char + 32)>>, Headers);
read_hkey(Socket, <<Char, Rest/binary>>, Acc, Headers)
when 32 =< Char, Char =< 57;
59 =< Char, Char =< 126 ->
read_hkey(Socket, Rest, <<Acc/binary, Char>>, Headers);
read_hkey(Socket, <<":", Rest/binary>>, Key, Headers) ->
skip_hblanks(Socket, Rest, Key, Headers);
read_hkey(_, <<"\r\n", Rest/binary>>, <<>>, Headers) ->
{ok, Headers, Rest};
read_hkey(Socket, <<>>, Acc, Headers) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_hkey(Socket, Bin, Acc, Headers);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
read_hkey(_, _, _, _) ->
{error, headers}.
skip_hblanks(Socket, <<" ", Rest/binary>>, Key, Headers) ->
skip_hblanks(Socket, Rest, Key, Headers);
skip_hblanks(Socket, <<>>, Key, Headers) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
skip_hblanks(Socket, Bin, Key, Headers);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
skip_hblanks(Socket, Rest, Key, Headers) ->
read_hval(Socket, Rest, <<>>, Key, Headers).
read_hval(Socket, <<"\r\n", Rest/binary>>, Val, Key, Headers) ->
read_hkey(Socket, Rest, <<>>, maps:put(Key, Val, Headers));
read_hval(Socket, <<Char, Rest/binary>>, Acc, Key, Headers) ->
read_hval(Socket, Rest, <<Acc/binary, Char>>, Key, Headers);
read_hval(Socket, <<>>, Val, Key, Headers) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
read_hval(Socket, Bin, Val, Key, Headers);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
read_hval(_, _, _, _, _) ->
{error, headers}.
read_enctype(#request{method = get}) ->
{ok, none};
read_enctype(#request{method = post, headers = Headers}) ->
case maps:find(<<"content-type">>, Headers) of
{ok, <<"multipart/form-data;", Rest/binary>>} -> get_boundary(Rest);
{ok, <<"application/x-www-form-urlencoded">>} -> {ok, urlencoded};
{ok, <<"application/json">>} -> {ok, json};
{ok, WTF} -> {error, {enctype, WTF}};
error -> {error, no_enctype}
end;
read_enctype(#request{method = options}) ->
{ok, none}.
get_boundary(<<"boundary=", Boundary/binary>>) -> {ok, {multipart, Boundary}};
get_boundary(<<" ", Rest/binary>>) -> get_boundary(Rest);
get_boundary(<<>>) -> {error, no_boundary};
get_boundary(_) -> {error, bad_enctype}.
read_cookies(#request{headers = Headers}) ->
case maps:find(<<"cookie">>, Headers) of
{ok, Values} -> parse_cookies(Values);
error -> {ok, #{}}
end.
parse_cookies(Values) ->
{ok, read_ckey(Values, <<>>, #{})}.
read_ckey(<<"=", Rest/binary>>, Acc, Cookies) ->
read_cval(Rest, <<>>, Acc, Cookies);
read_ckey(<<Char, Rest/binary>>, Acc, Cookies) ->
read_ckey(Rest, <<Acc/binary, Char>>, Cookies);
read_ckey(<<>>, <<>>, Cookies) ->
Cookies;
read_ckey(<<>>, Key, Cookies) ->
case maps:is_key(Key, Cookies) of
true -> Cookies;
false -> maps:put(Key, <<>>, Cookies)
end.
read_cval(<<>>, Val, Key, Cookies) ->
maps:put(Key, Val, Cookies);
read_cval(<<";", Rest/binary>>, Val, Key, Cookies) ->
skip_cblanks(Rest, maps:put(Key, Val, Cookies));
read_cval(<<",", Rest/binary>>, Val, Key, Cookies) ->
skip_cblanks(Rest, maps:put(Key, Val, Cookies));
read_cval(<<Char, Rest/binary>>, Acc, Key, Cookies) ->
read_cval(Rest, <<Acc/binary, Char>>, Key, Cookies).
skip_cblanks(<<" ", Rest/binary>>, Cookies) ->
skip_cblanks(Rest, Cookies);
skip_cblanks(Rest, Cookies) ->
read_ckey(Rest, <<>>, Cookies).
read_size(#request{method = get}) ->
{ok, 0};
read_size(#request{method = post, enctype = {multipart, _}, headers = Headers}) ->
case maps:find(<<"content-length">>, Headers) of
error ->
{ok, none};
{ok, Size} ->
try
{ok, binary_to_integer(Size)}
catch
error:badarg -> {error, headers}
end
end;
read_size(#request{method = post, headers = Headers}) ->
case maps:find(<<"content-length">>, Headers) of
error ->
{ok, 0};
{ok, Size} ->
try
{ok, binary_to_integer(Size)}
catch
error:badarg -> {error, headers}
end
end;
read_size(#request{method = options}) ->
{ok, 0}.
read_body(Received, Size) ->
io:format("~p read_body(~p, ~p)~n", [?LINE, Received, Size]),
case Received of
<<Bin:Size/binary>> ->
{ok, Bin};
<<Bin:Size/binary, Next/binary>> ->
{ok, Bin, Next};
Bin when byte_size(Bin) < Size ->
{incomplete, Bin}
end.
read_multipart(Socket, Received, Boundary, none) ->
read_multipart3(Socket, Received, Boundary);
read_multipart(Socket, Received, Boundary, Size) ->
Remaining = Size - byte_size(Received),
case accumulate(Socket, Remaining, Received) of
{ok, Complete} -> read_multipart3(Socket, Complete, Boundary);
{ok, Complete, Next} -> read_multipart2(Socket, Complete, Boundary, Next);
Error -> Error
end.
read_multipart2(Socket, Received, Boundary, Next) ->
case read_multipart3(Socket, Received, Boundary) of
{ok, Parts} -> {ok, Parts, Next};
{ok, Parts, Cont} -> {ok, Parts, <<Next/binary, Cont/binary>>};
Error -> Error
end.
read_multipart3(Socket, Received, Boundary) ->
SEP = <<"--", Boundary/binary, "\r\n">>,
SS = byte_size(SEP),
EOF = <<"--", Boundary/binary, "--\r\n">>,
ES = byte_size(EOF),
Seps = [{{SEP, SS}, {EOF, ES}}],
case simple_multipart(Socket, Received, Seps, []) of
{ok, Parts} -> {ok, lists:reverse(Parts)};
{ok, Parts, Next} -> {ok, lists:reverse(Parts), Next};
Error -> Error
end.
simple_multipart(Socket, Received, [{{SEP, SS}, {EOF, ES}} | RemSeps], Parts)
when byte_size(Received) >= ES ->
case Received of
<<SEP:SS/binary, Rest/binary>> ->
NewSep =
{{<<"\r\n", SEP/binary>>, SS + 2},
{<<"\r\n", EOF/binary>>, ES + 2}},
NewSeps = [NewSep | RemSeps],
simple_multipart2(Socket, Rest, NewSeps, Parts);
<<EOF:ES/binary, Rest/binary>> ->
simple_multipart2(Socket, Rest, RemSeps, Parts)
end;
simple_multipart(_, Received, [], Parts) ->
{ok, Parts, Received};
simple_multipart(Socket, Received, Seps, Parts) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
simple_multipart(Socket, <<Received/binary, Bin/binary>>, Seps, Parts);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end.
simple_multipart2(Socket, Received, Seps, Parts) ->
case read_headers(Socket, Received) of
{ok, Headers, Rest} ->
check_multipart_headers(Socket, Rest, Seps, Headers, Parts);
Error ->
Error
end.
check_multipart_headers(Socket, Received, Seps, Headers, Parts) ->
case maps:find(<<"content-disposition">>, Headers) of
{ok, StupidShit} ->
case sort_out(StupidShit) of
[<<"form-data">> | Shit] ->
read_form_data(Socket, Shit, Received, Seps, Headers, Parts);
[<<"file">> | Shit] ->
read_file_data(Socket, Shit, Received, Seps, Parts);
COVID ->
{error, {form_had, COVID}}
end;
error ->
{error, bad_disposition}
end.
read_form_data(Socket, Shit, Received, Seps, Headers, Parts) ->
case proplists:is_defined(<<"filename">>, Shit) of
true -> read_file_data(Socket, Shit, Received, Seps, Parts);
false -> read_form_data2(Socket, Shit, Received, Seps, Headers, Parts)
end.
read_form_data2(Socket, Shit, Received, Seps, Headers, Parts) ->
case maps:find(<<"content-type">>, Headers) of
{ok, StupidShit} ->
case sort_out(StupidShit) of
[<<"multipart/mixed">> | Shit] ->
read_multipart_mixed(Socket, Shit, Received, Seps, Parts);
_ ->
read_form_data3(Socket, Shit, Received, Seps, Parts)
end;
error ->
read_form_data3(Socket, Shit, Received, Seps, Parts)
end.
read_form_data3(Socket, Shit, Received, Seps, Parts) ->
case proplists:get_value(<<"name">>, Shit) of
undefined -> {error, fucked_up_form};
Name -> read_form_data4(Socket, Received, Name, Seps, Parts)
end.
read_form_data4(Socket, Received, Name, Seps = [CurrSep | PrevSeps], Parts) ->
case unfuck_form_data(Socket, Received, <<>>, CurrSep) of
{ok, Data, Rest} ->
NewParts = [{Name, Data} | Parts],
simple_multipart2(Socket, Rest, Seps, NewParts);
{done, Data, Rest} ->
NewParts = [{Name, Data} | Parts],
simple_multipart(Socket, Rest, PrevSeps, NewParts);
Error ->
Error
end.
read_multipart_mixed(Socket, Shit, Received, Seps, Parts) ->
case proplists:get_value(<<"boundary">>, Shit) of
undefined ->
{error, no_boundary};
Boundary ->
SEP = <<"--", Boundary/binary, "\r\n">>,
SS = byte_size(SEP),
EOF = <<"--", Boundary/binary, "--\r\n">>,
ES = byte_size(EOF),
NewSeps = [{{SEP, SS}, {EOF, ES}} | Seps],
simple_multipart(Socket, Received, NewSeps, Parts)
end.
read_file_data(Socket, Shit, Received, Seps = [CurrSep | PrevSeps], Parts) ->
case {proplists:get_value(<<"name">>, Shit),
proplists:get_value(<<"filename">>, Shit)} of
{undefined, undefined} ->
{error, fucked_up_form};
{_, undefined} ->
{error, fucked_up_form};
{undefined, _} ->
{error, fucked_up_form};
{Name, Filename} ->
case unfuck_form_data(Socket, Received, <<>>, CurrSep) of
{ok, Data, Rest} ->
NewParts = [{Name, {Filename, Data}} | Parts],
simple_multipart2(Socket, Rest, Seps, NewParts);
{done, Data, Rest} ->
NewParts = [{Name, {Filename, Data}} | Parts],
simple_multipart(Socket, Rest, PrevSeps, NewParts);
Error ->
Error
end
end.
unfuck_form_data(Socket, Received, Acc, Seps) ->
unfuck_form_data(Socket, Received, Acc, Seps, byte_size(Received)).
unfuck_form_data(Socket, Received, Acc, Seps = {{SEP, SS}, {EOF, ES}}, Size)
when byte_size(Received) >= SS andalso Size < 52428800 ->
case Received of
<<SEP:SS/binary, Rest/binary>> ->
{ok, Acc, Rest};
<<EOF:ES/binary, Rest/binary>> ->
{done, Acc, Rest};
<<Char, Rest/binary>> when byte_size(Received) >= byte_size(SEP) ->
unfuck_form_data(Socket, Rest, <<Acc/binary, Char>>, Seps)
end;
unfuck_form_data(Socket, Received, Acc, Seps, Size) when Size < 52428800 ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
NewBin = <<Received/binary, Bin/binary>>,
unfuck_form_data(Socket, NewBin, Acc, Seps);
{tcp_closed, Socket} ->
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
{error, {tcp_error, Reason}}
after 3000 ->
{error, timeout}
end;
unfuck_form_data(_, _, _, _, Size) ->
{error, {size, Size}}.
sort_out(StupidShit) ->
read_shitkey(StupidShit, <<>>, []).
posted(Body) ->
read_pkey(Body, <<>>, []).
read_pkey(<<$%, A, B, Rest/binary>>, Acc, Data) ->
read_pkey(Rest, <<Acc/binary, (list_to_integer([A, B], 16))>>, Data);
read_pkey(<<"=", Rest/binary>>, Acc, Data) ->
read_pval(Rest, <<>>, Acc, Data);
read_pkey(<<"&", Rest/binary>>, Acc, Data) ->
read_pkey(Rest, <<>>, [{Acc, <<>>} | Data]);
read_pkey(<<Char, Rest/binary>>, Acc, Data) ->
read_pkey(Rest, <<Acc/binary, Char>>, Data);
read_pkey(<<>>, <<>>, Data) ->
Data;
read_pkey(<<>>, Acc, Data) ->
[{Acc, <<>>} | Data].
read_pval(<<$%, $3, $C, Rest/binary>>, Acc, Key, Data) ->
read_pval(Rest, <<Acc/binary, "&lt;">>, Key, Data);
read_pval(<<$%, $3, $c, Rest/binary>>, Acc, Key, Data) ->
read_pval(Rest, <<Acc/binary, "&lt;">>, Key, Data);
read_pval(<<$%, $3, $E, Rest/binary>>, Acc, Key, Data) ->
read_pval(Rest, <<Acc/binary, "&gt;">>, Key, Data);
read_pval(<<$%, $3, $e, Rest/binary>>, Acc, Key, Data) ->
read_pval(Rest, <<Acc/binary, "&gt;">>, Key, Data);
read_pval(<<$%, A, B, Rest/binary>>, Acc, Key, Data) ->
read_pval(Rest, <<Acc/binary, (list_to_integer([A, B], 16))>>, Key, Data);
read_pval(<<"&", Rest/binary>>, Val, Key, Data) ->
read_pkey(Rest, <<>>, [{Key, Val} | Data]);
read_pval(<<"+", Rest/binary>>, Acc, Key, Data) ->
read_pval(Rest, <<Acc/binary, " ">>, Key, Data);
read_pval(<<Char, Rest/binary>>, Acc, Key, Data) ->
read_pval(Rest, <<Acc/binary, Char>>, Key, Data);
read_pval(<<>>, Val, Key, Data) ->
[{Key, Val} | Data].
read_shitkey(<<";", Rest/binary>>, Key, Values) ->
Tail = skip_blanks(Rest),
read_shitkey(Tail, <<>>, [Key | Values]);
read_shitkey(<<"=\"", Rest/binary>>, Key, Values) ->
case read_quote(Rest) of
{ok, Val, <<";", Tail/binary>>} ->
read_shitkey(skip_blanks(Tail), <<>>, [{Key, Val} | Values]);
{ok, Val, Tail} ->
read_shitkey(skip_blanks(Tail), <<>>, [{Key, Val} | Values]);
{ok, <<>>} ->
[Key | Values];
{ok, Val} ->
[{Key, Val} | Values]
end;
read_shitkey(<<"=", Rest/binary>>, Key, Values) ->
read_shitval(Rest, <<>>, Key, Values);
read_shitkey(<<Char, Rest/binary>>, Acc, Values) ->
read_shitkey(Rest, <<Acc/binary, Char>>, Values);
read_shitkey(<<>>, <<>>, Values) ->
lists:reverse(Values);
read_shitkey(<<>>, Key, Values) ->
lists:reverse([Key | Values]).
read_shitval(<<";", Rest/binary>>, <<>>, Key, Values) ->
read_shitkey(skip_blanks(Rest), <<>>, [Key | Values]);
read_shitval(<<";", Rest/binary>>, Val, Key, Values) ->
read_shitkey(skip_blanks(Rest), <<>>, [{Key, Val} | Values]);
read_shitval(<<Char, Rest/binary>>, Acc, Key, Values) ->
read_shitval(Rest, <<Acc/binary, Char>>, Key, Values);
read_shitval(<<>>, <<>>, Key, Values) ->
[{Key, none} | Values];
read_shitval(<<>>, Val, Key, Values) ->
[{Key, Val} | Values].
skip_blanks(<<" ", Rest/binary>>) -> <<(skip_blanks(Rest))/binary>>;
skip_blanks(<<>>) -> <<>>;
skip_blanks(<<Rest/binary>>) -> Rest.
read_quote(Bin) -> read_quote(Bin, <<>>).
read_quote(<<"\"", Rest/binary>>, Acc) ->
{ok, Acc, Rest};
read_quote(<<"\"">>, Acc) ->
{ok, Acc};
read_quote(<<"\\\"", Rest/binary>>, Acc) ->
read_quote(Rest, <<Acc/binary, "\"">>);
read_quote(<<Char, Rest/binary>>, Acc) ->
read_quote(Rest, <<Acc/binary, Char>>).
accumulate(Socket, M = #request{size = Size, body = Body}) ->
Remaining = Size - byte_size(Body),
case accumulate(Socket, Remaining, Body) of
{ok, Received} -> {ok, M#request{body = Received}, none};
{ok, Received, Next} -> {ok, M#request{body = Received}, Next};
Error -> Error
end.
accumulate(Socket, Remaining, Received) when Remaining > 0 ->
io:format("~p accumulate(~p, ~p, ~p)~n", [?LINE, Socket, Remaining, Received]),
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
io:format("~p~n", [?LINE]),
Size = byte_size(Bin),
if
Size == Remaining ->
{ok, <<Received/binary, Bin/binary>>};
Size < Remaining ->
NewRemaining = Remaining - Size,
NewReceived = <<Received/binary, Bin/binary>>,
accumulate(Socket, NewRemaining, NewReceived);
Size > Remaining ->
<<Tail:Remaining/binary, Next/binary>> = Bin,
NewReceived = <<Received/binary, Tail/binary>>,
{ok, NewReceived, Next}
end;
{tcp_closed, Socket} ->
io:format("~p~n", [?LINE]),
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
io:format("~p~n", [?LINE]),
{error, {tcp_error, Reason}};
X ->
io:format("~p raseevd: ~p~n", [?LINE, X])
after 10_000 ->
io:format("~p~n", [?LINE]),
{error, timeout}
end;
accumulate(_, 0, Received) ->
{ok, Received};
accumulate(_, Remaining, Received) when Remaining =< 0 ->
{error, {oversend, Received}}.
%%% Websocket insanity
-spec recv(Socket, Received) -> Result
when Socket :: gen_tcp:socket(),
Received :: binary(),
Result :: {ok, Message} | {error, Reason},
Message :: binary(),
Reason :: term(). % FIXME
recv(_, _) -> {error, nih}.
-spec resp(Socket, Binary) -> Result
when Socket :: gen_tcp:socket(),
Binary :: binary(),
Result :: ok | {error, Reason},
Reason :: term(). % FIXME
resp(_, _) -> {error, nih}.
%%% Utility Functions
-spec percent_encode(RawBinary) -> EncodedBinary
when RawBinary :: binary(),
EncodedBinary :: binary().
%% @doc
%% Percent encodes a binary string as per RFC-3986 Section 2.1
%% https://datatracker.ietf.org/doc/html/rfc3986#section-2.1
percent_encode(<<C, Rest/binary>>)
when $0 =< C, C =< $9;
$A =< C, C =< $Z;
$a =< C, C =< $z;
C == $-; C == $.;
C == $_; C == $~ ->
<<C, (percent_encode(Rest))/binary>>;
percent_encode(<<C, Rest/binary>>) ->
case integer_to_list(C, 16) of
[A, B] -> <<$%, A, B, (percent_encode(Rest))/binary>>;
[B] -> <<$%, $0, B, (percent_encode(Rest))/binary>>
end;
percent_encode(<<>>) ->
<<>>.
-spec percent_decode(EncodedBinary) -> RawBinary
when EncodedBinary :: binary(),
RawBinary :: binary().
%% @doc
%% Decodes a percent encoded binary as per RFC-3986 Section 2.1
%% https://datatracker.ietf.org/doc/html/rfc3986#section-2.1
percent_decode(<<$%, A, B, Rest/binary>>) ->
<<(list_to_integer([A, B], 16)), (percent_decode(Rest))/binary>>;
percent_decode(<<$+, Rest/binary>>) ->
<<" ", (percent_decode(Rest))/binary>>;
percent_decode(<<C, Rest/binary>>) ->
<<C, (percent_decode(Rest))/binary>>;
percent_decode(<<>>) ->
<<>>.
-spec join(Headers) -> IOListHeaders
when Headers :: [{Key, Value}],
IOListHeaders :: iolist(),
Key :: binary(),
Value :: binary().
%% @doc
%% Convert a header K/V tuple list to an `iolist()' of the form
%% `[Key, ": ", Value, "\r\n"]' that can be consumed by iolist
%% operations such as those in the unicode module.
join([{K, V} | Rest]) ->
[K, ": ", V, "\r\n" | join(Rest)];
join([]) ->
[].
-spec ridiculous_web_date() -> iolist().
%% @equiv ridiculous_web_data(Date).
ridiculous_web_date() ->
ridiculous_web_date(calendar:universal_time()).
-spec ridiculous_web_date(Date) -> StupifyinglyRetardedDateFormat
when Date :: calendar:datetime(),
StupifyinglyRetardedDateFormat :: iolist().
%% @doc
%% Perform the humiliation ritual satirically known in the business as
%% web date format generation and return it as an `iolist()'.
ridiculous_web_date({DS = {Y, M, D}, {Hr, Mn, Sc}}) ->
Day =
case calendar:day_of_the_week(DS) of
1 -> "Mon";
2 -> "Tue";
3 -> "Wed";
4 -> "Thu";
5 -> "Fri";
6 -> "Sat";
7 -> "Sun"
end,
Year = integer_to_list(Y),
Month =
case M of
1 -> "Jan";
2 -> "Feb";
3 -> "Mar";
4 -> "Apr";
5 -> "May";
6 -> "Jun";
7 -> "Jul";
8 -> "Aug";
9 -> "Sep";
10 -> "Oct";
11 -> "Nov";
12 -> "Dec"
end,
Date = ridiculous_bullshit_fixed_width_garbage(D),
Hour = ridiculous_bullshit_fixed_width_garbage(Hr),
Minute = ridiculous_bullshit_fixed_width_garbage(Mn),
Second = ridiculous_bullshit_fixed_width_garbage(Sc),
[Day, ", ", Date, " ", Month, " ", Year, " ",
Hour, ":", Minute, ":", Second, " GMT"].
ridiculous_bullshit_fixed_width_garbage(N) when N < 10 -> [$0, integer_to_list(N)];
ridiculous_bullshit_fixed_width_garbage(N) -> integer_to_list(N).
-spec slogan(Code) -> Slogan
when Code :: pos_integer(),
Slogan :: string().
%% @doc
%% A convenience function that returns the standard slogan of a number of well-known
%% HTTP response codes. Take note of which codes are not supported and manually
%% intervene accordingly.
slogan(100) -> "Continue";
slogan(101) -> "Switching Protocols";
slogan(102) -> "Early Hints";
slogan(200) -> "OK";
slogan(201) -> "Created";
slogan(202) -> "Accepted";
slogan(203) -> "Non-Authoritative Information";
slogan(204) -> "No Content";
slogan(301) -> "Moved Permanently";
slogan(302) -> "Found";
slogan(303) -> "See Other";
slogan(304) -> "Not Modified";
slogan(307) -> "Temporary Redirect";
slogan(308) -> "Permanent Redirect";
slogan(400) -> "Bad Request";
slogan(401) -> "Unauthorized";
slogan(402) -> "Payment Required";
slogan(403) -> "Forbidden";
slogan(404) -> "Not Found";
slogan(405) -> "Method Not Allowed";
slogan(409) -> "Conflict";
slogan(410) -> "Gone";
slogan(413) -> "Content Too Large";
slogan(414) -> "URI Too Long";
slogan(426) -> "Upgrade Required";
slogan(429) -> "Too Many Requests";
slogan(451) -> "Unavailable for Legal Reasons";
slogan(500) -> "Internal Server Error";
slogan(501) -> "Not Implemented";
slogan(503) -> "Service Unavailable".
-spec break_nl(WithOrdinaryRN_Newlines) -> WithBR_TaggedHTML
when WithOrdinaryRN_Newlines :: binary(),
WithBR_TaggedHTML :: binary().
%% @doc
%% Substitute `<<"\\r\\n">>' with `<<"<br>">>' in a binary string.
break_nl(<<"\r\n", Rest/binary>>) -> <<"<br>", (break_nl(Rest))/binary>>;
break_nl(<<Char, Rest/binary>>) -> <<Char, (break_nl(Rest))/binary>>;
break_nl(<<>>) -> <<>>.
-spec unbreak_nl(WithBR_TaggedHTML) -> WithOrdinaryRN_Newlines
when WithBR_TaggedHTML :: binary(),
WithOrdinaryRN_Newlines :: binary().
%% @doc
%% Substitute `<<"<br>">>' with `<<"\\r\\n">>' in a binary string.
unbreak_nl(<<"<br>", Rest/binary>>) -> <<"\r\n", (unbreak_nl(Rest))/binary>>;
unbreak_nl(<<Char, Rest/binary>>) -> <<Char, (unbreak_nl(Rest))/binary>>;
unbreak_nl(<<>>) -> <<>>.
-spec clean(MessyAngleBracketyHTML) -> MessierEscapedHTML
when MessyAngleBracketyHTML :: binary(),
MessierEscapedHTML :: binary().
%% @doc
%% Receive HTML input and convert reserved characters to the comedy versions.
clean(<<"<", Rest/binary>>) -> <<"&lt;", (clean(Rest))/binary>>;
clean(<<">", Rest/binary>>) -> <<"&gt;", (clean(Rest))/binary>>;
clean(<<"\"", Rest/binary>>) -> <<"&quot;", (clean(Rest))/binary>>;
clean(<<Char, Rest/binary>>) -> <<Char, (clean(Rest))/binary>>;
clean(<<>>) -> <<>>.
-spec bin_to_int(IntegerBinaryString) -> Result
when IntegerBinaryString :: binary(),
Result :: {ok, integer()} | error.
%% @doc
%% Convert an input to a binary string the old-fashioned non-exploding way.
%% (The caller gets to decide whether this explodes depending on how it is called,
%% without the `try .. catch' mess.
bin_to_int(Bin) ->
case bin_to_int(Bin, <<>>) of
{ok, Clean} -> {ok, binary_to_integer(Clean)};
error -> error
end.
bin_to_int(<<C, Rest/binary>>, Acc) when $0 =< C andalso C =< $9 ->
bin_to_int(Rest, <<Acc/binary, C>>);
bin_to_int(<<"/">>, Acc) ->
{ok, Acc};
bin_to_int(<<>>, Acc) ->
{ok, Acc};
bin_to_int(_, _) ->
error.