aboutsummaryrefslogtreecommitdiff
path: root/server/_build/default/lib/cowboy/src
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
committerCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
commit49fa5aa2a127bdf8924d02bf77e5086b39c7a447 (patch)
tree61d86a7705dacc9fddccc29fa79d075d83ab8059 /server/_build/default/lib/cowboy/src
i vibe coded itHEADmaster
Diffstat (limited to 'server/_build/default/lib/cowboy/src')
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy.erl105
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_app.erl27
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_bstr.erl123
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_children.erl192
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_clear.erl60
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_clock.erl221
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_compress_h.erl249
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_constraints.erl174
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_handler.erl57
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_http.erl1523
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_http2.erl1225
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_loop.erl108
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl331
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_middleware.erl24
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_req.erl1016
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_rest.erl1637
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_router.erl603
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_static.erl418
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_stream.erl193
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_stream_h.erl324
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl24
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_sup.erl30
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_tls.erl56
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl192
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_websocket.erl707
25 files changed, 9619 insertions, 0 deletions
diff --git a/server/_build/default/lib/cowboy/src/cowboy.erl b/server/_build/default/lib/cowboy/src/cowboy.erl
new file mode 100644
index 0000000..c4be25b
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy.erl
@@ -0,0 +1,105 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy).
+
+-export([start_clear/3]).
+-export([start_tls/3]).
+-export([stop_listener/1]).
+-export([set_env/3]).
+
+%% Internal.
+-export([log/2]).
+-export([log/4]).
+
+-type opts() :: cowboy_http:opts() | cowboy_http2:opts().
+-export_type([opts/0]).
+
+-type fields() :: [atom()
+ | {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()]}
+ | {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()], any()}].
+-export_type([fields/0]).
+
+-type http_headers() :: #{binary() => iodata()}.
+-export_type([http_headers/0]).
+
+-type http_status() :: non_neg_integer() | binary().
+-export_type([http_status/0]).
+
+-type http_version() :: 'HTTP/2' | 'HTTP/1.1' | 'HTTP/1.0'.
+-export_type([http_version/0]).
+
+-spec start_clear(ranch:ref(), ranch:opts(), opts())
+ -> {ok, pid()} | {error, any()}.
+start_clear(Ref, TransOpts0, ProtoOpts0) ->
+ TransOpts1 = ranch:normalize_opts(TransOpts0),
+ {TransOpts, ConnectionType} = ensure_connection_type(TransOpts1),
+ ProtoOpts = ProtoOpts0#{connection_type => ConnectionType},
+ ranch:start_listener(Ref, ranch_tcp, TransOpts, cowboy_clear, ProtoOpts).
+
+-spec start_tls(ranch:ref(), ranch:opts(), opts())
+ -> {ok, pid()} | {error, any()}.
+start_tls(Ref, TransOpts0, ProtoOpts0) ->
+ TransOpts1 = ranch:normalize_opts(TransOpts0),
+ SocketOpts = maps:get(socket_opts, TransOpts1, []),
+ TransOpts2 = TransOpts1#{socket_opts => [
+ {next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
+ {alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
+ |SocketOpts]},
+ {TransOpts, ConnectionType} = ensure_connection_type(TransOpts2),
+ ProtoOpts = ProtoOpts0#{connection_type => ConnectionType},
+ ranch:start_listener(Ref, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts).
+
+ensure_connection_type(TransOpts=#{connection_type := ConnectionType}) ->
+ {TransOpts, ConnectionType};
+ensure_connection_type(TransOpts) ->
+ {TransOpts#{connection_type => supervisor}, supervisor}.
+
+-spec stop_listener(ranch:ref()) -> ok | {error, not_found}.
+stop_listener(Ref) ->
+ ranch:stop_listener(Ref).
+
+-spec set_env(ranch:ref(), atom(), any()) -> ok.
+set_env(Ref, Name, Value) ->
+ Opts = ranch:get_protocol_options(Ref),
+ Env = maps:get(env, Opts, #{}),
+ Opts2 = maps:put(env, maps:put(Name, Value, Env), Opts),
+ ok = ranch:set_protocol_options(Ref, Opts2).
+
+%% Internal.
+
+-spec log({log, logger:level(), io:format(), list()}, opts()) -> ok.
+log({log, Level, Format, Args}, Opts) ->
+ log(Level, Format, Args, Opts).
+
+-spec log(logger:level(), io:format(), list(), opts()) -> ok.
+log(Level, Format, Args, #{logger := Logger})
+ when Logger =/= error_logger ->
+ _ = Logger:Level(Format, Args),
+ ok;
+%% We use error_logger by default. Because error_logger does
+%% not have all the levels we accept we have to do some
+%% mapping to error_logger functions.
+log(Level, Format, Args, _) ->
+ Function = case Level of
+ emergency -> error_msg;
+ alert -> error_msg;
+ critical -> error_msg;
+ error -> error_msg;
+ warning -> warning_msg;
+ notice -> warning_msg;
+ info -> info_msg;
+ debug -> info_msg
+ end,
+ error_logger:Function(Format, Args).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_app.erl b/server/_build/default/lib/cowboy/src/cowboy_app.erl
new file mode 100644
index 0000000..74cba41
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_app.erl
@@ -0,0 +1,27 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_app).
+-behaviour(application).
+
+-export([start/2]).
+-export([stop/1]).
+
+-spec start(_, _) -> {ok, pid()}.
+start(_, _) ->
+ cowboy_sup:start_link().
+
+-spec stop(_) -> ok.
+stop(_) ->
+ ok.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_bstr.erl b/server/_build/default/lib/cowboy/src/cowboy_bstr.erl
new file mode 100644
index 0000000..d8041e4
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_bstr.erl
@@ -0,0 +1,123 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_bstr).
+
+%% Binary strings.
+-export([capitalize_token/1]).
+-export([to_lower/1]).
+-export([to_upper/1]).
+
+%% Characters.
+-export([char_to_lower/1]).
+-export([char_to_upper/1]).
+
+%% The first letter and all letters after a dash are capitalized.
+%% This is the form seen for header names in the HTTP/1.1 RFC and
+%% others. Note that using this form isn't required, as header names
+%% are case insensitive, and it is only provided for use with eventual
+%% badly implemented clients.
+-spec capitalize_token(B) -> B when B::binary().
+capitalize_token(B) ->
+ capitalize_token(B, true, <<>>).
+capitalize_token(<<>>, _, Acc) ->
+ Acc;
+capitalize_token(<< $-, Rest/bits >>, _, Acc) ->
+ capitalize_token(Rest, true, << Acc/binary, $- >>);
+capitalize_token(<< C, Rest/bits >>, true, Acc) ->
+ capitalize_token(Rest, false, << Acc/binary, (char_to_upper(C)) >>);
+capitalize_token(<< C, Rest/bits >>, false, Acc) ->
+ capitalize_token(Rest, false, << Acc/binary, (char_to_lower(C)) >>).
+
+-spec to_lower(B) -> B when B::binary().
+to_lower(B) ->
+ << << (char_to_lower(C)) >> || << C >> <= B >>.
+
+-spec to_upper(B) -> B when B::binary().
+to_upper(B) ->
+ << << (char_to_upper(C)) >> || << C >> <= B >>.
+
+-spec char_to_lower(char()) -> char().
+char_to_lower($A) -> $a;
+char_to_lower($B) -> $b;
+char_to_lower($C) -> $c;
+char_to_lower($D) -> $d;
+char_to_lower($E) -> $e;
+char_to_lower($F) -> $f;
+char_to_lower($G) -> $g;
+char_to_lower($H) -> $h;
+char_to_lower($I) -> $i;
+char_to_lower($J) -> $j;
+char_to_lower($K) -> $k;
+char_to_lower($L) -> $l;
+char_to_lower($M) -> $m;
+char_to_lower($N) -> $n;
+char_to_lower($O) -> $o;
+char_to_lower($P) -> $p;
+char_to_lower($Q) -> $q;
+char_to_lower($R) -> $r;
+char_to_lower($S) -> $s;
+char_to_lower($T) -> $t;
+char_to_lower($U) -> $u;
+char_to_lower($V) -> $v;
+char_to_lower($W) -> $w;
+char_to_lower($X) -> $x;
+char_to_lower($Y) -> $y;
+char_to_lower($Z) -> $z;
+char_to_lower(Ch) -> Ch.
+
+-spec char_to_upper(char()) -> char().
+char_to_upper($a) -> $A;
+char_to_upper($b) -> $B;
+char_to_upper($c) -> $C;
+char_to_upper($d) -> $D;
+char_to_upper($e) -> $E;
+char_to_upper($f) -> $F;
+char_to_upper($g) -> $G;
+char_to_upper($h) -> $H;
+char_to_upper($i) -> $I;
+char_to_upper($j) -> $J;
+char_to_upper($k) -> $K;
+char_to_upper($l) -> $L;
+char_to_upper($m) -> $M;
+char_to_upper($n) -> $N;
+char_to_upper($o) -> $O;
+char_to_upper($p) -> $P;
+char_to_upper($q) -> $Q;
+char_to_upper($r) -> $R;
+char_to_upper($s) -> $S;
+char_to_upper($t) -> $T;
+char_to_upper($u) -> $U;
+char_to_upper($v) -> $V;
+char_to_upper($w) -> $W;
+char_to_upper($x) -> $X;
+char_to_upper($y) -> $Y;
+char_to_upper($z) -> $Z;
+char_to_upper(Ch) -> Ch.
+
+%% Tests.
+
+-ifdef(TEST).
+capitalize_token_test_() ->
+ Tests = [
+ {<<"heLLo-woRld">>, <<"Hello-World">>},
+ {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
+ {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
+ {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
+ {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
+ {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--Version">>},
+ {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
+ ],
+ [{H, fun() -> R = capitalize_token(H) end} || {H, R} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_children.erl b/server/_build/default/lib/cowboy/src/cowboy_children.erl
new file mode 100644
index 0000000..05d39fb
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_children.erl
@@ -0,0 +1,192 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_children).
+
+-export([init/0]).
+-export([up/4]).
+-export([down/2]).
+-export([shutdown/2]).
+-export([shutdown_timeout/3]).
+-export([terminate/1]).
+-export([handle_supervisor_call/4]).
+
+-record(child, {
+ pid :: pid(),
+ streamid :: cowboy_stream:streamid() | undefined,
+ shutdown :: timeout(),
+ timer = undefined :: undefined | reference()
+}).
+
+-type children() :: [#child{}].
+-export_type([children/0]).
+
+-spec init() -> [].
+init() ->
+ [].
+
+-spec up(Children, pid(), cowboy_stream:streamid(), timeout())
+ -> Children when Children::children().
+up(Children, Pid, StreamID, Shutdown) ->
+ [#child{
+ pid=Pid,
+ streamid=StreamID,
+ shutdown=Shutdown
+ }|Children].
+
+-spec down(Children, pid())
+ -> {ok, cowboy_stream:streamid() | undefined, Children} | error
+ when Children::children().
+down(Children0, Pid) ->
+ case lists:keytake(Pid, #child.pid, Children0) of
+ {value, #child{streamid=StreamID, timer=Ref}, Children} ->
+ _ = case Ref of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(Ref, [{async, true}, {info, false}])
+ end,
+ {ok, StreamID, Children};
+ false ->
+ error
+ end.
+
+%% We ask the processes to shutdown first. This gives
+%% a chance to processes that are trapping exits to
+%% shut down gracefully. Others will exit immediately.
+%%
+%% @todo We currently fire one timer per process being
+%% shut down. This is probably not the most efficient.
+%% A more efficient solution could be to maintain a
+%% single timer and decrease the shutdown time of all
+%% processes when it fires. This is however much more
+%% complex, and there aren't that many processes that
+%% will need to be shutdown through this function, so
+%% this is left for later.
+-spec shutdown(Children, cowboy_stream:streamid())
+ -> Children when Children::children().
+shutdown(Children0, StreamID) ->
+ [
+ case Child of
+ #child{pid=Pid, streamid=StreamID, shutdown=Shutdown} ->
+ exit(Pid, shutdown),
+ Ref = erlang:start_timer(Shutdown, self(), {shutdown, Pid}),
+ Child#child{streamid=undefined, timer=Ref};
+ _ ->
+ Child
+ end
+ || Child <- Children0].
+
+-spec shutdown_timeout(children(), reference(), pid()) -> ok.
+shutdown_timeout(Children, Ref, Pid) ->
+ case lists:keyfind(Pid, #child.pid, Children) of
+ #child{timer=Ref} ->
+ exit(Pid, kill),
+ ok;
+ _ ->
+ ok
+ end.
+
+-spec terminate(children()) -> ok.
+terminate(Children) ->
+ %% For each child, either ask for it to shut down,
+ %% or cancel its shutdown timer if it already is.
+ %%
+ %% We do not need to flush stray timeout messages out because
+ %% we are either terminating or switching protocols,
+ %% and in the latter case we flush all messages.
+ _ = [case TRef of
+ undefined -> exit(Pid, shutdown);
+ _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
+ end || #child{pid=Pid, timer=TRef} <- Children],
+ before_terminate_loop(Children).
+
+before_terminate_loop([]) ->
+ ok;
+before_terminate_loop(Children) ->
+ %% Find the longest shutdown time.
+ Time = longest_shutdown_time(Children, 0),
+ %% We delay the creation of the timer if one of the
+ %% processes has an infinity shutdown value.
+ TRef = case Time of
+ infinity -> undefined;
+ _ -> erlang:start_timer(Time, self(), terminate)
+ end,
+ %% Loop until that time or until all children are dead.
+ terminate_loop(Children, TRef).
+
+terminate_loop([], TRef) ->
+ %% Don't forget to cancel the timer, if any!
+ case TRef of
+ undefined ->
+ ok;
+ _ ->
+ _ = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
+ ok
+ end;
+terminate_loop(Children, TRef) ->
+ receive
+ {'EXIT', Pid, _} when TRef =:= undefined ->
+ {value, #child{shutdown=Shutdown}, Children1}
+ = lists:keytake(Pid, #child.pid, Children),
+ %% We delayed the creation of the timer. If a process with
+ %% infinity shutdown just ended, we might have to start that timer.
+ case Shutdown of
+ infinity -> before_terminate_loop(Children1);
+ _ -> terminate_loop(Children1, TRef)
+ end;
+ {'EXIT', Pid, _} ->
+ terminate_loop(lists:keydelete(Pid, #child.pid, Children), TRef);
+ {timeout, TRef, terminate} ->
+ %% Brutally kill any remaining children.
+ _ = [exit(Pid, kill) || #child{pid=Pid} <- Children],
+ ok
+ end.
+
+longest_shutdown_time([], Time) ->
+ Time;
+longest_shutdown_time([#child{shutdown=ChildTime}|Tail], Time) when ChildTime > Time ->
+ longest_shutdown_time(Tail, ChildTime);
+longest_shutdown_time([_|Tail], Time) ->
+ longest_shutdown_time(Tail, Time).
+
+-spec handle_supervisor_call(any(), {pid(), any()}, children(), module()) -> ok.
+handle_supervisor_call(which_children, {From, Tag}, Children, Module) ->
+ From ! {Tag, which_children(Children, Module)},
+ ok;
+handle_supervisor_call(count_children, {From, Tag}, Children, _) ->
+ From ! {Tag, count_children(Children)},
+ ok;
+%% We disable start_child since only incoming requests
+%% end up creating a new process.
+handle_supervisor_call({start_child, _}, {From, Tag}, _, _) ->
+ From ! {Tag, {error, start_child_disabled}},
+ ok;
+%% All other calls refer to children. We act in a similar way
+%% to a simple_one_for_one so we never find those.
+handle_supervisor_call(_, {From, Tag}, _, _) ->
+ From ! {Tag, {error, not_found}},
+ ok.
+
+-spec which_children(children(), module()) -> [{module(), pid(), worker, [module()]}].
+which_children(Children, Module) ->
+ [{Module, Pid, worker, [Module]} || #child{pid=Pid} <- Children].
+
+-spec count_children(children()) -> [{atom(), non_neg_integer()}].
+count_children(Children) ->
+ Count = length(Children),
+ [
+ {specs, 1},
+ {active, Count},
+ {supervisors, 0},
+ {workers, Count}
+ ].
diff --git a/server/_build/default/lib/cowboy/src/cowboy_clear.erl b/server/_build/default/lib/cowboy/src/cowboy_clear.erl
new file mode 100644
index 0000000..4f3a234
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_clear.erl
@@ -0,0 +1,60 @@
+%% Copyright (c) 2016-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_clear).
+-behavior(ranch_protocol).
+
+-export([start_link/3]).
+-export([start_link/4]).
+-export([connection_process/4]).
+
+%% Ranch 1.
+-spec start_link(ranch:ref(), inet:socket(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, _Socket, Transport, Opts) ->
+ start_link(Ref, Transport, Opts).
+
+%% Ranch 2.
+-spec start_link(ranch:ref(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, Transport, Opts) ->
+ Pid = proc_lib:spawn_link(?MODULE, connection_process,
+ [self(), Ref, Transport, Opts]),
+ {ok, Pid}.
+
+-spec connection_process(pid(), ranch:ref(), module(), cowboy:opts()) -> ok.
+connection_process(Parent, Ref, Transport, Opts) ->
+ ProxyInfo = case maps:get(proxy_header, Opts, false) of
+ true ->
+ {ok, ProxyInfo0} = ranch:recv_proxy_header(Ref, 1000),
+ ProxyInfo0;
+ false ->
+ undefined
+ end,
+ {ok, Socket} = ranch:handshake(Ref),
+ %% Use cowboy_http2 directly only when 'http' is missing.
+ %% Otherwise switch to cowboy_http2 from cowboy_http.
+ %%
+ %% @todo Extend this option to cowboy_tls and allow disabling
+ %% the switch to cowboy_http2 in cowboy_http. Also document it.
+ Protocol = case maps:get(protocols, Opts, [http2, http]) of
+ [http2] -> cowboy_http2;
+ [_|_] -> cowboy_http
+ end,
+ init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol).
+
+init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->
+ _ = case maps:get(connection_type, Opts, supervisor) of
+ worker -> ok;
+ supervisor -> process_flag(trap_exit, true)
+ end,
+ Protocol:init(Parent, Ref, Socket, Transport, ProxyInfo, Opts).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_clock.erl b/server/_build/default/lib/cowboy/src/cowboy_clock.erl
new file mode 100644
index 0000000..28f8a1b
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_clock.erl
@@ -0,0 +1,221 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% While a gen_server process runs in the background to update
+%% the cache of formatted dates every second, all API calls are
+%% local and directly read from the ETS cache table, providing
+%% fast time and date computations.
+-module(cowboy_clock).
+-behaviour(gen_server).
+
+%% API.
+-export([start_link/0]).
+-export([stop/0]).
+-export([rfc1123/0]).
+-export([rfc1123/1]).
+
+%% gen_server.
+-export([init/1]).
+-export([handle_call/3]).
+-export([handle_cast/2]).
+-export([handle_info/2]).
+-export([terminate/2]).
+-export([code_change/3]).
+
+-record(state, {
+ universaltime = undefined :: undefined | calendar:datetime(),
+ rfc1123 = <<>> :: binary(),
+ tref = undefined :: undefined | reference()
+}).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+-spec stop() -> stopped.
+stop() ->
+ gen_server:call(?MODULE, stop).
+
+%% When the ets table doesn't exist, either because of a bug
+%% or because Cowboy is being restarted, we perform in a
+%% slightly degraded state and build a new timestamp for
+%% every request.
+-spec rfc1123() -> binary().
+rfc1123() ->
+ try
+ ets:lookup_element(?MODULE, rfc1123, 2)
+ catch error:badarg ->
+ rfc1123(erlang:universaltime())
+ end.
+
+-spec rfc1123(calendar:datetime()) -> binary().
+rfc1123(DateTime) ->
+ update_rfc1123(<<>>, undefined, DateTime).
+
+%% gen_server.
+
+-spec init([]) -> {ok, #state{}}.
+init([]) ->
+ ?MODULE = ets:new(?MODULE, [set, protected,
+ named_table, {read_concurrency, true}]),
+ T = erlang:universaltime(),
+ B = update_rfc1123(<<>>, undefined, T),
+ TRef = erlang:send_after(1000, self(), update),
+ ets:insert(?MODULE, {rfc1123, B}),
+ {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
+
+-type from() :: {pid(), term()}.
+-spec handle_call
+ (stop, from(), State) -> {stop, normal, stopped, State}
+ when State::#state{}.
+handle_call(stop, _From, State) ->
+ {stop, normal, stopped, State};
+handle_call(_Request, _From, State) ->
+ {reply, ignored, State}.
+
+-spec handle_cast(_, State) -> {noreply, State} when State::#state{}.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+-spec handle_info(any(), State) -> {noreply, State} when State::#state{}.
+handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef0}) ->
+ %% Cancel the timer in case an external process sent an update message.
+ _ = erlang:cancel_timer(TRef0),
+ T = erlang:universaltime(),
+ B2 = update_rfc1123(B1, Prev, T),
+ ets:insert(?MODULE, {rfc1123, B2}),
+ TRef = erlang:send_after(1000, self(), update),
+ {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+-spec terminate(_, _) -> ok.
+terminate(_Reason, _State) ->
+ ok.
+
+-spec code_change(_, State, _) -> {ok, State} when State::#state{}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% Internal.
+
+-spec update_rfc1123(binary(), undefined | calendar:datetime(),
+ calendar:datetime()) -> binary().
+update_rfc1123(Bin, Now, Now) ->
+ Bin;
+update_rfc1123(<< Keep:23/binary, _/bits >>,
+ {Date, {H, M, _}}, {Date, {H, M, S}}) ->
+ << Keep/binary, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< Keep:20/binary, _/bits >>,
+ {Date, {H, _, _}}, {Date, {H, M, S}}) ->
+ << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) ->
+ << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>,
+ {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary,
+ (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>,
+ {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
+ (month(Mo))/binary, Keep/binary,
+ (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
+ (month(Mo))/binary, " ", (integer_to_binary(Y))/binary,
+ " ", (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>.
+
+%% Following suggestion by MononcQc on #erlounge.
+-spec pad_int(0..59) -> binary().
+pad_int(X) when X < 10 ->
+ << $0, ($0 + X) >>;
+pad_int(X) ->
+ integer_to_binary(X).
+
+-spec weekday(1..7) -> <<_:24>>.
+weekday(1) -> <<"Mon">>;
+weekday(2) -> <<"Tue">>;
+weekday(3) -> <<"Wed">>;
+weekday(4) -> <<"Thu">>;
+weekday(5) -> <<"Fri">>;
+weekday(6) -> <<"Sat">>;
+weekday(7) -> <<"Sun">>.
+
+-spec month(1..12) -> <<_:24>>.
+month( 1) -> <<"Jan">>;
+month( 2) -> <<"Feb">>;
+month( 3) -> <<"Mar">>;
+month( 4) -> <<"Apr">>;
+month( 5) -> <<"May">>;
+month( 6) -> <<"Jun">>;
+month( 7) -> <<"Jul">>;
+month( 8) -> <<"Aug">>;
+month( 9) -> <<"Sep">>;
+month(10) -> <<"Oct">>;
+month(11) -> <<"Nov">>;
+month(12) -> <<"Dec">>.
+
+%% Tests.
+
+-ifdef(TEST).
+update_rfc1123_test_() ->
+ Tests = [
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined,
+ {{2011, 5, 14}, {14, 25, 33}}, <<>>},
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
+ {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
+ {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
+ {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
+ {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}},
+ {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>},
+ {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}},
+ {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>},
+ {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}},
+ {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>},
+ {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
+ {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>},
+ {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
+ {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>}
+ ],
+ [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests].
+
+pad_int_test_() ->
+ Tests = [
+ { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>},
+ { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>},
+ { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>},
+ {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>},
+ {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>},
+ {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>},
+ {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>},
+ {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>},
+ {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>},
+ {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>},
+ {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>},
+ {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>},
+ {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>},
+ {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>},
+ {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>}
+ ],
+ [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_compress_h.erl b/server/_build/default/lib/cowboy/src/cowboy_compress_h.erl
new file mode 100644
index 0000000..374cb6a
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_compress_h.erl
@@ -0,0 +1,249 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_compress_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-record(state, {
+ next :: any(),
+ threshold :: non_neg_integer() | undefined,
+ compress = undefined :: undefined | gzip,
+ deflate = undefined :: undefined | zlib:zstream(),
+ deflate_flush = sync :: none | sync
+}).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {cowboy_stream:commands(), #state{}}.
+init(StreamID, Req, Opts) ->
+ State0 = check_req(Req),
+ CompressThreshold = maps:get(compress_threshold, Opts, 300),
+ DeflateFlush = buffering_to_zflush(maps:get(compress_buffering, Opts, false)),
+ {Commands0, Next} = cowboy_stream:init(StreamID, Req, Opts),
+ fold(Commands0, State0#state{next=Next,
+ threshold=CompressThreshold,
+ deflate_flush=DeflateFlush}).
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+data(StreamID, IsFin, Data, State0=#state{next=Next0}) ->
+ {Commands0, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
+ fold(Commands0, State0#state{next=Next}).
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+info(StreamID, Info, State0=#state{next=Next0}) ->
+ {Commands0, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ fold(Commands0, State0#state{next=Next}).
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().
+terminate(StreamID, Reason, #state{next=Next, deflate=Z}) ->
+ %% Clean the zlib:stream() in case something went wrong.
+ %% In the normal scenario the stream is already closed.
+ case Z of
+ undefined -> ok;
+ _ -> zlib:close(Z)
+ end,
+ cowboy_stream:terminate(StreamID, Reason, Next).
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
+
+%% Internal.
+
+%% Check if the client supports decoding of gzip responses.
+%%
+%% A malformed accept-encoding header is ignored (no compression).
+check_req(Req) ->
+ try cowboy_req:parse_header(<<"accept-encoding">>, Req) of
+ %% Client doesn't support any compression algorithm.
+ undefined ->
+ #state{compress=undefined};
+ Encodings ->
+ %% We only support gzip so look for it specifically.
+ %% @todo A recipient SHOULD consider "x-gzip" to be
+ %% equivalent to "gzip". (RFC7230 4.2.3)
+ case [E || E={<<"gzip">>, Q} <- Encodings, Q =/= 0] of
+ [] ->
+ #state{compress=undefined};
+ _ ->
+ #state{compress=gzip}
+ end
+ catch
+ _:_ ->
+ #state{compress=undefined}
+ end.
+
+%% Do not compress responses that contain the content-encoding header.
+check_resp_headers(#{<<"content-encoding">> := _}, State) ->
+ State#state{compress=undefined};
+check_resp_headers(_, State) ->
+ State.
+
+fold(Commands, State=#state{compress=undefined}) ->
+ {Commands, State};
+fold(Commands, State) ->
+ fold(Commands, State, []).
+
+fold([], State, Acc) ->
+ {lists:reverse(Acc), State};
+%% We do not compress full sendfile bodies.
+fold([Response={response, _, _, {sendfile, _, _, _}}|Tail], State, Acc) ->
+ fold(Tail, State, [Response|Acc]);
+%% We compress full responses directly, unless they are lower than
+%% the configured threshold or we find we are not able to by looking at the headers.
+fold([Response0={response, _, Headers, Body}|Tail],
+ State0=#state{threshold=CompressThreshold}, Acc) ->
+ case check_resp_headers(Headers, State0) of
+ State=#state{compress=undefined} ->
+ fold(Tail, State, [Response0|Acc]);
+ State1 ->
+ BodyLength = iolist_size(Body),
+ if
+ BodyLength =< CompressThreshold ->
+ fold(Tail, State1, [Response0|Acc]);
+ true ->
+ {Response, State} = gzip_response(Response0, State1),
+ fold(Tail, State, [Response|Acc])
+ end
+ end;
+%% Check headers and initiate compression...
+fold([Response0={headers, _, Headers}|Tail], State0, Acc) ->
+ case check_resp_headers(Headers, State0) of
+ State=#state{compress=undefined} ->
+ fold(Tail, State, [Response0|Acc]);
+ State1 ->
+ {Response, State} = gzip_headers(Response0, State1),
+ fold(Tail, State, [Response|Acc])
+ end;
+%% then compress each data commands individually.
+fold([Data0={data, _, _}|Tail], State0=#state{compress=gzip}, Acc) ->
+ {Data, State} = gzip_data(Data0, State0),
+ fold(Tail, State, [Data|Acc]);
+%% When trailers are sent we need to end the compression.
+%% This results in an extra data command being sent.
+fold([Trailers={trailers, _}|Tail], State0=#state{compress=gzip}, Acc) ->
+ {{data, fin, Data}, State} = gzip_data({data, fin, <<>>}, State0),
+ fold(Tail, State, [Trailers, {data, nofin, Data}|Acc]);
+%% All the options from this handler can be updated for the current stream.
+%% The set_options command must be propagated as-is regardless.
+fold([SetOptions={set_options, Opts}|Tail], State=#state{
+ threshold=CompressThreshold0, deflate_flush=DeflateFlush0}, Acc) ->
+ CompressThreshold = maps:get(compress_threshold, Opts, CompressThreshold0),
+ DeflateFlush = case Opts of
+ #{compress_buffering := CompressBuffering} ->
+ buffering_to_zflush(CompressBuffering);
+ _ ->
+ DeflateFlush0
+ end,
+ fold(Tail, State#state{threshold=CompressThreshold, deflate_flush=DeflateFlush},
+ [SetOptions|Acc]);
+%% Otherwise, we have an unrelated command or compression is disabled.
+fold([Command|Tail], State, Acc) ->
+ fold(Tail, State, [Command|Acc]).
+
+buffering_to_zflush(true) -> none;
+buffering_to_zflush(false) -> sync.
+
+gzip_response({response, Status, Headers, Body}, State) ->
+ %% We can't call zlib:gzip/1 because it does an
+ %% iolist_to_binary(GzBody) at the end to return
+ %% a binary(). Therefore the code here is largely
+ %% a duplicate of the code of that function.
+ Z = zlib:open(),
+ GzBody = try
+ %% 31 = 16+?MAX_WBITS from zlib.erl
+ %% @todo It might be good to allow them to be configured?
+ zlib:deflateInit(Z, default, deflated, 31, 8, default),
+ Gz = zlib:deflate(Z, Body, finish),
+ zlib:deflateEnd(Z),
+ Gz
+ after
+ zlib:close(Z)
+ end,
+ {{response, Status, vary(Headers#{
+ <<"content-length">> => integer_to_binary(iolist_size(GzBody)),
+ <<"content-encoding">> => <<"gzip">>
+ }), GzBody}, State}.
+
+gzip_headers({headers, Status, Headers0}, State) ->
+ Z = zlib:open(),
+ %% We use the same arguments as when compressing the body fully.
+ %% @todo It might be good to allow them to be configured?
+ zlib:deflateInit(Z, default, deflated, 31, 8, default),
+ Headers = maps:remove(<<"content-length">>, Headers0),
+ {{headers, Status, vary(Headers#{
+ <<"content-encoding">> => <<"gzip">>
+ })}, State#state{deflate=Z}}.
+
+%% We must add content-encoding to vary if it's not already there.
+vary(Headers=#{<<"vary">> := Vary}) ->
+ try cow_http_hd:parse_vary(iolist_to_binary(Vary)) of
+ '*' -> Headers;
+ List ->
+ case lists:member(<<"accept-encoding">>, List) of
+ true -> Headers;
+ false -> Headers#{<<"vary">> => [Vary, <<", accept-encoding">>]}
+ end
+ catch _:_ ->
+ %% The vary header is invalid. Probably empty. We replace it with ours.
+ Headers#{<<"vary">> => <<"accept-encoding">>}
+ end;
+vary(Headers) ->
+ Headers#{<<"vary">> => <<"accept-encoding">>}.
+
+%% It is not possible to combine zlib and the sendfile
+%% syscall as far as I can tell, because the zlib format
+%% includes a checksum at the end of the stream. We have
+%% to read the file in memory, making this not suitable for
+%% large files.
+gzip_data({data, nofin, Sendfile={sendfile, _, _, _}},
+ State=#state{deflate=Z, deflate_flush=Flush}) ->
+ {ok, Data0} = read_file(Sendfile),
+ Data = zlib:deflate(Z, Data0, Flush),
+ {{data, nofin, Data}, State};
+gzip_data({data, fin, Sendfile={sendfile, _, _, _}}, State=#state{deflate=Z}) ->
+ {ok, Data0} = read_file(Sendfile),
+ Data = zlib:deflate(Z, Data0, finish),
+ zlib:deflateEnd(Z),
+ zlib:close(Z),
+ {{data, fin, Data}, State#state{deflate=undefined}};
+gzip_data({data, nofin, Data0}, State=#state{deflate=Z, deflate_flush=Flush}) ->
+ Data = zlib:deflate(Z, Data0, Flush),
+ {{data, nofin, Data}, State};
+gzip_data({data, fin, Data0}, State=#state{deflate=Z}) ->
+ Data = zlib:deflate(Z, Data0, finish),
+ zlib:deflateEnd(Z),
+ zlib:close(Z),
+ {{data, fin, Data}, State#state{deflate=undefined}}.
+
+read_file({sendfile, Offset, Bytes, Path}) ->
+ {ok, IoDevice} = file:open(Path, [read, raw, binary]),
+ try
+ _ = case Offset of
+ 0 -> ok;
+ _ -> file:position(IoDevice, {bof, Offset})
+ end,
+ file:read(IoDevice, Bytes)
+ after
+ file:close(IoDevice)
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_constraints.erl b/server/_build/default/lib/cowboy/src/cowboy_constraints.erl
new file mode 100644
index 0000000..6509c4b
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_constraints.erl
@@ -0,0 +1,174 @@
+%% Copyright (c) 2014-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_constraints).
+
+-export([validate/2]).
+-export([reverse/2]).
+-export([format_error/1]).
+
+-type constraint() :: int | nonempty | fun().
+-export_type([constraint/0]).
+
+-type reason() :: {constraint(), any(), any()}.
+-export_type([reason/0]).
+
+-spec validate(binary(), constraint() | [constraint()])
+ -> {ok, any()} | {error, reason()}.
+validate(Value, Constraints) when is_list(Constraints) ->
+ apply_list(forward, Value, Constraints);
+validate(Value, Constraint) ->
+ apply_list(forward, Value, [Constraint]).
+
+-spec reverse(any(), constraint() | [constraint()])
+ -> {ok, binary()} | {error, reason()}.
+reverse(Value, Constraints) when is_list(Constraints) ->
+ apply_list(reverse, Value, Constraints);
+reverse(Value, Constraint) ->
+ apply_list(reverse, Value, [Constraint]).
+
+-spec format_error(reason()) -> iodata().
+format_error({Constraint, Reason, Value}) ->
+ apply_constraint(format_error, {Reason, Value}, Constraint).
+
+apply_list(_, Value, []) ->
+ {ok, Value};
+apply_list(Type, Value0, [Constraint|Tail]) ->
+ case apply_constraint(Type, Value0, Constraint) of
+ {ok, Value} ->
+ apply_list(Type, Value, Tail);
+ {error, Reason} ->
+ {error, {Constraint, Reason, Value0}}
+ end.
+
+%% @todo {int, From, To}, etc.
+apply_constraint(Type, Value, int) ->
+ int(Type, Value);
+apply_constraint(Type, Value, nonempty) ->
+ nonempty(Type, Value);
+apply_constraint(Type, Value, F) when is_function(F) ->
+ F(Type, Value).
+
+%% Constraint functions.
+
+int(forward, Value) ->
+ try
+ {ok, binary_to_integer(Value)}
+ catch _:_ ->
+ {error, not_an_integer}
+ end;
+int(reverse, Value) ->
+ try
+ {ok, integer_to_binary(Value)}
+ catch _:_ ->
+ {error, not_an_integer}
+ end;
+int(format_error, {not_an_integer, Value}) ->
+ io_lib:format("The value ~p is not an integer.", [Value]).
+
+nonempty(Type, <<>>) when Type =/= format_error ->
+ {error, empty};
+nonempty(Type, Value) when Type =/= format_error, is_binary(Value) ->
+ {ok, Value};
+nonempty(format_error, {empty, Value}) ->
+ io_lib:format("The value ~p is empty.", [Value]).
+
+-ifdef(TEST).
+
+validate_test() ->
+ F = fun(_, Value) ->
+ try
+ {ok, binary_to_atom(Value, latin1)}
+ catch _:_ ->
+ {error, not_a_binary}
+ end
+ end,
+ %% Value, Constraints, Result.
+ Tests = [
+ {<<>>, [], <<>>},
+ {<<"123">>, int, 123},
+ {<<"123">>, [int], 123},
+ {<<"123">>, [nonempty, int], 123},
+ {<<"123">>, [int, nonempty], 123},
+ {<<>>, nonempty, error},
+ {<<>>, [nonempty], error},
+ {<<"hello">>, F, hello},
+ {<<"hello">>, [F], hello},
+ {<<"123">>, [F, int], error},
+ {<<"123">>, [int, F], error},
+ {<<"hello">>, [nonempty, F], hello},
+ {<<"hello">>, [F, nonempty], hello}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() ->
+ case R of
+ error -> {error, _} = validate(V, C);
+ _ -> {ok, R} = validate(V, C)
+ end
+ end} || {V, C, R} <- Tests].
+
+reverse_test() ->
+ F = fun(_, Value) ->
+ try
+ {ok, atom_to_binary(Value, latin1)}
+ catch _:_ ->
+ {error, not_an_atom}
+ end
+ end,
+ %% Value, Constraints, Result.
+ Tests = [
+ {<<>>, [], <<>>},
+ {123, int, <<"123">>},
+ {123, [int], <<"123">>},
+ {123, [nonempty, int], <<"123">>},
+ {123, [int, nonempty], <<"123">>},
+ {<<>>, nonempty, error},
+ {<<>>, [nonempty], error},
+ {hello, F, <<"hello">>},
+ {hello, [F], <<"hello">>},
+ {123, [F, int], error},
+ {123, [int, F], error},
+ {hello, [nonempty, F], <<"hello">>},
+ {hello, [F, nonempty], <<"hello">>}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() ->
+ case R of
+ error -> {error, _} = reverse(V, C);
+ _ -> {ok, R} = reverse(V, C)
+ end
+ end} || {V, C, R} <- Tests].
+
+int_format_error_test() ->
+ {error, Reason} = validate(<<"string">>, int),
+ Bin = iolist_to_binary(format_error(Reason)),
+ true = is_binary(Bin),
+ ok.
+
+nonempty_format_error_test() ->
+ {error, Reason} = validate(<<>>, nonempty),
+ Bin = iolist_to_binary(format_error(Reason)),
+ true = is_binary(Bin),
+ ok.
+
+fun_format_error_test() ->
+ F = fun
+ (format_error, {test, <<"value">>}) ->
+ formatted;
+ (_, _) ->
+ {error, test}
+ end,
+ {error, Reason} = validate(<<"value">>, F),
+ formatted = format_error(Reason),
+ ok.
+
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_handler.erl b/server/_build/default/lib/cowboy/src/cowboy_handler.erl
new file mode 100644
index 0000000..c0f7ff7
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_handler.erl
@@ -0,0 +1,57 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Handler middleware.
+%%
+%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>
+%% environment values. The result of this execution is added to the
+%% environment under the <em>result</em> value.
+-module(cowboy_handler).
+-behaviour(cowboy_middleware).
+
+-export([execute/2]).
+-export([terminate/4]).
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback terminate(any(), map(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+-spec execute(Req, Env) -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
+ try Handler:init(Req, HandlerOpts) of
+ {ok, Req2, State} ->
+ Result = terminate(normal, Req2, State, Handler),
+ {ok, Req2, Env#{result => Result}};
+ {Mod, Req2, State} ->
+ Mod:upgrade(Req2, Env, Handler, State);
+ {Mod, Req2, State, Opts} ->
+ Mod:upgrade(Req2, Env, Handler, State, Opts)
+ catch Class:Reason:Stacktrace ->
+ terminate({crash, Class, Reason}, Req, HandlerOpts, Handler),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+-spec terminate(any(), Req | undefined, any(), module()) -> ok when Req::cowboy_req:req().
+terminate(Reason, Req, State, Handler) ->
+ case erlang:function_exported(Handler, terminate, 3) of
+ true ->
+ Handler:terminate(Reason, Req, State);
+ false ->
+ ok
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_http.erl b/server/_build/default/lib/cowboy/src/cowboy_http.erl
new file mode 100644
index 0000000..c9bceed
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_http.erl
@@ -0,0 +1,1523 @@
+%% Copyright (c) 2016-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_http).
+
+-export([init/6]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type opts() :: #{
+ active_n => pos_integer(),
+ chunked => boolean(),
+ compress_buffering => boolean(),
+ compress_threshold => non_neg_integer(),
+ connection_type => worker | supervisor,
+ env => cowboy_middleware:env(),
+ http10_keepalive => boolean(),
+ idle_timeout => timeout(),
+ inactivity_timeout => timeout(),
+ initial_stream_flow_size => non_neg_integer(),
+ linger_timeout => timeout(),
+ logger => module(),
+ max_authority_length => non_neg_integer(),
+ max_empty_lines => non_neg_integer(),
+ max_header_name_length => non_neg_integer(),
+ max_header_value_length => non_neg_integer(),
+ max_headers => non_neg_integer(),
+ max_keepalive => non_neg_integer(),
+ max_method_length => non_neg_integer(),
+ max_request_line_length => non_neg_integer(),
+ metrics_callback => cowboy_metrics_h:metrics_callback(),
+ metrics_req_filter => fun((cowboy_req:req()) -> map()),
+ metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
+ middlewares => [module()],
+ proxy_header => boolean(),
+ request_timeout => timeout(),
+ sendfile => boolean(),
+ shutdown_timeout => timeout(),
+ stream_handlers => [module()],
+ tracer_callback => cowboy_tracer_h:tracer_callback(),
+ tracer_flags => [atom()],
+ tracer_match_specs => cowboy_tracer_h:tracer_match_specs(),
+ %% Open ended because configured stream handlers might add options.
+ _ => _
+}.
+-export_type([opts/0]).
+
+-record(ps_request_line, {
+ empty_lines = 0 :: non_neg_integer()
+}).
+
+-record(ps_header, {
+ method = undefined :: binary(),
+ authority = undefined :: binary() | undefined,
+ path = undefined :: binary(),
+ qs = undefined :: binary(),
+ version = undefined :: cowboy:http_version(),
+ headers = undefined :: cowboy:http_headers() | undefined,
+ name = undefined :: binary() | undefined
+}).
+
+-record(ps_body, {
+ length :: non_neg_integer() | undefined,
+ received = 0 :: non_neg_integer(),
+ transfer_decode_fun :: fun((binary(), cow_http_te:state()) -> cow_http_te:decode_ret()),
+ transfer_decode_state :: cow_http_te:state()
+}).
+
+-record(stream, {
+ id = undefined :: cowboy_stream:streamid(),
+ %% Stream handlers and their state.
+ state = undefined :: {module(), any()},
+ %% Request method.
+ method = undefined :: binary(),
+ %% Client HTTP version for this stream.
+ version = undefined :: cowboy:http_version(),
+ %% Unparsed te header. Used to know if we can send trailers.
+ te :: undefined | binary(),
+ %% Expected body size.
+ local_expected_size = undefined :: undefined | non_neg_integer(),
+ %% Sent body size.
+ local_sent_size = 0 :: non_neg_integer(),
+ %% Commands queued.
+ queue = [] :: cowboy_stream:commands()
+}).
+
+-type stream() :: #stream{}.
+
+-record(state, {
+ parent :: pid(),
+ ref :: ranch:ref(),
+ socket :: inet:socket(),
+ transport :: module(),
+ proxy_header :: undefined | ranch_proxy_header:proxy_info(),
+ opts = #{} :: cowboy:opts(),
+ buffer = <<>> :: binary(),
+
+ %% Some options may be overriden for the current stream.
+ overriden_opts = #{} :: cowboy:opts(),
+
+ %% Remote address and port for the connection.
+ peer = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Local address and port for the connection.
+ sock = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Client certificate (TLS only).
+ cert :: undefined | binary(),
+
+ timer = undefined :: undefined | reference(),
+
+ %% Whether we are currently receiving data from the socket.
+ active = true :: boolean(),
+
+ %% Identifier for the stream currently being read (or waiting to be received).
+ in_streamid = 1 :: pos_integer(),
+
+ %% Parsing state for the current stream or stream-to-be.
+ in_state = #ps_request_line{} :: #ps_request_line{} | #ps_header{} | #ps_body{},
+
+ %% Flow requested for the current stream.
+ flow = infinity :: non_neg_integer() | infinity,
+
+ %% Identifier for the stream currently being written.
+ %% Note that out_streamid =< in_streamid.
+ out_streamid = 1 :: pos_integer(),
+
+ %% Whether we finished writing data for the current stream.
+ out_state = wait :: wait | chunked | streaming | done,
+
+ %% The connection will be closed after this stream.
+ last_streamid = undefined :: pos_integer(),
+
+ %% Currently active HTTP/1.1 streams.
+ streams = [] :: [stream()],
+
+ %% Children processes created by streams.
+ children = cowboy_children:init() :: cowboy_children:children()
+}).
+
+-include_lib("cowlib/include/cow_inline.hrl").
+-include_lib("cowlib/include/cow_parse.hrl").
+
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info(), cowboy:opts()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->
+ Peer0 = Transport:peername(Socket),
+ Sock0 = Transport:sockname(Socket),
+ Cert1 = case Transport:name() of
+ ssl ->
+ case ssl:peercert(Socket) of
+ {error, no_peercert} ->
+ {ok, undefined};
+ Cert0 ->
+ Cert0
+ end;
+ _ ->
+ {ok, undefined}
+ end,
+ case {Peer0, Sock0, Cert1} of
+ {{ok, Peer}, {ok, Sock}, {ok, Cert}} ->
+ State = #state{
+ parent=Parent, ref=Ref, socket=Socket,
+ transport=Transport, proxy_header=ProxyHeader, opts=Opts,
+ peer=Peer, sock=Sock, cert=Cert,
+ last_streamid=maps:get(max_keepalive, Opts, 1000)},
+ setopts_active(State),
+ loop(set_timeout(State, request_timeout));
+ {{error, Reason}, _, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the peer name.'});
+ {_, {error, Reason}, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the sock name.'});
+ {_, _, {error, Reason}} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the client TLS certificate.'})
+ end.
+
+setopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ N = maps:get(active_n, Opts, 100),
+ Transport:setopts(Socket, [{active, N}]).
+
+active(State) ->
+ setopts_active(State),
+ State#state{active=true}.
+
+passive(State=#state{socket=Socket, transport=Transport}) ->
+ Transport:setopts(Socket, [{active, false}]),
+ Messages = Transport:messages(),
+ flush_passive(Socket, Messages),
+ State#state{active=false}.
+
+flush_passive(Socket, Messages) ->
+ receive
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ flush_passive(Socket, Messages)
+ after 0 ->
+ ok
+ end.
+
+loop(State=#state{parent=Parent, socket=Socket, transport=Transport, opts=Opts,
+ buffer=Buffer, timer=TimerRef, children=Children, in_streamid=InStreamID,
+ last_streamid=LastStreamID}) ->
+ Messages = Transport:messages(),
+ InactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),
+ receive
+ %% Discard data coming in after the last request
+ %% we want to process was received fully.
+ {OK, Socket, _} when OK =:= element(1, Messages), InStreamID > LastStreamID ->
+ loop(State);
+ %% Socket messages.
+ {OK, Socket, Data} when OK =:= element(1, Messages) ->
+ parse(<< Buffer/binary, Data/binary >>, State);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ terminate(State, {socket_error, closed, 'The socket has been closed.'});
+ {Error, Socket, Reason} when Error =:= element(3, Messages) ->
+ terminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ setopts_active(State),
+ loop(State);
+ %% Timeouts.
+ {timeout, Ref, {shutdown, Pid}} ->
+ cowboy_children:shutdown_timeout(Children, Ref, Pid),
+ loop(State);
+ {timeout, TimerRef, Reason} ->
+ timeout(State, Reason);
+ {timeout, _, _} ->
+ loop(State);
+ %% System messages.
+ {'EXIT', Parent, shutdown} ->
+ Reason = {stop, {exit, shutdown}, 'Parent process requested shutdown.'},
+ loop(initiate_closing(State, Reason));
+ {'EXIT', Parent, Reason} ->
+ terminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State);
+ %% Messages pertaining to a stream.
+ {{Pid, StreamID}, Msg} when Pid =:= self() ->
+ loop(info(State, StreamID, Msg));
+ %% Exit signal from children.
+ Msg = {'EXIT', Pid, _} ->
+ loop(down(State, Pid, Msg));
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),
+ loop(State);
+ %% Unknown messages.
+ Msg ->
+ cowboy:log(warning, "Received stray message ~p.~n", [Msg], Opts),
+ loop(State)
+ after InactivityTimeout ->
+ terminate(State, {internal_error, timeout, 'No message or data received before timeout.'})
+ end.
+
+%% We do not set request_timeout if there are active streams.
+set_timeout(State=#state{streams=[_|_]}, request_timeout) ->
+ State;
+%% We do not set request_timeout if we are skipping a body.
+set_timeout(State=#state{in_state=#ps_body{}}, request_timeout) ->
+ State;
+%% We do not set idle_timeout if there are no active streams,
+%% unless when we are skipping a body.
+set_timeout(State=#state{streams=[], in_state=InState}, idle_timeout)
+ when element(1, InState) =/= ps_body ->
+ State;
+%% Otherwise we can set the timeout.
+set_timeout(State0=#state{opts=Opts, overriden_opts=Override}, Name) ->
+ State = cancel_timeout(State0),
+ Default = case Name of
+ request_timeout -> 5000;
+ idle_timeout -> 60000
+ end,
+ Timeout = case Override of
+ %% The timeout may have been overriden for the current stream.
+ #{Name := Timeout0} -> Timeout0;
+ _ -> maps:get(Name, Opts, Default)
+ end,
+ TimerRef = case Timeout of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), Name)
+ end,
+ State#state{timer=TimerRef}.
+
+cancel_timeout(State=#state{timer=TimerRef}) ->
+ ok = case TimerRef of
+ undefined ->
+ ok;
+ _ ->
+ %% Do a synchronous cancel and remove the message if any
+ %% to avoid receiving stray messages.
+ _ = erlang:cancel_timer(TimerRef),
+ receive
+ {timeout, TimerRef, _} -> ok
+ after 0 ->
+ ok
+ end
+ end,
+ State#state{timer=undefined}.
+
+-spec timeout(_, _) -> no_return().
+timeout(State=#state{in_state=#ps_request_line{}}, request_timeout) ->
+ terminate(State, {connection_error, timeout,
+ 'No request-line received before timeout.'});
+timeout(State=#state{in_state=#ps_header{}}, request_timeout) ->
+ error_terminate(408, State, {connection_error, timeout,
+ 'Request headers not received before timeout.'});
+timeout(State, idle_timeout) ->
+ terminate(State, {connection_error, timeout,
+ 'Connection idle longer than configuration allows.'}).
+
+parse(<<>>, State) ->
+ loop(State#state{buffer= <<>>});
+%% Do not process requests that come in after the last request
+%% and discard the buffer if any to save memory.
+parse(_, State=#state{in_streamid=InStreamID, in_state=#ps_request_line{},
+ last_streamid=LastStreamID}) when InStreamID > LastStreamID ->
+ loop(State#state{buffer= <<>>});
+parse(Buffer, State=#state{in_state=#ps_request_line{empty_lines=EmptyLines}}) ->
+ after_parse(parse_request(Buffer, State, EmptyLines));
+parse(Buffer, State=#state{in_state=PS=#ps_header{headers=Headers, name=undefined}}) ->
+ after_parse(parse_header(Buffer,
+ State#state{in_state=PS#ps_header{headers=undefined}},
+ Headers));
+parse(Buffer, State=#state{in_state=PS=#ps_header{headers=Headers, name=Name}}) ->
+ after_parse(parse_hd_before_value(Buffer,
+ State#state{in_state=PS#ps_header{headers=undefined, name=undefined}},
+ Headers, Name));
+parse(Buffer, State=#state{in_state=#ps_body{}}) ->
+ after_parse(parse_body(Buffer, State)).
+
+after_parse({request, Req=#{streamid := StreamID, method := Method,
+ headers := Headers, version := Version},
+ State0=#state{opts=Opts, buffer=Buffer, streams=Streams0}}) ->
+ try cowboy_stream:init(StreamID, Req, Opts) of
+ {Commands, StreamState} ->
+ Flow = maps:get(initial_stream_flow_size, Opts, 65535),
+ TE = maps:get(<<"te">>, Headers, undefined),
+ Streams = [#stream{id=StreamID, state=StreamState,
+ method=Method, version=Version, te=TE}|Streams0],
+ State1 = case maybe_req_close(State0, Headers, Version) of
+ close -> State0#state{streams=Streams, last_streamid=StreamID, flow=Flow};
+ keepalive -> State0#state{streams=Streams, flow=Flow}
+ end,
+ State = set_timeout(State1, idle_timeout),
+ parse(Buffer, commands(State, StreamID, Commands))
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(init,
+ [StreamID, Req, Opts],
+ Class, Exception, Stacktrace), Opts),
+ early_error(500, State0, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:init/3.'}, Req),
+ parse(Buffer, State0)
+ end;
+%% Streams are sequential so the body is always about the last stream created
+%% unless that stream has terminated.
+after_parse({data, StreamID, IsFin, Data, State0=#state{opts=Opts, buffer=Buffer,
+ streams=Streams0=[Stream=#stream{id=StreamID, state=StreamState0}|_]}}) ->
+ try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
+ {Commands, StreamState} ->
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{state=StreamState}),
+ State1 = set_timeout(State0, case IsFin of
+ fin -> request_timeout;
+ nofin -> idle_timeout
+ end),
+ State = update_flow(IsFin, Data, State1#state{streams=Streams}),
+ parse(Buffer, commands(State, StreamID, Commands))
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(data,
+ [StreamID, IsFin, Data, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ %% @todo Should call parse after this.
+ stream_terminate(State0, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:data/4.'})
+ end;
+%% No corresponding stream. We must skip the body of the previous request
+%% in order to process the next one.
+after_parse({data, _, IsFin, _, State}) ->
+ loop(set_timeout(State, case IsFin of
+ fin -> request_timeout;
+ nofin -> idle_timeout
+ end));
+after_parse({more, State}) ->
+ loop(set_timeout(State, idle_timeout)).
+
+update_flow(fin, _, State) ->
+ %% This function is only called after parsing, therefore we
+ %% are expecting to be in active mode already.
+ State#state{flow=infinity};
+update_flow(nofin, Data, State0=#state{flow=Flow0}) ->
+ Flow = Flow0 - byte_size(Data),
+ State = State0#state{flow=Flow},
+ if
+ Flow0 > 0, Flow =< 0 ->
+ passive(State);
+ true ->
+ State
+ end.
+
+%% Request-line.
+
+-spec parse_request(Buffer, State, non_neg_integer())
+ -> {request, cowboy_req:req(), State}
+ | {data, cowboy_stream:streamid(), cowboy_stream:fin(), binary(), State}
+ | {more, State}
+ when Buffer::binary(), State::#state{}.
+%% Empty lines must be using \r\n.
+parse_request(<< $\n, _/bits >>, State, _) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Empty lines between requests must use the CRLF line terminator. (RFC7230 3.5)'});
+parse_request(<< $\s, _/bits >>, State, _) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-line must not begin with a space. (RFC7230 3.1.1, RFC7230 3.5)'});
+%% We limit the length of the Request-line to MaxLength to avoid endlessly
+%% reading from the socket and eventually crashing.
+parse_request(Buffer, State=#state{opts=Opts, in_streamid=InStreamID}, EmptyLines) ->
+ MaxLength = maps:get(max_request_line_length, Opts, 8000),
+ MaxEmptyLines = maps:get(max_empty_lines, Opts, 5),
+ case match_eol(Buffer, 0) of
+ nomatch when byte_size(Buffer) > MaxLength ->
+ error_terminate(414, State, {connection_error, limit_reached,
+ 'The request-line length is larger than configuration allows. (RFC7230 3.1.1)'});
+ nomatch ->
+ {more, State#state{buffer=Buffer, in_state=#ps_request_line{empty_lines=EmptyLines}}};
+ 1 when EmptyLines =:= MaxEmptyLines ->
+ error_terminate(400, State, {connection_error, limit_reached,
+ 'More empty lines were received than configuration allows. (RFC7230 3.5)'});
+ 1 ->
+ << _:16, Rest/bits >> = Buffer,
+ parse_request(Rest, State, EmptyLines + 1);
+ _ ->
+ case Buffer of
+ %% @todo * is only for server-wide OPTIONS request (RFC7230 5.3.4); tests
+ << "OPTIONS * ", Rest/bits >> ->
+ parse_version(Rest, State, <<"OPTIONS">>, undefined, <<"*">>, <<>>);
+ <<"CONNECT ", _/bits>> ->
+ error_terminate(501, State, {connection_error, no_error,
+ 'The CONNECT method is currently not implemented. (RFC7231 4.3.6)'});
+ <<"TRACE ", _/bits>> ->
+ error_terminate(501, State, {connection_error, no_error,
+ 'The TRACE method is currently not implemented. (RFC7231 4.3.8)'});
+ %% Accept direct HTTP/2 only at the beginning of the connection.
+ << "PRI * HTTP/2.0\r\n", _/bits >> when InStreamID =:= 1 ->
+ %% @todo Might be worth throwing to get a clean stacktrace.
+ http2_upgrade(State, Buffer);
+ _ ->
+ parse_method(Buffer, State, <<>>,
+ maps:get(max_method_length, Opts, 32))
+ end
+ end.
+
+match_eol(<< $\n, _/bits >>, N) ->
+ N;
+match_eol(<< _, Rest/bits >>, N) ->
+ match_eol(Rest, N + 1);
+match_eol(_, _) ->
+ nomatch.
+
+parse_method(_, State, _, 0) ->
+ error_terminate(501, State, {connection_error, limit_reached,
+ 'The method name is longer than configuration allows. (RFC7230 3.1.1)'});
+parse_method(<< C, Rest/bits >>, State, SoFar, Remaining) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The method name must not be followed with a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_uri(Rest, State, SoFar);
+ _ when ?IS_TOKEN(C) -> parse_method(Rest, State, << SoFar/binary, C >>, Remaining - 1);
+ _ -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The method name must contain only valid token characters. (RFC7230 3.1.1)'})
+ end.
+
+parse_uri(<< H, T, T, P, "://", Rest/bits >>, State, Method)
+ when H =:= $h orelse H =:= $H, T =:= $t orelse T =:= $T;
+ P =:= $p orelse P =:= $P ->
+ parse_uri_authority(Rest, State, Method);
+parse_uri(<< H, T, T, P, S, "://", Rest/bits >>, State, Method)
+ when H =:= $h orelse H =:= $H, T =:= $t orelse T =:= $T;
+ P =:= $p orelse P =:= $P; S =:= $s orelse S =:= $S ->
+ parse_uri_authority(Rest, State, Method);
+parse_uri(<< $/, Rest/bits >>, State, Method) ->
+ parse_uri_path(Rest, State, Method, undefined, <<$/>>);
+parse_uri(_, State, _) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Invalid request-line or request-target. (RFC7230 3.1.1, RFC7230 5.3)'}).
+
+%% @todo We probably want to apply max_authority_length also
+%% to the host header and to document this option. It might
+%% also be useful for HTTP/2 requests.
+parse_uri_authority(Rest, State=#state{opts=Opts}, Method) ->
+ parse_uri_authority(Rest, State, Method, <<>>,
+ maps:get(max_authority_length, Opts, 255)).
+
+parse_uri_authority(_, State, _, _, 0) ->
+ error_terminate(414, State, {connection_error, limit_reached,
+ 'The authority component of the absolute URI is longer than configuration allows. (RFC7230 2.7.1)'});
+parse_uri_authority(<<C, Rest/bits>>, State, Method, SoFar, Remaining) ->
+ case C of
+ $\r ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $@ ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Absolute URIs must not include a userinfo component. (RFC7230 2.7.1)'});
+ C when SoFar =:= <<>> andalso
+ ((C =:= $/) orelse (C =:= $\s) orelse (C =:= $?) orelse (C =:= $#)) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Absolute URIs must include a non-empty host component. (RFC7230 2.7.1)'});
+ $: when SoFar =:= <<>> ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Absolute URIs must include a non-empty host component. (RFC7230 2.7.1)'});
+ $/ -> parse_uri_path(Rest, State, Method, SoFar, <<"/">>);
+ $\s -> parse_version(Rest, State, Method, SoFar, <<"/">>, <<>>);
+ $? -> parse_uri_query(Rest, State, Method, SoFar, <<"/">>, <<>>);
+ $# -> skip_uri_fragment(Rest, State, Method, SoFar, <<"/">>, <<>>);
+ C -> parse_uri_authority(Rest, State, Method, <<SoFar/binary, C>>, Remaining - 1)
+ end.
+
+parse_uri_path(<<C, Rest/bits>>, State, Method, Authority, SoFar) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_version(Rest, State, Method, Authority, SoFar, <<>>);
+ $? -> parse_uri_query(Rest, State, Method, Authority, SoFar, <<>>);
+ $# -> skip_uri_fragment(Rest, State, Method, Authority, SoFar, <<>>);
+ _ -> parse_uri_path(Rest, State, Method, Authority, <<SoFar/binary, C>>)
+ end.
+
+parse_uri_query(<<C, Rest/bits>>, State, M, A, P, SoFar) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_version(Rest, State, M, A, P, SoFar);
+ $# -> skip_uri_fragment(Rest, State, M, A, P, SoFar);
+ _ -> parse_uri_query(Rest, State, M, A, P, <<SoFar/binary, C>>)
+ end.
+
+skip_uri_fragment(<<C, Rest/bits>>, State, M, A, P, Q) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_version(Rest, State, M, A, P, Q);
+ _ -> skip_uri_fragment(Rest, State, M, A, P, Q)
+ end.
+
+parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, State, M, A, P, Q) ->
+ before_parse_headers(Rest, State, M, A, P, Q, 'HTTP/1.1');
+parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, State, M, A, P, Q) ->
+ before_parse_headers(Rest, State, M, A, P, Q, 'HTTP/1.0');
+parse_version(<< "HTTP/1.", _, C, _/bits >>, State, _, _, _, _) when C =:= $\s; C =:= $\t ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Whitespace is not allowed after the HTTP version. (RFC7230 3.1.1)'});
+parse_version(<< C, _/bits >>, State, _, _, _, _) when C =:= $\s; C =:= $\t ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The separator between request target and version must be a single SP. (RFC7230 3.1.1)'});
+parse_version(_, State, _, _, _, _) ->
+ error_terminate(505, State, {connection_error, protocol_error,
+ 'Unsupported HTTP version. (RFC7230 2.6)'}).
+
+before_parse_headers(Rest, State, M, A, P, Q, V) ->
+ parse_header(Rest, State#state{in_state=#ps_header{
+ method=M, authority=A, path=P, qs=Q, version=V}}, #{}).
+
+%% Headers.
+
+%% We need two or more bytes in the buffer to continue.
+parse_header(Rest, State=#state{in_state=PS}, Headers) when byte_size(Rest) < 2 ->
+ {more, State#state{buffer=Rest, in_state=PS#ps_header{headers=Headers}}};
+parse_header(<< $\r, $\n, Rest/bits >>, S, Headers) ->
+ request(Rest, S, Headers);
+parse_header(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->
+ MaxHeaders = maps:get(max_headers, Opts, 100),
+ NumHeaders = maps:size(Headers),
+ if
+ NumHeaders >= MaxHeaders ->
+ error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
+ {connection_error, limit_reached,
+ 'The number of headers is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
+ true ->
+ parse_header_colon(Buffer, State, Headers)
+ end.
+
+parse_header_colon(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->
+ MaxLength = maps:get(max_header_name_length, Opts, 64),
+ case match_colon(Buffer, 0) of
+ nomatch when byte_size(Buffer) > MaxLength ->
+ error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
+ {connection_error, limit_reached,
+ 'A header name is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
+ nomatch ->
+ %% We don't have a colon but we might have an invalid header line,
+ %% so check if we have an LF and abort with an error if we do.
+ case match_eol(Buffer, 0) of
+ nomatch ->
+ {more, State#state{buffer=Buffer, in_state=PS#ps_header{headers=Headers}}};
+ _ ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {connection_error, protocol_error,
+ 'A header line is missing a colon separator. (RFC7230 3.2.4)'})
+ end;
+ _ ->
+ parse_hd_name(Buffer, State, Headers, <<>>)
+ end.
+
+match_colon(<< $:, _/bits >>, N) ->
+ N;
+match_colon(<< _, Rest/bits >>, N) ->
+ match_colon(Rest, N + 1);
+match_colon(_, _) ->
+ nomatch.
+
+parse_hd_name(<< $:, Rest/bits >>, State, H, SoFar) ->
+ parse_hd_before_value(Rest, State, H, SoFar);
+parse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, <<>>) when ?IS_WS(C) ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=H}},
+ {connection_error, protocol_error,
+ 'Whitespace is not allowed before the header name. (RFC7230 3.2)'});
+parse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, _) when ?IS_WS(C) ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=H}},
+ {connection_error, protocol_error,
+ 'Whitespace is not allowed between the header name and the colon. (RFC7230 3.2.4)'});
+parse_hd_name(<< C, Rest/bits >>, State, H, SoFar) ->
+ ?LOWER(parse_hd_name, Rest, State, H, SoFar).
+
+parse_hd_before_value(<< $\s, Rest/bits >>, S, H, N) ->
+ parse_hd_before_value(Rest, S, H, N);
+parse_hd_before_value(<< $\t, Rest/bits >>, S, H, N) ->
+ parse_hd_before_value(Rest, S, H, N);
+parse_hd_before_value(Buffer, State=#state{opts=Opts, in_state=PS}, H, N) ->
+ MaxLength = maps:get(max_header_value_length, Opts, 4096),
+ case match_eol(Buffer, 0) of
+ nomatch when byte_size(Buffer) > MaxLength ->
+ error_terminate(431, State#state{in_state=PS#ps_header{headers=H}},
+ {connection_error, limit_reached,
+ 'A header value is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
+ nomatch ->
+ {more, State#state{buffer=Buffer, in_state=PS#ps_header{headers=H, name=N}}};
+ _ ->
+ parse_hd_value(Buffer, State, H, N, <<>>)
+ end.
+
+parse_hd_value(<< $\r, $\n, Rest/bits >>, S, Headers0, Name, SoFar) ->
+ Value = clean_value_ws_end(SoFar, byte_size(SoFar) - 1),
+ Headers = case maps:get(Name, Headers0, undefined) of
+ undefined -> Headers0#{Name => Value};
+ %% The cookie header does not use proper HTTP header lists.
+ Value0 when Name =:= <<"cookie">> -> Headers0#{Name => << Value0/binary, "; ", Value/binary >>};
+ Value0 -> Headers0#{Name => << Value0/binary, ", ", Value/binary >>}
+ end,
+ parse_header(Rest, S, Headers);
+parse_hd_value(<< C, Rest/bits >>, S, H, N, SoFar) ->
+ parse_hd_value(Rest, S, H, N, << SoFar/binary, C >>).
+
+clean_value_ws_end(_, -1) ->
+ <<>>;
+clean_value_ws_end(Value, N) ->
+ case binary:at(Value, N) of
+ $\s -> clean_value_ws_end(Value, N - 1);
+ $\t -> clean_value_ws_end(Value, N - 1);
+ _ ->
+ S = N + 1,
+ << Value2:S/binary, _/bits >> = Value,
+ Value2
+ end.
+
+-ifdef(TEST).
+clean_value_ws_end_test_() ->
+ Tests = [
+ {<<>>, <<>>},
+ {<<" ">>, <<>>},
+ {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5 \t \t ">>,
+ <<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5">>}
+ ],
+ [{V, fun() -> R = clean_value_ws_end(V, byte_size(V) - 1) end} || {V, R} <- Tests].
+
+horse_clean_value_ws_end() ->
+ horse:repeat(200000,
+ clean_value_ws_end(
+ <<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5 ">>,
+ byte_size(<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5 ">>) - 1)
+ ).
+-endif.
+
+request(Buffer, State=#state{transport=Transport,
+ in_state=PS=#ps_header{authority=Authority, version=Version}}, Headers) ->
+ case maps:get(<<"host">>, Headers, undefined) of
+ undefined when Version =:= 'HTTP/1.1' ->
+ %% @todo Might want to not close the connection on this and next one.
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {stream_error, protocol_error,
+ 'HTTP/1.1 requests must include a host header. (RFC7230 5.4)'});
+ undefined ->
+ request(Buffer, State, Headers, <<>>, default_port(Transport:secure()));
+ %% @todo When CONNECT requests come in we need to ignore the RawHost
+ %% and instead use the Authority as the source of host.
+ RawHost when Authority =:= undefined; Authority =:= RawHost ->
+ request_parse_host(Buffer, State, Headers, RawHost);
+ %% RFC7230 does not explicitly ask us to reject requests
+ %% that have a different authority component and host header.
+ %% However it DOES ask clients to set them to the same value,
+ %% so we enforce that.
+ _ ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {stream_error, protocol_error,
+ 'The host header is different than the absolute-form authority component. (RFC7230 5.4)'})
+ end.
+
+request_parse_host(Buffer, State=#state{transport=Transport, in_state=PS}, Headers, RawHost) ->
+ try cow_http_hd:parse_host(RawHost) of
+ {Host, undefined} ->
+ request(Buffer, State, Headers, Host, default_port(Transport:secure()));
+ {Host, Port} when Port > 0, Port =< 65535 ->
+ request(Buffer, State, Headers, Host, Port);
+ _ ->
+ error_terminate(400, State, {stream_error, protocol_error,
+ 'The port component of the absolute-form is not in the range 0..65535. (RFC7230 2.7.1)'})
+ catch _:_ ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {stream_error, protocol_error,
+ 'The host header is invalid. (RFC7230 5.4)'})
+ end.
+
+-spec default_port(boolean()) -> 80 | 443.
+default_port(true) -> 443;
+default_port(_) -> 80.
+
+%% End of request parsing.
+
+request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert,
+ proxy_header=ProxyHeader, in_streamid=StreamID, in_state=
+ PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},
+ Headers0, Host, Port) ->
+ Scheme = case Transport:secure() of
+ true -> <<"https">>;
+ false -> <<"http">>
+ end,
+ {Headers, HasBody, BodyLength, TDecodeFun, TDecodeState} = case Headers0 of
+ #{<<"transfer-encoding">> := TransferEncoding0} ->
+ try cow_http_hd:parse_transfer_encoding(TransferEncoding0) of
+ [<<"chunked">>] ->
+ {maps:remove(<<"content-length">>, Headers0),
+ true, undefined, fun cow_http_te:stream_chunked/2, {0, 0}};
+ _ ->
+ error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
+ {stream_error, protocol_error,
+ 'Cowboy only supports transfer-encoding: chunked. (RFC7230 3.3.1)'})
+ catch _:_ ->
+ error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
+ {stream_error, protocol_error,
+ 'The transfer-encoding header is invalid. (RFC7230 3.3.1)'})
+ end;
+ #{<<"content-length">> := <<"0">>} ->
+ {Headers0, false, 0, undefined, undefined};
+ #{<<"content-length">> := BinLength} ->
+ Length = try
+ cow_http_hd:parse_content_length(BinLength)
+ catch _:_ ->
+ error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
+ {stream_error, protocol_error,
+ 'The content-length header is invalid. (RFC7230 3.3.2)'})
+ end,
+ {Headers0, true, Length, fun cow_http_te:stream_identity/2, {0, Length}};
+ _ ->
+ {Headers0, false, 0, undefined, undefined}
+ end,
+ Req0 = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ peer => Peer,
+ sock => Sock,
+ cert => Cert,
+ method => Method,
+ scheme => Scheme,
+ host => Host,
+ port => Port,
+ path => Path,
+ qs => Qs,
+ version => Version,
+ %% We are transparently taking care of transfer-encodings so
+ %% the user code has no need to know about it.
+ headers => maps:remove(<<"transfer-encoding">>, Headers),
+ has_body => HasBody,
+ body_length => BodyLength
+ },
+ %% We add the PROXY header information if any.
+ Req = case ProxyHeader of
+ undefined -> Req0;
+ _ -> Req0#{proxy_header => ProxyHeader}
+ end,
+ case is_http2_upgrade(Headers, Version) of
+ false ->
+ State = case HasBody of
+ true ->
+ State0#state{in_state=#ps_body{
+ length = BodyLength,
+ transfer_decode_fun = TDecodeFun,
+ transfer_decode_state = TDecodeState
+ }};
+ false ->
+ State0#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}
+ end,
+ {request, Req, State#state{buffer=Buffer}};
+ {true, HTTP2Settings} ->
+ %% We save the headers in case the upgrade will fail
+ %% and we need to pass them to cowboy_stream:early_error.
+ http2_upgrade(State0#state{in_state=PS#ps_header{headers=Headers}},
+ Buffer, HTTP2Settings, Req)
+ end.
+
+%% HTTP/2 upgrade.
+
+%% @todo We must not upgrade to h2c over a TLS connection.
+is_http2_upgrade(#{<<"connection">> := Conn, <<"upgrade">> := Upgrade,
+ <<"http2-settings">> := HTTP2Settings}, 'HTTP/1.1') ->
+ Conns = cow_http_hd:parse_connection(Conn),
+ case {lists:member(<<"upgrade">>, Conns), lists:member(<<"http2-settings">>, Conns)} of
+ {true, true} ->
+ Protocols = cow_http_hd:parse_upgrade(Upgrade),
+ case lists:member(<<"h2c">>, Protocols) of
+ true ->
+ {true, HTTP2Settings};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end;
+is_http2_upgrade(_, _) ->
+ false.
+
+%% Prior knowledge upgrade, without an HTTP/1.1 request.
+http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
+ proxy_header=ProxyHeader, opts=Opts, peer=Peer, sock=Sock, cert=Cert}, Buffer) ->
+ case Transport:secure() of
+ false ->
+ _ = cancel_timeout(State),
+ cowboy_http2:init(Parent, Ref, Socket, Transport,
+ ProxyHeader, Opts, Peer, Sock, Cert, Buffer);
+ true ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})
+ end.
+
+%% Upgrade via an HTTP/1.1 request.
+http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
+ proxy_header=ProxyHeader, opts=Opts, peer=Peer, sock=Sock, cert=Cert},
+ Buffer, HTTP2Settings, Req) ->
+ %% @todo
+ %% However if the client sent a body, we need to read the body in full
+ %% and if we can't do that, return a 413 response. Some options are in order.
+ %% Always half-closed stream coming from this side.
+ try cow_http_hd:parse_http2_settings(HTTP2Settings) of
+ Settings ->
+ _ = cancel_timeout(State),
+ cowboy_http2:init(Parent, Ref, Socket, Transport,
+ ProxyHeader, Opts, Peer, Sock, Cert, Buffer, Settings, Req)
+ catch _:_ ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The HTTP2-Settings header must contain a base64 SETTINGS payload. (RFC7540 3.2, RFC7540 3.2.1)'})
+ end.
+
+%% Request body parsing.
+
+parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
+ PS=#ps_body{received=Received, transfer_decode_fun=TDecode,
+ transfer_decode_state=TState0}}) ->
+ %% @todo Proper trailers.
+ try TDecode(Buffer, TState0) of
+ more ->
+ {more, State#state{buffer=Buffer}};
+ {more, Data, TState} ->
+ {data, StreamID, nofin, Data, State#state{buffer= <<>>,
+ in_state=PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}};
+ {more, Data, _Length, TState} when is_integer(_Length) ->
+ {data, StreamID, nofin, Data, State#state{buffer= <<>>,
+ in_state=PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}};
+ {more, Data, Rest, TState} ->
+ {data, StreamID, nofin, Data, State#state{buffer=Rest,
+ in_state=PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}};
+ {done, _HasTrailers, Rest} ->
+ {data, StreamID, fin, <<>>,
+ State#state{buffer=Rest, in_streamid=StreamID + 1, in_state=#ps_request_line{}}};
+ {done, Data, _HasTrailers, Rest} ->
+ {data, StreamID, fin, Data,
+ State#state{buffer=Rest, in_streamid=StreamID + 1, in_state=#ps_request_line{}}}
+ catch _:_ ->
+ Reason = {connection_error, protocol_error,
+ 'Failure to decode the content. (RFC7230 4)'},
+ terminate(stream_terminate(State, StreamID, Reason), Reason)
+ end.
+
+%% Message handling.
+
+down(State=#state{opts=Opts, children=Children0}, Pid, Msg) ->
+ case cowboy_children:down(Children0, Pid) of
+ %% The stream was terminated already.
+ {ok, undefined, Children} ->
+ State#state{children=Children};
+ %% The stream is still running.
+ {ok, StreamID, Children} ->
+ info(State#state{children=Children}, StreamID, Msg);
+ %% The process was unknown.
+ error ->
+ cowboy:log(warning, "Received EXIT signal ~p for unknown process ~p.~n",
+ [Msg, Pid], Opts),
+ State
+ end.
+
+info(State=#state{opts=Opts, streams=Streams0}, StreamID, Msg) ->
+ case lists:keyfind(StreamID, #stream.id, Streams0) of
+ Stream = #stream{state=StreamState0} ->
+ try cowboy_stream:info(StreamID, Msg, StreamState0) of
+ {Commands, StreamState} ->
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{state=StreamState}),
+ commands(State#state{streams=Streams}, StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(info,
+ [StreamID, Msg, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ stream_terminate(State, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:info/3.'})
+ end;
+ false ->
+ cowboy:log(warning, "Received message ~p for unknown stream ~p.~n",
+ [Msg, StreamID], Opts),
+ State
+ end.
+
+%% Commands.
+
+commands(State, _, []) ->
+ State;
+%% Supervise a child process.
+commands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->
+ commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},
+ StreamID, Tail);
+%% Error handling.
+commands(State, StreamID, [Error = {internal_error, _, _}|Tail]) ->
+ commands(stream_terminate(State, StreamID, Error), StreamID, Tail);
+%% Commands for a stream currently inactive.
+commands(State=#state{out_streamid=Current, streams=Streams0}, StreamID, Commands)
+ when Current =/= StreamID ->
+
+ %% @todo We still want to handle some commands...
+
+ Stream = #stream{queue=Queue} = lists:keyfind(StreamID, #stream.id, Streams0),
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{queue=Queue ++ Commands}),
+ State#state{streams=Streams};
+%% When we have finished reading the request body, do nothing.
+commands(State=#state{flow=infinity}, StreamID, [{flow, _}|Tail]) ->
+ commands(State, StreamID, Tail);
+%% Read the request body.
+commands(State0=#state{flow=Flow0}, StreamID, [{flow, Size}|Tail]) ->
+ %% We must read *at least* Size of data otherwise functions
+ %% like cowboy_req:read_body/1,2 will wait indefinitely.
+ Flow = if
+ Flow0 < 0 -> Size;
+ true -> Flow0 + Size
+ end,
+ %% Reenable active mode if necessary.
+ State = if
+ Flow0 =< 0, Flow > 0 ->
+ active(State0);
+ true ->
+ State0
+ end,
+ commands(State#state{flow=Flow}, StreamID, Tail);
+%% Error responses are sent only if a response wasn't sent already.
+commands(State=#state{out_state=wait, out_streamid=StreamID}, StreamID,
+ [{error_response, Status, Headers0, Body}|Tail]) ->
+ %% We close the connection when the error response is 408, as it
+ %% indicates a timeout and the RFC recommends that we stop here. (RFC7231 6.5.7)
+ Headers = case Status of
+ 408 -> Headers0#{<<"connection">> => <<"close">>};
+ <<"408", _/bits>> -> Headers0#{<<"connection">> => <<"close">>};
+ _ -> Headers0
+ end,
+ commands(State, StreamID, [{response, Status, Headers, Body}|Tail]);
+commands(State, StreamID, [{error_response, _, _, _}|Tail]) ->
+ commands(State, StreamID, Tail);
+%% Send an informational response.
+commands(State=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams},
+ StreamID, [{inform, StatusCode, Headers}|Tail]) ->
+ %% @todo I'm pretty sure the last stream in the list is the one we want
+ %% considering all others are queued.
+ #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),
+ _ = case Version of
+ 'HTTP/1.1' ->
+ Transport:send(Socket, cow_http:response(StatusCode, 'HTTP/1.1',
+ headers_to_list(Headers)));
+ %% Do not send informational responses to HTTP/1.0 clients. (RFC7231 6.2)
+ 'HTTP/1.0' ->
+ ok
+ end,
+ commands(State, StreamID, Tail);
+%% Send a full response.
+%%
+%% @todo Kill the stream if it sent a response when one has already been sent.
+%% @todo Keep IsFin in the state.
+%% @todo Same two things above apply to DATA, possibly promise too.
+commands(State0=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams}, StreamID,
+ [{response, StatusCode, Headers0, Body}|Tail]) ->
+ %% @todo I'm pretty sure the last stream in the list is the one we want
+ %% considering all others are queued.
+ #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),
+ {State1, Headers} = connection(State0, Headers0, StreamID, Version),
+ State = State1#state{out_state=done},
+ %% @todo Ensure content-length is set. 204 must never have content-length set.
+ Response = cow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers)),
+ %% @todo 204 and 304 responses must not include a response body. (RFC7230 3.3.1, RFC7230 3.3.2)
+ case Body of
+ {sendfile, _, _, _} ->
+ Transport:send(Socket, Response),
+ sendfile(State, Body);
+ _ ->
+ Transport:send(Socket, [Response, Body])
+ end,
+ commands(State, StreamID, Tail);
+%% Send response headers and initiate chunked encoding or streaming.
+commands(State0=#state{socket=Socket, transport=Transport,
+ opts=Opts, overriden_opts=Override, streams=Streams0, out_state=OutState},
+ StreamID, [{headers, StatusCode, Headers0}|Tail]) ->
+ %% @todo Same as above (about the last stream in the list).
+ Stream = #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams0),
+ Status = cow_http:status_to_integer(StatusCode),
+ ContentLength = maps:get(<<"content-length">>, Headers0, undefined),
+ %% Chunked transfer-encoding can be disabled on a per-request basis.
+ Chunked = case Override of
+ #{chunked := Chunked0} -> Chunked0;
+ _ -> maps:get(chunked, Opts, true)
+ end,
+ {State1, Headers1} = case {Status, ContentLength, Version} of
+ {204, _, 'HTTP/1.1'} ->
+ {State0#state{out_state=done}, Headers0};
+ {304, _, 'HTTP/1.1'} ->
+ {State0#state{out_state=done}, Headers0};
+ {_, undefined, 'HTTP/1.1'} when Chunked ->
+ {State0#state{out_state=chunked}, Headers0#{<<"transfer-encoding">> => <<"chunked">>}};
+ %% Close the connection after streaming without content-length
+ %% to all HTTP/1.0 clients and to HTTP/1.1 clients when chunked is disabled.
+ {_, undefined, _} ->
+ {State0#state{out_state=streaming, last_streamid=StreamID}, Headers0};
+ %% Stream the response body without chunked transfer-encoding.
+ _ ->
+ ExpectedSize = cow_http_hd:parse_content_length(ContentLength),
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{local_expected_size=ExpectedSize}),
+ {State0#state{out_state=streaming, streams=Streams}, Headers0}
+ end,
+ Headers2 = case stream_te(OutState, Stream) of
+ trailers -> Headers1;
+ _ -> maps:remove(<<"trailer">>, Headers1)
+ end,
+ {State, Headers} = connection(State1, Headers2, StreamID, Version),
+ Transport:send(Socket, cow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers))),
+ commands(State, StreamID, Tail);
+%% Send a response body chunk.
+%% @todo We need to kill the stream if it tries to send data before headers.
+commands(State0=#state{socket=Socket, transport=Transport, streams=Streams0, out_state=OutState},
+ StreamID, [{data, IsFin, Data}|Tail]) ->
+ %% Do not send anything when the user asks to send an empty
+ %% data frame, as that would break the protocol.
+ Size = case Data of
+ {sendfile, _, B, _} -> B;
+ _ -> iolist_size(Data)
+ end,
+ %% Depending on the current state we may need to send nothing,
+ %% the last chunk, chunked data with/without the last chunk,
+ %% or just the data as-is.
+ Stream = case lists:keyfind(StreamID, #stream.id, Streams0) of
+ Stream0=#stream{method= <<"HEAD">>} ->
+ Stream0;
+ Stream0 when Size =:= 0, IsFin =:= fin, OutState =:= chunked ->
+ Transport:send(Socket, <<"0\r\n\r\n">>),
+ Stream0;
+ Stream0 when Size =:= 0 ->
+ Stream0;
+ Stream0 when is_tuple(Data), OutState =:= chunked ->
+ Transport:send(Socket, [integer_to_binary(Size, 16), <<"\r\n">>]),
+ sendfile(State0, Data),
+ Transport:send(Socket,
+ case IsFin of
+ fin -> <<"\r\n0\r\n\r\n">>;
+ nofin -> <<"\r\n">>
+ end),
+ Stream0;
+ Stream0 when OutState =:= chunked ->
+ Transport:send(Socket, [
+ integer_to_binary(Size, 16), <<"\r\n">>, Data,
+ case IsFin of
+ fin -> <<"\r\n0\r\n\r\n">>;
+ nofin -> <<"\r\n">>
+ end
+ ]),
+ Stream0;
+ Stream0 when OutState =:= streaming ->
+ #stream{local_sent_size=SentSize0, local_expected_size=ExpectedSize} = Stream0,
+ SentSize = SentSize0 + Size,
+ if
+ %% ExpectedSize may be undefined, which is > any integer value.
+ SentSize > ExpectedSize ->
+ terminate(State0, response_body_too_large);
+ is_tuple(Data) ->
+ sendfile(State0, Data);
+ true ->
+ Transport:send(Socket, Data)
+ end,
+ Stream0#stream{local_sent_size=SentSize}
+ end,
+ State = case IsFin of
+ fin -> State0#state{out_state=done};
+ nofin -> State0
+ end,
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0, Stream),
+ commands(State#state{streams=Streams}, StreamID, Tail);
+commands(State=#state{socket=Socket, transport=Transport, streams=Streams, out_state=OutState},
+ StreamID, [{trailers, Trailers}|Tail]) ->
+ case stream_te(OutState, lists:keyfind(StreamID, #stream.id, Streams)) of
+ trailers ->
+ Transport:send(Socket, [
+ <<"0\r\n">>,
+ cow_http:headers(maps:to_list(Trailers)),
+ <<"\r\n">>
+ ]);
+ no_trailers ->
+ Transport:send(Socket, <<"0\r\n\r\n">>);
+ not_chunked ->
+ ok
+ end,
+ commands(State#state{out_state=done}, StreamID, Tail);
+%% Protocol takeover.
+commands(State0=#state{ref=Ref, parent=Parent, socket=Socket, transport=Transport,
+ out_state=OutState, opts=Opts, buffer=Buffer, children=Children}, StreamID,
+ [{switch_protocol, Headers, Protocol, InitialState}|_Tail]) ->
+ %% @todo If there's streams opened after this one, fail instead of 101.
+ State1 = cancel_timeout(State0),
+ %% Before we send the 101 response we need to stop receiving data
+ %% from the socket, otherwise the data might be receive before the
+ %% call to flush/0 and we end up inadvertently dropping a packet.
+ %%
+ %% @todo Handle cases where the request came with a body. We need
+ %% to process or skip the body before the upgrade can be completed.
+ State = passive(State1),
+ %% Send a 101 response if necessary, then terminate the stream.
+ #state{streams=Streams} = case OutState of
+ wait -> info(State, StreamID, {inform, 101, Headers});
+ _ -> State
+ end,
+ #stream{state=StreamState} = lists:keyfind(StreamID, #stream.id, Streams),
+ %% @todo We need to shutdown processes here first.
+ stream_call_terminate(StreamID, switch_protocol, StreamState, State),
+ %% Terminate children processes and flush any remaining messages from the mailbox.
+ cowboy_children:terminate(Children),
+ flush(Parent),
+ Protocol:takeover(Parent, Ref, Socket, Transport, Opts, Buffer, InitialState);
+%% Set options dynamically.
+commands(State0=#state{overriden_opts=Opts},
+ StreamID, [{set_options, SetOpts}|Tail]) ->
+ State1 = case SetOpts of
+ #{idle_timeout := IdleTimeout} ->
+ set_timeout(State0#state{overriden_opts=Opts#{idle_timeout => IdleTimeout}},
+ idle_timeout);
+ _ ->
+ State0
+ end,
+ State = case SetOpts of
+ #{chunked := Chunked} ->
+ State1#state{overriden_opts=Opts#{chunked => Chunked}};
+ _ ->
+ State1
+ end,
+ commands(State, StreamID, Tail);
+%% Stream shutdown.
+commands(State, StreamID, [stop|Tail]) ->
+ %% @todo Do we want to run the commands after a stop?
+ %% @todo We currently wait for the stop command before we
+ %% continue with the next request/response. In theory, if
+ %% the request body was read fully and the response body
+ %% was sent fully we should be able to start working on
+ %% the next request concurrently. This can be done as a
+ %% future optimization.
+ maybe_terminate(State, StreamID, Tail);
+%% Log event.
+commands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->
+ cowboy:log(Log, Opts),
+ commands(State, StreamID, Tail);
+%% HTTP/1.1 does not support push; ignore.
+commands(State, StreamID, [{push, _, _, _, _, _, _, _}|Tail]) ->
+ commands(State, StreamID, Tail).
+
+%% The set-cookie header is special; we can only send one cookie per header.
+headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
+ Headers1 = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
+ Headers1 ++ [{<<"set-cookie">>, Value} || Value <- SetCookies];
+headers_to_list(Headers) ->
+ maps:to_list(Headers).
+
+%% We wrap the sendfile call into a try/catch because on OTP-20
+%% and earlier a few different crashes could occur for sockets
+%% that were closing or closed. For example a badarg in
+%% erlang:port_get_data(#Port<...>) or a badmatch like
+%% {{badmatch,{error,einval}},[{prim_file,sendfile,8,[]}...
+%%
+%% OTP-21 uses a NIF instead of a port so the implementation
+%% and behavior has dramatically changed and it is unclear
+%% whether it will be necessary in the future.
+%%
+%% This try/catch prevents some noisy logs to be written
+%% when these errors occur.
+sendfile(State=#state{socket=Socket, transport=Transport, opts=Opts},
+ {sendfile, Offset, Bytes, Path}) ->
+ try
+ %% When sendfile is disabled we explicitly use the fallback.
+ _ = case maps:get(sendfile, Opts, true) of
+ true -> Transport:sendfile(Socket, Path, Offset, Bytes);
+ false -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])
+ end,
+ ok
+ catch _:_ ->
+ terminate(State, {socket_error, sendfile_crash,
+ 'An error occurred when using the sendfile function.'})
+ end.
+
+%% Flush messages specific to cowboy_http before handing over the
+%% connection to another protocol.
+flush(Parent) ->
+ receive
+ {timeout, _, _} ->
+ flush(Parent);
+ {{Pid, _}, _} when Pid =:= self() ->
+ flush(Parent);
+ {'EXIT', Pid, _} when Pid =/= Parent ->
+ flush(Parent)
+ after 0 ->
+ ok
+ end.
+
+%% @todo In these cases I'm not sure if we should continue processing commands.
+maybe_terminate(State=#state{last_streamid=StreamID}, StreamID, _Tail) ->
+ terminate(stream_terminate(State, StreamID, normal), normal); %% @todo Reason ok?
+maybe_terminate(State, StreamID, _Tail) ->
+ stream_terminate(State, StreamID, normal).
+
+stream_terminate(State0=#state{opts=Opts, in_streamid=InStreamID, in_state=InState,
+ out_streamid=OutStreamID, out_state=OutState, streams=Streams0,
+ children=Children0}, StreamID, Reason) ->
+ #stream{version=Version, local_expected_size=ExpectedSize, local_sent_size=SentSize}
+ = lists:keyfind(StreamID, #stream.id, Streams0),
+ %% Send a response or terminate chunks depending on the current output state.
+ State1 = #state{streams=Streams1} = case OutState of
+ wait when element(1, Reason) =:= internal_error ->
+ info(State0, StreamID, {response, 500, #{<<"content-length">> => <<"0">>}, <<>>});
+ wait when element(1, Reason) =:= connection_error ->
+ info(State0, StreamID, {response, 400, #{<<"content-length">> => <<"0">>}, <<>>});
+ wait ->
+ info(State0, StreamID, {response, 204, #{}, <<>>});
+ chunked when Version =:= 'HTTP/1.1' ->
+ info(State0, StreamID, {data, fin, <<>>});
+ streaming when SentSize < ExpectedSize ->
+ terminate(State0, response_body_too_small);
+ _ -> %% done or Version =:= 'HTTP/1.0'
+ State0
+ end,
+ %% Stop the stream, shutdown children and reset overriden options.
+ {value, #stream{state=StreamState}, Streams}
+ = lists:keytake(StreamID, #stream.id, Streams1),
+ stream_call_terminate(StreamID, Reason, StreamState, State1),
+ Children = cowboy_children:shutdown(Children0, StreamID),
+ State = State1#state{overriden_opts=#{}, streams=Streams, children=Children},
+ %% We want to drop the connection if the body was not read fully
+ %% and we don't know its length or more remains to be read than
+ %% configuration allows.
+ MaxSkipBodyLength = maps:get(max_skip_body_length, Opts, 1000000),
+ case InState of
+ #ps_body{length=undefined}
+ when InStreamID =:= OutStreamID ->
+ terminate(State, skip_body_unknown_length);
+ #ps_body{length=Len, received=Received}
+ when InStreamID =:= OutStreamID, Received + MaxSkipBodyLength < Len ->
+ terminate(State, skip_body_too_large);
+ #ps_body{} when InStreamID =:= OutStreamID ->
+ stream_next(State#state{flow=infinity});
+ _ ->
+ stream_next(State)
+ end.
+
+stream_next(State0=#state{opts=Opts, active=Active, out_streamid=OutStreamID, streams=Streams}) ->
+ NextOutStreamID = OutStreamID + 1,
+ case lists:keyfind(NextOutStreamID, #stream.id, Streams) of
+ false ->
+ State0#state{out_streamid=NextOutStreamID, out_state=wait};
+ #stream{queue=Commands} ->
+ State = case Active of
+ true -> State0;
+ false -> active(State0)
+ end,
+ %% @todo Remove queue from the stream.
+ %% We set the flow to the initial flow size even though
+ %% we might have sent some data through already due to pipelining.
+ Flow = maps:get(initial_stream_flow_size, Opts, 65535),
+ commands(State#state{flow=Flow, out_streamid=NextOutStreamID, out_state=wait},
+ NextOutStreamID, Commands)
+ end.
+
+stream_call_terminate(StreamID, Reason, StreamState, #state{opts=Opts}) ->
+ try
+ cowboy_stream:terminate(StreamID, Reason, StreamState)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(terminate,
+ [StreamID, Reason, StreamState],
+ Class, Exception, Stacktrace), Opts)
+ end.
+
+maybe_req_close(#state{opts=#{http10_keepalive := false}}, _, 'HTTP/1.0') ->
+ close;
+maybe_req_close(_, #{<<"connection">> := Conn}, 'HTTP/1.0') ->
+ Conns = cow_http_hd:parse_connection(Conn),
+ case lists:member(<<"keep-alive">>, Conns) of
+ true -> keepalive;
+ false -> close
+ end;
+maybe_req_close(_, _, 'HTTP/1.0') ->
+ close;
+maybe_req_close(_, #{<<"connection">> := Conn}, 'HTTP/1.1') ->
+ case connection_hd_is_close(Conn) of
+ true -> close;
+ false -> keepalive
+ end;
+maybe_req_close(_, _, _) ->
+ keepalive.
+
+connection(State=#state{last_streamid=StreamID}, Headers=#{<<"connection">> := Conn}, StreamID, _) ->
+ case connection_hd_is_close(Conn) of
+ true -> {State, Headers};
+ %% @todo Here we need to remove keep-alive and add close, not just add close.
+ false -> {State, Headers#{<<"connection">> => [<<"close, ">>, Conn]}}
+ end;
+connection(State=#state{last_streamid=StreamID}, Headers, StreamID, _) ->
+ {State, Headers#{<<"connection">> => <<"close">>}};
+connection(State, Headers=#{<<"connection">> := Conn}, StreamID, _) ->
+ case connection_hd_is_close(Conn) of
+ true -> {State#state{last_streamid=StreamID}, Headers};
+ %% @todo Here we need to set keep-alive only if it wasn't set before.
+ false -> {State, Headers}
+ end;
+connection(State, Headers, _, 'HTTP/1.0') ->
+ {State, Headers#{<<"connection">> => <<"keep-alive">>}};
+connection(State, Headers, _, _) ->
+ {State, Headers}.
+
+connection_hd_is_close(Conn) ->
+ Conns = cow_http_hd:parse_connection(iolist_to_binary(Conn)),
+ lists:member(<<"close">>, Conns).
+
+stream_te(streaming, _) ->
+ not_chunked;
+%% No TE header was sent.
+stream_te(_, #stream{te=undefined}) ->
+ no_trailers;
+stream_te(_, #stream{te=TE0}) ->
+ try cow_http_hd:parse_te(TE0) of
+ {TE1, _} -> TE1
+ catch _:_ ->
+ %% If we can't parse the TE header, assume we can't send trailers.
+ no_trailers
+ end.
+
+%% This function is only called when an error occurs on a new stream.
+-spec error_terminate(cowboy:http_status(), #state{}, _) -> no_return().
+error_terminate(StatusCode, State=#state{ref=Ref, peer=Peer, in_state=StreamState}, Reason) ->
+ PartialReq = case StreamState of
+ #ps_request_line{} -> #{
+ ref => Ref,
+ peer => Peer
+ };
+ #ps_header{method=Method, path=Path, qs=Qs,
+ version=Version, headers=ReqHeaders} -> #{
+ ref => Ref,
+ peer => Peer,
+ method => Method,
+ path => Path,
+ qs => Qs,
+ version => Version,
+ headers => case ReqHeaders of
+ undefined -> #{};
+ _ -> ReqHeaders
+ end
+ }
+ end,
+ early_error(StatusCode, State, Reason, PartialReq, #{<<"connection">> => <<"close">>}),
+ terminate(State, Reason).
+
+early_error(StatusCode, State, Reason, PartialReq) ->
+ early_error(StatusCode, State, Reason, PartialReq, #{}).
+
+early_error(StatusCode0, #state{socket=Socket, transport=Transport,
+ opts=Opts, in_streamid=StreamID}, Reason, PartialReq, RespHeaders0) ->
+ RespHeaders1 = RespHeaders0#{<<"content-length">> => <<"0">>},
+ Resp = {response, StatusCode0, RespHeaders1, <<>>},
+ try cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of
+ {response, StatusCode, RespHeaders, RespBody} ->
+ Transport:send(Socket, [
+ cow_http:response(StatusCode, 'HTTP/1.1', maps:to_list(RespHeaders)),
+ %% @todo We shouldn't send the body when the method is HEAD.
+ %% @todo Technically we allow the sendfile tuple.
+ RespBody
+ ])
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(early_error,
+ [StreamID, Reason, PartialReq, Resp, Opts],
+ Class, Exception, Stacktrace), Opts),
+ %% We still need to send an error response, so send what we initially
+ %% wanted to send. It's better than nothing.
+ Transport:send(Socket, cow_http:response(StatusCode0,
+ 'HTTP/1.1', maps:to_list(RespHeaders1)))
+ end,
+ ok.
+
+initiate_closing(State=#state{streams=[]}, Reason) ->
+ terminate(State, Reason);
+initiate_closing(State=#state{streams=[_Stream|Streams],
+ out_streamid=OutStreamID}, Reason) ->
+ terminate_all_streams(State, Streams, Reason),
+ State#state{last_streamid=OutStreamID}.
+
+-spec terminate(_, _) -> no_return().
+terminate(undefined, Reason) ->
+ exit({shutdown, Reason});
+terminate(State=#state{streams=Streams, children=Children}, Reason) ->
+ terminate_all_streams(State, Streams, Reason),
+ cowboy_children:terminate(Children),
+ terminate_linger(State),
+ exit({shutdown, Reason}).
+
+terminate_all_streams(_, [], _) ->
+ ok;
+terminate_all_streams(State, [#stream{id=StreamID, state=StreamState}|Tail], Reason) ->
+ stream_call_terminate(StreamID, Reason, StreamState, State),
+ terminate_all_streams(State, Tail, Reason).
+
+terminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ case Transport:shutdown(Socket, write) of
+ ok ->
+ case maps:get(linger_timeout, Opts, 1000) of
+ 0 ->
+ ok;
+ infinity ->
+ terminate_linger_before_loop(State, undefined, Transport:messages());
+ Timeout ->
+ TimerRef = erlang:start_timer(Timeout, self(), linger_timeout),
+ terminate_linger_before_loop(State, TimerRef, Transport:messages())
+ end;
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_before_loop(State, TimerRef, Messages) ->
+ %% We may already be in active mode when we do this
+ %% but it's OK because we are shutting down anyway.
+ case setopts_active(State) of
+ ok ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) ->
+ receive
+ {OK, Socket, _} when OK =:= element(1, Messages) ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ ok;
+ {Error, Socket, _} when Error =:= element(3, Messages) ->
+ ok;
+ {Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ terminate_linger_before_loop(State, TimerRef, Messages);
+ {timeout, TimerRef, linger_timeout} ->
+ ok;
+ _ ->
+ terminate_linger_loop(State, TimerRef, Messages)
+ end.
+
+%% System callbacks.
+
+-spec system_continue(_, _, #state{}) -> ok.
+system_continue(_, _, State) ->
+ loop(State).
+
+-spec system_terminate(any(), _, _, #state{}) -> no_return().
+system_terminate(Reason0, _, _, State) ->
+ Reason = {stop, {exit, Reason0}, 'sys:terminate/2,3 was called.'},
+ loop(initiate_closing(State, Reason)).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_http2.erl b/server/_build/default/lib/cowboy/src/cowboy_http2.erl
new file mode 100644
index 0000000..7440d91
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_http2.erl
@@ -0,0 +1,1225 @@
+%% Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_http2).
+
+-export([init/6]).
+-export([init/10]).
+-export([init/12]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type opts() :: #{
+ active_n => pos_integer(),
+ compress_buffering => boolean(),
+ compress_threshold => non_neg_integer(),
+ connection_type => worker | supervisor,
+ connection_window_margin_size => 0..16#7fffffff,
+ connection_window_update_threshold => 0..16#7fffffff,
+ enable_connect_protocol => boolean(),
+ env => cowboy_middleware:env(),
+ goaway_initial_timeout => timeout(),
+ goaway_complete_timeout => timeout(),
+ idle_timeout => timeout(),
+ inactivity_timeout => timeout(),
+ initial_connection_window_size => 65535..16#7fffffff,
+ initial_stream_window_size => 0..16#7fffffff,
+ linger_timeout => timeout(),
+ logger => module(),
+ max_concurrent_streams => non_neg_integer() | infinity,
+ max_connection_buffer_size => non_neg_integer(),
+ max_connection_window_size => 0..16#7fffffff,
+ max_decode_table_size => non_neg_integer(),
+ max_encode_table_size => non_neg_integer(),
+ max_frame_size_received => 16384..16777215,
+ max_frame_size_sent => 16384..16777215 | infinity,
+ max_received_frame_rate => {pos_integer(), timeout()},
+ max_reset_stream_rate => {pos_integer(), timeout()},
+ max_stream_buffer_size => non_neg_integer(),
+ max_stream_window_size => 0..16#7fffffff,
+ metrics_callback => cowboy_metrics_h:metrics_callback(),
+ metrics_req_filter => fun((cowboy_req:req()) -> map()),
+ metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
+ middlewares => [module()],
+ preface_timeout => timeout(),
+ proxy_header => boolean(),
+ sendfile => boolean(),
+ settings_timeout => timeout(),
+ shutdown_timeout => timeout(),
+ stream_handlers => [module()],
+ stream_window_data_threshold => 0..16#7fffffff,
+ stream_window_margin_size => 0..16#7fffffff,
+ stream_window_update_threshold => 0..16#7fffffff,
+ tracer_callback => cowboy_tracer_h:tracer_callback(),
+ tracer_flags => [atom()],
+ tracer_match_specs => cowboy_tracer_h:tracer_match_specs(),
+ %% Open ended because configured stream handlers might add options.
+ _ => _
+}.
+-export_type([opts/0]).
+
+-record(stream, {
+ %% Whether the stream is currently stopping.
+ status = running :: running | stopping,
+
+ %% Flow requested for this stream.
+ flow = 0 :: non_neg_integer(),
+
+ %% Stream state.
+ state :: {module, any()}
+}).
+
+-record(state, {
+ parent = undefined :: pid(),
+ ref :: ranch:ref(),
+ socket = undefined :: inet:socket(),
+ transport :: module(),
+ proxy_header :: undefined | ranch_proxy_header:proxy_info(),
+ opts = #{} :: opts(),
+
+ %% Timer for idle_timeout; also used for goaway timers.
+ timer = undefined :: undefined | reference(),
+
+ %% Remote address and port for the connection.
+ peer = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Local address and port for the connection.
+ sock = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Client certificate (TLS only).
+ cert :: undefined | binary(),
+
+ %% HTTP/2 state machine.
+ http2_status :: sequence | settings | upgrade | connected | closing_initiated | closing,
+ http2_machine :: cow_http2_machine:http2_machine(),
+
+ %% HTTP/2 frame rate flood protection.
+ frame_rate_num :: undefined | pos_integer(),
+ frame_rate_time :: undefined | integer(),
+
+ %% HTTP/2 reset stream flood protection.
+ reset_rate_num :: undefined | pos_integer(),
+ reset_rate_time :: undefined | integer(),
+
+ %% Flow requested for all streams.
+ flow = 0 :: non_neg_integer(),
+
+ %% Currently active HTTP/2 streams. Streams may be initiated either
+ %% by the client or by the server through PUSH_PROMISE frames.
+ streams = #{} :: #{cow_http2:streamid() => #stream{}},
+
+ %% Streams can spawn zero or more children which are then managed
+ %% by this module if operating as a supervisor.
+ children = cowboy_children:init() :: cowboy_children:children()
+}).
+
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info() | undefined, cowboy:opts()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->
+ Peer0 = Transport:peername(Socket),
+ Sock0 = Transport:sockname(Socket),
+ Cert1 = case Transport:name() of
+ ssl ->
+ case ssl:peercert(Socket) of
+ {error, no_peercert} ->
+ {ok, undefined};
+ Cert0 ->
+ Cert0
+ end;
+ _ ->
+ {ok, undefined}
+ end,
+ case {Peer0, Sock0, Cert1} of
+ {{ok, Peer}, {ok, Sock}, {ok, Cert}} ->
+ init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, <<>>);
+ {{error, Reason}, _, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the peer name.'});
+ {_, {error, Reason}, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the sock name.'});
+ {_, _, {error, Reason}} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the client TLS certificate.'})
+ end.
+
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
+ {inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
+ binary() | undefined, binary()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer) ->
+ {ok, Preface, HTTP2Machine} = cow_http2_machine:init(server, Opts),
+ State = set_idle_timeout(init_rate_limiting(#state{parent=Parent, ref=Ref, socket=Socket,
+ transport=Transport, proxy_header=ProxyHeader,
+ opts=Opts, peer=Peer, sock=Sock, cert=Cert,
+ http2_status=sequence, http2_machine=HTTP2Machine})),
+ Transport:send(Socket, Preface),
+ setopts_active(State),
+ case Buffer of
+ <<>> -> loop(State, Buffer);
+ _ -> parse(State, Buffer)
+ end.
+
+init_rate_limiting(State) ->
+ CurrentTime = erlang:monotonic_time(millisecond),
+ init_reset_rate_limiting(init_frame_rate_limiting(State, CurrentTime), CurrentTime).
+
+init_frame_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
+ {FrameRateNum, FrameRatePeriod} = maps:get(max_received_frame_rate, Opts, {10000, 10000}),
+ State#state{
+ frame_rate_num=FrameRateNum, frame_rate_time=add_period(CurrentTime, FrameRatePeriod)
+ }.
+
+init_reset_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
+ {ResetRateNum, ResetRatePeriod} = maps:get(max_reset_stream_rate, Opts, {10, 10000}),
+ State#state{
+ reset_rate_num=ResetRateNum, reset_rate_time=add_period(CurrentTime, ResetRatePeriod)
+ }.
+
+add_period(_, infinity) -> infinity;
+add_period(Time, Period) -> Time + Period.
+
+%% @todo Add an argument for the request body.
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
+ {inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
+ binary() | undefined, binary(), map() | undefined, cowboy_req:req()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer,
+ _Settings, Req=#{method := Method}) ->
+ {ok, Preface, HTTP2Machine0} = cow_http2_machine:init(server, Opts),
+ {ok, StreamID, HTTP2Machine}
+ = cow_http2_machine:init_upgrade_stream(Method, HTTP2Machine0),
+ State0 = #state{parent=Parent, ref=Ref, socket=Socket,
+ transport=Transport, proxy_header=ProxyHeader,
+ opts=Opts, peer=Peer, sock=Sock, cert=Cert,
+ http2_status=upgrade, http2_machine=HTTP2Machine},
+ State1 = headers_frame(State0#state{
+ http2_machine=HTTP2Machine}, StreamID, Req),
+ %% We assume that the upgrade will be applied. A stream handler
+ %% must not prevent the normal operations of the server.
+ State2 = info(State1, 1, {switch_protocol, #{
+ <<"connection">> => <<"Upgrade">>,
+ <<"upgrade">> => <<"h2c">>
+ }, ?MODULE, undefined}), %% @todo undefined or #{}?
+ State = set_idle_timeout(init_rate_limiting(State2#state{http2_status=sequence})),
+ Transport:send(Socket, Preface),
+ setopts_active(State),
+ case Buffer of
+ <<>> -> loop(State, Buffer);
+ _ -> parse(State, Buffer)
+ end.
+
+%% Because HTTP/2 has flow control and Cowboy has other rate limiting
+%% mechanisms implemented, a very large active_n value should be fine,
+%% as long as the stream handlers do their work in a timely manner.
+setopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ N = maps:get(active_n, Opts, 100),
+ Transport:setopts(Socket, [{active, N}]).
+
+loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
+ opts=Opts, timer=TimerRef, children=Children}, Buffer) ->
+ Messages = Transport:messages(),
+ InactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),
+ receive
+ %% Socket messages.
+ {OK, Socket, Data} when OK =:= element(1, Messages) ->
+ parse(set_idle_timeout(State), << Buffer/binary, Data/binary >>);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ Reason = case State#state.http2_status of
+ closing -> {stop, closed, 'The client is going away.'};
+ _ -> {socket_error, closed, 'The socket has been closed.'}
+ end,
+ terminate(State, Reason);
+ {Error, Socket, Reason} when Error =:= element(3, Messages) ->
+ terminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ setopts_active(State),
+ loop(State, Buffer);
+ %% System messages.
+ {'EXIT', Parent, shutdown} ->
+ Reason = {stop, {exit, shutdown}, 'Parent process requested shutdown.'},
+ loop(initiate_closing(State, Reason), Buffer);
+ {'EXIT', Parent, Reason} ->
+ terminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {State, Buffer});
+ %% Timeouts.
+ {timeout, TimerRef, idle_timeout} ->
+ terminate(State, {stop, timeout,
+ 'Connection idle longer than configuration allows.'});
+ {timeout, Ref, {shutdown, Pid}} ->
+ cowboy_children:shutdown_timeout(Children, Ref, Pid),
+ loop(State, Buffer);
+ {timeout, TRef, {cow_http2_machine, Name}} ->
+ loop(timeout(State, Name, TRef), Buffer);
+ {timeout, TimerRef, {goaway_initial_timeout, Reason}} ->
+ loop(closing(State, Reason), Buffer);
+ {timeout, TimerRef, {goaway_complete_timeout, Reason}} ->
+ terminate(State, {stop, stop_reason(Reason),
+ 'Graceful shutdown timed out.'});
+ %% Messages pertaining to a stream.
+ {{Pid, StreamID}, Msg} when Pid =:= self() ->
+ loop(info(State, StreamID, Msg), Buffer);
+ %% Exit signal from children.
+ Msg = {'EXIT', Pid, _} ->
+ loop(down(State, Pid, Msg), Buffer);
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),
+ loop(State, Buffer);
+ Msg ->
+ cowboy:log(warning, "Received stray message ~p.", [Msg], Opts),
+ loop(State, Buffer)
+ after InactivityTimeout ->
+ terminate(State, {internal_error, timeout, 'No message or data received before timeout.'})
+ end.
+
+set_idle_timeout(State=#state{http2_status=Status, timer=TimerRef})
+ when Status =:= closing_initiated orelse Status =:= closing,
+ TimerRef =/= undefined ->
+ State;
+set_idle_timeout(State=#state{opts=Opts}) ->
+ set_timeout(State, maps:get(idle_timeout, Opts, 60000), idle_timeout).
+
+set_timeout(State=#state{timer=TimerRef0}, Timeout, Message) ->
+ ok = case TimerRef0 of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(TimerRef0, [{async, true}, {info, false}])
+ end,
+ TimerRef = case Timeout of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), Message)
+ end,
+ State#state{timer=TimerRef}.
+
+%% HTTP/2 protocol parsing.
+
+parse(State=#state{http2_status=sequence}, Data) ->
+ case cow_http2:parse_sequence(Data) of
+ {ok, Rest} ->
+ parse(State#state{http2_status=settings}, Rest);
+ more ->
+ loop(State, Data);
+ Error = {connection_error, _, _} ->
+ terminate(State, Error)
+ end;
+parse(State=#state{http2_status=Status, http2_machine=HTTP2Machine, streams=Streams}, Data) ->
+ MaxFrameSize = cow_http2_machine:get_local_setting(max_frame_size, HTTP2Machine),
+ case cow_http2:parse(Data, MaxFrameSize) of
+ {ok, Frame, Rest} ->
+ parse(frame_rate(State, Frame), Rest);
+ {ignore, Rest} ->
+ parse(frame_rate(State, ignore), Rest);
+ {stream_error, StreamID, Reason, Human, Rest} ->
+ parse(reset_stream(State, StreamID, {stream_error, Reason, Human}), Rest);
+ Error = {connection_error, _, _} ->
+ terminate(State, Error);
+ %% Terminate the connection if we are closing and all streams have completed.
+ more when Status =:= closing, Streams =:= #{} ->
+ terminate(State, {stop, normal, 'The connection is going away.'});
+ more ->
+ loop(State, Data)
+ end.
+
+%% Frame rate flood protection.
+
+frame_rate(State0=#state{frame_rate_num=Num0, frame_rate_time=Time}, Frame) ->
+ {Result, State} = case Num0 - 1 of
+ 0 ->
+ CurrentTime = erlang:monotonic_time(millisecond),
+ if
+ CurrentTime < Time ->
+ {error, State0};
+ true ->
+ %% When the option has a period of infinity we cannot reach this clause.
+ {ok, init_frame_rate_limiting(State0, CurrentTime)}
+ end;
+ Num ->
+ {ok, State0#state{frame_rate_num=Num}}
+ end,
+ case {Result, Frame} of
+ {ok, ignore} -> ignored_frame(State);
+ {ok, _} -> frame(State, Frame);
+ {error, _} -> terminate(State, {connection_error, enhance_your_calm,
+ 'Frame rate larger than configuration allows. Flood? (CVE-2019-9512, CVE-2019-9515, CVE-2019-9518)'})
+ end.
+
+%% Frames received.
+
+%% We do nothing when receiving a lingering DATA frame.
+%% We already removed the stream flow from the connection
+%% flow and are therefore already accounting for the window
+%% being reduced by these frames.
+frame(State=#state{http2_machine=HTTP2Machine0}, Frame) ->
+ case cow_http2_machine:frame(Frame, HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame);
+ {ok, {data, StreamID, IsFin, Data}, HTTP2Machine} ->
+ data_frame(State#state{http2_machine=HTTP2Machine}, StreamID, IsFin, Data);
+ {ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, BodyLen}, HTTP2Machine} ->
+ headers_frame(State#state{http2_machine=HTTP2Machine},
+ StreamID, IsFin, Headers, PseudoHeaders, BodyLen);
+ {ok, {trailers, _StreamID, _Trailers}, HTTP2Machine} ->
+ %% @todo Propagate trailers.
+ State#state{http2_machine=HTTP2Machine};
+ {ok, {rst_stream, StreamID, Reason}, HTTP2Machine} ->
+ rst_stream_frame(State#state{http2_machine=HTTP2Machine}, StreamID, Reason);
+ {ok, GoAway={goaway, _, _, _}, HTTP2Machine} ->
+ goaway(State#state{http2_machine=HTTP2Machine}, GoAway);
+ {send, SendData, HTTP2Machine} ->
+ %% We may need to send an alarm for each of the streams sending data.
+ lists:foldl(
+ fun({StreamID, _, _}, S) -> maybe_send_data_alarm(S, HTTP2Machine0, StreamID) end,
+ send_data(maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame), SendData, []),
+ SendData);
+ {error, {stream_error, StreamID, Reason, Human}, HTTP2Machine} ->
+ reset_stream(State#state{http2_machine=HTTP2Machine},
+ StreamID, {stream_error, Reason, Human});
+ {error, Error={connection_error, _, _}, HTTP2Machine} ->
+ terminate(State#state{http2_machine=HTTP2Machine}, Error)
+ end.
+
+%% We use this opportunity to mark the HTTP/2 status as connected
+%% if we were still waiting for a SETTINGS frame.
+maybe_ack(State=#state{http2_status=settings}, Frame) ->
+ maybe_ack(State#state{http2_status=connected}, Frame);
+maybe_ack(State=#state{socket=Socket, transport=Transport}, Frame) ->
+ case Frame of
+ {settings, _} -> Transport:send(Socket, cow_http2:settings_ack());
+ {ping, Opaque} -> Transport:send(Socket, cow_http2:ping_ack(Opaque));
+ _ -> ok
+ end,
+ State.
+
+data_frame(State0=#state{opts=Opts, flow=Flow, streams=Streams}, StreamID, IsFin, Data) ->
+ case Streams of
+ #{StreamID := Stream=#stream{status=running, flow=StreamFlow, state=StreamState0}} ->
+ try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
+ {Commands, StreamState} ->
+ %% Remove the amount of data received from the flow.
+ %% We may receive more data than we requested. We ensure
+ %% that the flow value doesn't go lower than 0.
+ Size = byte_size(Data),
+ State = update_window(State0#state{flow=max(0, Flow - Size),
+ streams=Streams#{StreamID => Stream#stream{
+ flow=max(0, StreamFlow - Size), state=StreamState}}},
+ StreamID),
+ commands(State, StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(data,
+ [StreamID, IsFin, Data, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ reset_stream(State0, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:data/4.'})
+ end;
+ %% We ignore DATA frames for streams that are stopping.
+ #{} ->
+ State0
+ end.
+
+headers_frame(State, StreamID, IsFin, Headers,
+ PseudoHeaders=#{method := <<"CONNECT">>}, _)
+ when map_size(PseudoHeaders) =:= 2 ->
+ early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
+ 'The CONNECT method is currently not implemented. (RFC7231 4.3.6)');
+headers_frame(State, StreamID, IsFin, Headers,
+ PseudoHeaders=#{method := <<"TRACE">>}, _) ->
+ early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
+ 'The TRACE method is currently not implemented. (RFC7231 4.3.8)');
+headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) ->
+ headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
+headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen) ->
+ case lists:keyfind(<<"host">>, 1, Headers) of
+ {_, Authority} ->
+ headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
+ _ ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'})
+ end.
+
+headers_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert, proxy_header=ProxyHeader},
+ StreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs},
+ BodyLen, Authority) ->
+ try cow_http_hd:parse_host(Authority) of
+ {Host, Port0} ->
+ Port = ensure_port(Scheme, Port0),
+ try cow_http:parse_fullpath(PathWithQs) of
+ {<<>>, _} ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'The path component must not be empty. (RFC7540 8.1.2.3)'});
+ {Path, Qs} ->
+ Req0 = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ peer => Peer,
+ sock => Sock,
+ cert => Cert,
+ method => Method,
+ scheme => Scheme,
+ host => Host,
+ port => Port,
+ path => Path,
+ qs => Qs,
+ version => 'HTTP/2',
+ headers => headers_to_map(Headers, #{}),
+ has_body => IsFin =:= nofin,
+ body_length => BodyLen
+ },
+ %% We add the PROXY header information if any.
+ Req1 = case ProxyHeader of
+ undefined -> Req0;
+ _ -> Req0#{proxy_header => ProxyHeader}
+ end,
+ %% We add the protocol information for extended CONNECTs.
+ Req = case PseudoHeaders of
+ #{protocol := Protocol} -> Req1#{protocol => Protocol};
+ _ -> Req1
+ end,
+ headers_frame(State, StreamID, Req)
+ catch _:_ ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'The :path pseudo-header is invalid. (RFC7540 8.1.2.3)'})
+ end
+ catch _:_ ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'The :authority pseudo-header is invalid. (RFC7540 8.1.2.3)'})
+ end.
+
+ensure_port(<<"http">>, undefined) -> 80;
+ensure_port(<<"https">>, undefined) -> 443;
+ensure_port(_, Port) -> Port.
+
+%% This function is necessary to properly handle duplicate headers
+%% and the special-case cookie header.
+headers_to_map([], Acc) ->
+ Acc;
+headers_to_map([{Name, Value}|Tail], Acc0) ->
+ Acc = case Acc0 of
+ %% The cookie header does not use proper HTTP header lists.
+ #{Name := Value0} when Name =:= <<"cookie">> ->
+ Acc0#{Name => << Value0/binary, "; ", Value/binary >>};
+ #{Name := Value0} ->
+ Acc0#{Name => << Value0/binary, ", ", Value/binary >>};
+ _ ->
+ Acc0#{Name => Value}
+ end,
+ headers_to_map(Tail, Acc).
+
+headers_frame(State=#state{opts=Opts, streams=Streams}, StreamID, Req) ->
+ try cowboy_stream:init(StreamID, Req, Opts) of
+ {Commands, StreamState} ->
+ commands(State#state{
+ streams=Streams#{StreamID => #stream{state=StreamState}}},
+ StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(init,
+ [StreamID, Req, Opts],
+ Class, Exception, Stacktrace), Opts),
+ reset_stream(State, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:init/3.'})
+ end.
+
+early_error(State0=#state{ref=Ref, opts=Opts, peer=Peer},
+ StreamID, _IsFin, Headers, #{method := Method},
+ StatusCode0, HumanReadable) ->
+ %% We automatically terminate the stream but it is not an error
+ %% per se (at least not in the first implementation).
+ Reason = {stream_error, no_error, HumanReadable},
+ %% The partial Req is minimal for now. We only have one case
+ %% where it can be called (when a method is completely disabled).
+ %% @todo Fill in the other elements.
+ PartialReq = #{
+ ref => Ref,
+ peer => Peer,
+ method => Method,
+ headers => headers_to_map(Headers, #{})
+ },
+ Resp = {response, StatusCode0, RespHeaders0=#{<<"content-length">> => <<"0">>}, <<>>},
+ try cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of
+ {response, StatusCode, RespHeaders, RespBody} ->
+ send_response(State0, StreamID, StatusCode, RespHeaders, RespBody)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(early_error,
+ [StreamID, Reason, PartialReq, Resp, Opts],
+ Class, Exception, Stacktrace), Opts),
+ %% We still need to send an error response, so send what we initially
+ %% wanted to send. It's better than nothing.
+ send_headers(State0, StreamID, fin, StatusCode0, RespHeaders0)
+ end.
+
+rst_stream_frame(State=#state{streams=Streams0, children=Children0}, StreamID, Reason) ->
+ case maps:take(StreamID, Streams0) of
+ {#stream{state=StreamState}, Streams} ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ Children = cowboy_children:shutdown(Children0, StreamID),
+ State#state{streams=Streams, children=Children};
+ error ->
+ State
+ end.
+
+ignored_frame(State=#state{http2_machine=HTTP2Machine0}) ->
+ case cow_http2_machine:ignored_frame(HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ State#state{http2_machine=HTTP2Machine};
+ {error, Error={connection_error, _, _}, HTTP2Machine} ->
+ terminate(State#state{http2_machine=HTTP2Machine}, Error)
+ end.
+
+%% HTTP/2 timeouts.
+
+timeout(State=#state{http2_machine=HTTP2Machine0}, Name, TRef) ->
+ case cow_http2_machine:timeout(Name, TRef, HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ State#state{http2_machine=HTTP2Machine};
+ {error, Error={connection_error, _, _}, HTTP2Machine} ->
+ terminate(State#state{http2_machine=HTTP2Machine}, Error)
+ end.
+
+%% Erlang messages.
+
+down(State0=#state{opts=Opts, children=Children0}, Pid, Msg) ->
+ State = case cowboy_children:down(Children0, Pid) of
+ %% The stream was terminated already.
+ {ok, undefined, Children} ->
+ State0#state{children=Children};
+ %% The stream is still running.
+ {ok, StreamID, Children} ->
+ info(State0#state{children=Children}, StreamID, Msg);
+ %% The process was unknown.
+ error ->
+ cowboy:log(warning, "Received EXIT signal ~p for unknown process ~p.~n",
+ [Msg, Pid], Opts),
+ State0
+ end,
+ if
+ State#state.http2_status =:= closing, State#state.streams =:= #{} ->
+ terminate(State, {stop, normal, 'The connection is going away.'});
+ true ->
+ State
+ end.
+
+info(State=#state{opts=Opts, http2_machine=HTTP2Machine, streams=Streams}, StreamID, Msg) ->
+ case Streams of
+ #{StreamID := Stream=#stream{state=StreamState0}} ->
+ try cowboy_stream:info(StreamID, Msg, StreamState0) of
+ {Commands, StreamState} ->
+ commands(State#state{streams=Streams#{StreamID => Stream#stream{state=StreamState}}},
+ StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(info,
+ [StreamID, Msg, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ reset_stream(State, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:info/3.'})
+ end;
+ _ ->
+ case cow_http2_machine:is_lingering_stream(StreamID, HTTP2Machine) of
+ true ->
+ ok;
+ false ->
+ cowboy:log(warning, "Received message ~p for unknown stream ~p.",
+ [Msg, StreamID], Opts)
+ end,
+ State
+ end.
+
+%% Stream handler commands.
+%%
+%% @todo Kill the stream if it tries to send a response, headers,
+%% data or push promise when the stream is closed or half-closed.
+
+commands(State, _, []) ->
+ State;
+%% Error responses are sent only if a response wasn't sent already.
+commands(State=#state{http2_machine=HTTP2Machine}, StreamID,
+ [{error_response, StatusCode, Headers, Body}|Tail]) ->
+ case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
+ {ok, idle, _} ->
+ commands(State, StreamID, [{response, StatusCode, Headers, Body}|Tail]);
+ _ ->
+ commands(State, StreamID, Tail)
+ end;
+%% Send an informational response.
+commands(State0, StreamID, [{inform, StatusCode, Headers}|Tail]) ->
+ State = send_headers(State0, StreamID, idle, StatusCode, Headers),
+ commands(State, StreamID, Tail);
+%% Send response headers.
+commands(State0, StreamID, [{response, StatusCode, Headers, Body}|Tail]) ->
+ State = send_response(State0, StreamID, StatusCode, Headers, Body),
+ commands(State, StreamID, Tail);
+%% Send response headers.
+commands(State0, StreamID, [{headers, StatusCode, Headers}|Tail]) ->
+ State = send_headers(State0, StreamID, nofin, StatusCode, Headers),
+ commands(State, StreamID, Tail);
+%% Send a response body chunk.
+commands(State0, StreamID, [{data, IsFin, Data}|Tail]) ->
+ State = maybe_send_data(State0, StreamID, IsFin, Data, []),
+ commands(State, StreamID, Tail);
+%% Send trailers.
+commands(State0, StreamID, [{trailers, Trailers}|Tail]) ->
+ State = maybe_send_data(State0, StreamID, fin, {trailers, maps:to_list(Trailers)}, []),
+ commands(State, StreamID, Tail);
+%% Send a push promise.
+%%
+%% @todo Responses sent as a result of a push_promise request
+%% must not send push_promise frames themselves.
+%%
+%% @todo We should not send push_promise frames when we are
+%% in the closing http2_status.
+commands(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0},
+ StreamID, [{push, Method, Scheme, Host, Port, Path, Qs, Headers0}|Tail]) ->
+ Authority = case {Scheme, Port} of
+ {<<"http">>, 80} -> Host;
+ {<<"https">>, 443} -> Host;
+ _ -> iolist_to_binary([Host, $:, integer_to_binary(Port)])
+ end,
+ PathWithQs = iolist_to_binary(case Qs of
+ <<>> -> Path;
+ _ -> [Path, $?, Qs]
+ end),
+ PseudoHeaders = #{
+ method => Method,
+ scheme => Scheme,
+ authority => Authority,
+ path => PathWithQs
+ },
+ %% We need to make sure the header value is binary before we can
+ %% create the Req object, as it expects them to be flat.
+ Headers = maps:to_list(maps:map(fun(_, V) -> iolist_to_binary(V) end, Headers0)),
+ State = case cow_http2_machine:prepare_push_promise(StreamID, HTTP2Machine0,
+ PseudoHeaders, Headers) of
+ {ok, PromisedStreamID, HeaderBlock, HTTP2Machine} ->
+ Transport:send(Socket, cow_http2:push_promise(
+ StreamID, PromisedStreamID, HeaderBlock)),
+ headers_frame(State0#state{http2_machine=HTTP2Machine},
+ PromisedStreamID, fin, Headers, PseudoHeaders, 0);
+ {error, no_push} ->
+ State0
+ end,
+ commands(State, StreamID, Tail);
+%% Read the request body.
+commands(State0=#state{flow=Flow, streams=Streams}, StreamID, [{flow, Size}|Tail]) ->
+ #{StreamID := Stream=#stream{flow=StreamFlow}} = Streams,
+ State = update_window(State0#state{flow=Flow + Size,
+ streams=Streams#{StreamID => Stream#stream{flow=StreamFlow + Size}}},
+ StreamID),
+ commands(State, StreamID, Tail);
+%% Supervise a child process.
+commands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->
+ commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},
+ StreamID, Tail);
+%% Error handling.
+commands(State, StreamID, [Error = {internal_error, _, _}|_Tail]) ->
+ %% @todo Do we want to run the commands after an internal_error?
+ %% @todo Do we even allow commands after?
+ %% @todo Only reset when the stream still exists.
+ reset_stream(State, StreamID, Error);
+%% Upgrade to HTTP/2. This is triggered by cowboy_http2 itself.
+commands(State=#state{socket=Socket, transport=Transport, http2_status=upgrade},
+ StreamID, [{switch_protocol, Headers, ?MODULE, _}|Tail]) ->
+ %% @todo This 101 response needs to be passed through stream handlers.
+ Transport:send(Socket, cow_http:response(101, 'HTTP/1.1', maps:to_list(Headers))),
+ commands(State, StreamID, Tail);
+%% Use a different protocol within the stream (CONNECT :protocol).
+%% @todo Make sure we error out when the feature is disabled.
+commands(State0, StreamID, [{switch_protocol, Headers, _Mod, _ModState}|Tail]) ->
+ State = info(State0, StreamID, {headers, 200, Headers}),
+ commands(State, StreamID, Tail);
+%% Set options dynamically.
+commands(State, StreamID, [{set_options, _Opts}|Tail]) ->
+ commands(State, StreamID, Tail);
+commands(State, StreamID, [stop|_Tail]) ->
+ %% @todo Do we want to run the commands after a stop?
+ %% @todo Do we even allow commands after?
+ stop_stream(State, StreamID);
+%% Log event.
+commands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->
+ cowboy:log(Log, Opts),
+ commands(State, StreamID, Tail).
+
+%% Tentatively update the window after the flow was updated.
+
+update_window(State=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0, flow=Flow, streams=Streams}, StreamID) ->
+ #{StreamID := #stream{flow=StreamFlow}} = Streams,
+ {Data1, HTTP2Machine2} = case cow_http2_machine:ensure_window(Flow, HTTP2Machine0) of
+ ok -> {<<>>, HTTP2Machine0};
+ {ok, Increment1, HTTP2Machine1} -> {cow_http2:window_update(Increment1), HTTP2Machine1}
+ end,
+ {Data2, HTTP2Machine} = case cow_http2_machine:ensure_window(StreamID, StreamFlow, HTTP2Machine2) of
+ ok -> {<<>>, HTTP2Machine2};
+ {ok, Increment2, HTTP2Machine3} -> {cow_http2:window_update(StreamID, Increment2), HTTP2Machine3}
+ end,
+ case {Data1, Data2} of
+ {<<>>, <<>>} -> ok;
+ _ -> Transport:send(Socket, [Data1, Data2])
+ end,
+ State#state{http2_machine=HTTP2Machine}.
+
+%% Send the response, trailers or data.
+
+send_response(State0=#state{http2_machine=HTTP2Machine0}, StreamID, StatusCode, Headers, Body) ->
+ Size = case Body of
+ {sendfile, _, Bytes, _} -> Bytes;
+ _ -> iolist_size(Body)
+ end,
+ case Size of
+ 0 ->
+ State = send_headers(State0, StreamID, fin, StatusCode, Headers),
+ maybe_terminate_stream(State, StreamID, fin);
+ _ ->
+ %% @todo Add a test for HEAD to make sure we don't send the body when
+ %% returning {response...} from a stream handler (or {headers...} then {data...}).
+ {ok, _IsFin, HeaderBlock, HTTP2Machine}
+ = cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, nofin,
+ #{status => cow_http:status_to_integer(StatusCode)},
+ headers_to_list(Headers)),
+ maybe_send_data(State0#state{http2_machine=HTTP2Machine}, StreamID, fin, Body,
+ [cow_http2:headers(StreamID, nofin, HeaderBlock)])
+ end.
+
+send_headers(State=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID, IsFin0, StatusCode, Headers) ->
+ {ok, IsFin, HeaderBlock, HTTP2Machine}
+ = cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, IsFin0,
+ #{status => cow_http:status_to_integer(StatusCode)},
+ headers_to_list(Headers)),
+ Transport:send(Socket, cow_http2:headers(StreamID, IsFin, HeaderBlock)),
+ State#state{http2_machine=HTTP2Machine}.
+
+%% The set-cookie header is special; we can only send one cookie per header.
+headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
+ Headers = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
+ Headers ++ [{<<"set-cookie">>, Value} || Value <- SetCookies];
+headers_to_list(Headers) ->
+ maps:to_list(Headers).
+
+maybe_send_data(State0=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID, IsFin, Data0, Prefix) ->
+ Data = case is_tuple(Data0) of
+ false -> {data, Data0};
+ true -> Data0
+ end,
+ case cow_http2_machine:send_or_queue_data(StreamID, HTTP2Machine0, IsFin, Data) of
+ {ok, HTTP2Machine} ->
+ %% If we have prefix data (like a HEADERS frame) we need to send it
+ %% even if we do not send any DATA frames.
+ case Prefix of
+ [] -> ok;
+ _ -> Transport:send(Socket, Prefix)
+ end,
+ maybe_send_data_alarm(State0#state{http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID);
+ {send, SendData, HTTP2Machine} ->
+ State = #state{http2_status=Status, streams=Streams}
+ = send_data(State0#state{http2_machine=HTTP2Machine}, SendData, Prefix),
+ %% Terminate the connection if we are closing and all streams have completed.
+ if
+ Status =:= closing, Streams =:= #{} ->
+ terminate(State, {stop, normal, 'The connection is going away.'});
+ true ->
+ maybe_send_data_alarm(State, HTTP2Machine0, StreamID)
+ end
+ end.
+
+send_data(State0=#state{socket=Socket, transport=Transport, opts=Opts}, SendData, Prefix) ->
+ {Acc, State} = prepare_data(State0, SendData, [], Prefix),
+ _ = [case Data of
+ {sendfile, Offset, Bytes, Path} ->
+ %% When sendfile is disabled we explicitly use the fallback.
+ _ = case maps:get(sendfile, Opts, true) of
+ true -> Transport:sendfile(Socket, Path, Offset, Bytes);
+ false -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])
+ end;
+ _ ->
+ Transport:send(Socket, Data)
+ end || Data <- Acc],
+ send_data_terminate(State, SendData).
+
+send_data_terminate(State, []) ->
+ State;
+send_data_terminate(State0, [{StreamID, IsFin, _}|Tail]) ->
+ State = maybe_terminate_stream(State0, StreamID, IsFin),
+ send_data_terminate(State, Tail).
+
+prepare_data(State, [], Acc, []) ->
+ {lists:reverse(Acc), State};
+prepare_data(State, [], Acc, Buffer) ->
+ {lists:reverse([lists:reverse(Buffer)|Acc]), State};
+prepare_data(State0, [{StreamID, IsFin, SendData}|Tail], Acc0, Buffer0) ->
+ {Acc, Buffer, State} = prepare_data(State0, StreamID, IsFin, SendData, Acc0, Buffer0),
+ prepare_data(State, Tail, Acc, Buffer).
+
+prepare_data(State, _, _, [], Acc, Buffer) ->
+ {Acc, Buffer, State};
+prepare_data(State0, StreamID, IsFin, [FrameData|Tail], Acc, Buffer) ->
+ FrameIsFin = case Tail of
+ [] -> IsFin;
+ _ -> nofin
+ end,
+ case prepare_data_frame(State0, StreamID, FrameIsFin, FrameData) of
+ {{MoreData, Sendfile}, State} when is_tuple(Sendfile) ->
+ case Buffer of
+ [] ->
+ prepare_data(State, StreamID, IsFin, Tail,
+ [Sendfile, MoreData|Acc], []);
+ _ ->
+ prepare_data(State, StreamID, IsFin, Tail,
+ [Sendfile, lists:reverse([MoreData|Buffer])|Acc], [])
+ end;
+ {MoreData, State} ->
+ prepare_data(State, StreamID, IsFin, Tail,
+ Acc, [MoreData|Buffer])
+ end.
+
+prepare_data_frame(State, StreamID, IsFin, {data, Data}) ->
+ {cow_http2:data(StreamID, IsFin, Data),
+ State};
+prepare_data_frame(State, StreamID, IsFin, Sendfile={sendfile, _, Bytes, _}) ->
+ {{cow_http2:data_header(StreamID, IsFin, Bytes), Sendfile},
+ State};
+%% The stream is terminated in cow_http2_machine:prepare_trailers.
+prepare_data_frame(State=#state{http2_machine=HTTP2Machine0},
+ StreamID, nofin, {trailers, Trailers}) ->
+ {ok, HeaderBlock, HTTP2Machine}
+ = cow_http2_machine:prepare_trailers(StreamID, HTTP2Machine0, Trailers),
+ {cow_http2:headers(StreamID, fin, HeaderBlock),
+ State#state{http2_machine=HTTP2Machine}}.
+
+%% After we have sent or queued data we may need to set or clear an alarm.
+%% We do this by comparing the HTTP2Machine buffer state before/after for
+%% the relevant streams.
+maybe_send_data_alarm(State=#state{opts=Opts, http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID) ->
+ ConnBufferSizeBefore = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine0),
+ ConnBufferSizeAfter = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine),
+ {ok, StreamBufferSizeBefore} = cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine0),
+ %% When the stream ends up closed after it finished sending data,
+ %% we do not want to trigger an alarm. We act as if the buffer
+ %% size did not change.
+ StreamBufferSizeAfter = case cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine) of
+ {ok, BSA} -> BSA;
+ {error, closed} -> StreamBufferSizeBefore
+ end,
+ MaxConnBufferSize = maps:get(max_connection_buffer_size, Opts, 16000000),
+ MaxStreamBufferSize = maps:get(max_stream_buffer_size, Opts, 8000000),
+ %% I do not want to document these internal events yet. I am not yet
+ %% convinced it should be {alarm, Name, on|off} and not {internal_event, E}
+ %% or something else entirely. Though alarms are probably right.
+ if
+ ConnBufferSizeBefore >= MaxConnBufferSize, ConnBufferSizeAfter < MaxConnBufferSize ->
+ connection_alarm(State, connection_buffer_full, off);
+ ConnBufferSizeBefore < MaxConnBufferSize, ConnBufferSizeAfter >= MaxConnBufferSize ->
+ connection_alarm(State, connection_buffer_full, on);
+ StreamBufferSizeBefore >= MaxStreamBufferSize, StreamBufferSizeAfter < MaxStreamBufferSize ->
+ stream_alarm(State, StreamID, stream_buffer_full, off);
+ StreamBufferSizeBefore < MaxStreamBufferSize, StreamBufferSizeAfter >= MaxStreamBufferSize ->
+ stream_alarm(State, StreamID, stream_buffer_full, on);
+ true ->
+ State
+ end.
+
+connection_alarm(State0=#state{streams=Streams}, Name, Value) ->
+ lists:foldl(fun(StreamID, State) ->
+ stream_alarm(State, StreamID, Name, Value)
+ end, State0, maps:keys(Streams)).
+
+stream_alarm(State, StreamID, Name, Value) ->
+ info(State, StreamID, {alarm, Name, Value}).
+
+%% Terminate a stream or the connection.
+
+%% We may have to cancel streams even if we receive multiple
+%% GOAWAY frames as the LastStreamID value may be lower than
+%% the one previously received.
+goaway(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0,
+ http2_status=Status, streams=Streams0}, {goaway, LastStreamID, Reason, _})
+ when Status =:= connected; Status =:= closing_initiated; Status =:= closing ->
+ Streams = goaway_streams(State0, maps:to_list(Streams0), LastStreamID,
+ {stop, {goaway, Reason}, 'The connection is going away.'}, []),
+ State = State0#state{streams=maps:from_list(Streams)},
+ if
+ Status =:= connected; Status =:= closing_initiated ->
+ {OurLastStreamID, HTTP2Machine} =
+ cow_http2_machine:set_last_streamid(HTTP2Machine0),
+ Transport:send(Socket, cow_http2:goaway(
+ OurLastStreamID, no_error, <<>>)),
+ State#state{http2_status=closing,
+ http2_machine=HTTP2Machine};
+ true ->
+ State
+ end;
+%% We terminate the connection immediately if it hasn't fully been initialized.
+goaway(State, {goaway, _, Reason, _}) ->
+ terminate(State, {stop, {goaway, Reason}, 'The connection is going away.'}).
+
+%% Cancel client-initiated streams that are above LastStreamID.
+goaway_streams(_, [], _, _, Acc) ->
+ Acc;
+goaway_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], LastStreamID, Reason, Acc)
+ when StreamID > LastStreamID, (StreamID rem 2) =:= 0 ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ goaway_streams(State, Tail, LastStreamID, Reason, Acc);
+goaway_streams(State, [Stream|Tail], LastStreamID, Reason, Acc) ->
+ goaway_streams(State, Tail, LastStreamID, Reason, [Stream|Acc]).
+
+%% A server that is attempting to gracefully shut down a connection SHOULD send
+%% an initial GOAWAY frame with the last stream identifier set to 2^31-1 and a
+%% NO_ERROR code. This signals to the client that a shutdown is imminent and
+%% that initiating further requests is prohibited. After allowing time for any
+%% in-flight stream creation (at least one round-trip time), the server can send
+%% another GOAWAY frame with an updated last stream identifier. This ensures
+%% that a connection can be cleanly shut down without losing requests.
+-spec initiate_closing(#state{}, _) -> #state{}.
+initiate_closing(State=#state{http2_status=connected, socket=Socket,
+ transport=Transport, opts=Opts}, Reason) ->
+ Transport:send(Socket, cow_http2:goaway(16#7fffffff, no_error, <<>>)),
+ Timeout = maps:get(goaway_initial_timeout, Opts, 1000),
+ Message = {goaway_initial_timeout, Reason},
+ set_timeout(State#state{http2_status=closing_initiated}, Timeout, Message);
+initiate_closing(State=#state{http2_status=Status}, _Reason)
+ when Status =:= closing_initiated; Status =:= closing ->
+ %% This happens if sys:terminate/2,3 is called twice or if the supervisor
+ %% tells us to shutdown after sys:terminate/2,3 is called or vice versa.
+ State;
+initiate_closing(State, Reason) ->
+ terminate(State, {stop, stop_reason(Reason), 'The connection is going away.'}).
+
+%% Switch to 'closing' state and stop accepting new streams.
+-spec closing(#state{}, Reason :: term()) -> #state{}.
+closing(State=#state{streams=Streams}, Reason) when Streams =:= #{} ->
+ terminate(State, Reason);
+closing(State=#state{http2_status=closing_initiated,
+ http2_machine=HTTP2Machine0, socket=Socket, transport=Transport},
+ Reason) ->
+ %% Stop accepting new streams.
+ {LastStreamID, HTTP2Machine} =
+ cow_http2_machine:set_last_streamid(HTTP2Machine0),
+ Transport:send(Socket, cow_http2:goaway(LastStreamID, no_error, <<>>)),
+ closing(State#state{http2_status=closing, http2_machine=HTTP2Machine}, Reason);
+closing(State=#state{http2_status=closing, opts=Opts}, Reason) ->
+ %% If client sent GOAWAY, we may already be in 'closing' but without the
+ %% goaway complete timeout set.
+ Timeout = maps:get(goaway_complete_timeout, Opts, 3000),
+ Message = {goaway_complete_timeout, Reason},
+ set_timeout(State, Timeout, Message).
+
+stop_reason({stop, Reason, _}) -> Reason;
+stop_reason(Reason) -> Reason.
+
+-spec terminate(#state{}, _) -> no_return().
+terminate(undefined, Reason) ->
+ exit({shutdown, Reason});
+terminate(State=#state{socket=Socket, transport=Transport, http2_status=Status,
+ http2_machine=HTTP2Machine, streams=Streams, children=Children}, Reason)
+ when Status =:= connected; Status =:= closing_initiated; Status =:= closing ->
+ %% @todo We might want to optionally send the Reason value
+ %% as debug data in the GOAWAY frame here. Perhaps more.
+ if
+ Status =:= connected; Status =:= closing_initiated ->
+ Transport:send(Socket, cow_http2:goaway(
+ cow_http2_machine:get_last_streamid(HTTP2Machine),
+ terminate_reason(Reason), <<>>));
+ %% We already sent the GOAWAY frame.
+ Status =:= closing ->
+ ok
+ end,
+ terminate_all_streams(State, maps:to_list(Streams), Reason),
+ cowboy_children:terminate(Children),
+ terminate_linger(State),
+ exit({shutdown, Reason});
+terminate(#state{socket=Socket, transport=Transport}, Reason) ->
+ Transport:close(Socket),
+ exit({shutdown, Reason}).
+
+terminate_reason({connection_error, Reason, _}) -> Reason;
+terminate_reason({stop, _, _}) -> no_error;
+terminate_reason({socket_error, _, _}) -> internal_error;
+terminate_reason({internal_error, _, _}) -> internal_error.
+
+terminate_all_streams(_, [], _) ->
+ ok;
+terminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reason) ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ terminate_all_streams(State, Tail, Reason).
+
+%% This code is copied from cowboy_http.
+terminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ case Transport:shutdown(Socket, write) of
+ ok ->
+ case maps:get(linger_timeout, Opts, 1000) of
+ 0 ->
+ ok;
+ infinity ->
+ terminate_linger_before_loop(State, undefined, Transport:messages());
+ Timeout ->
+ TimerRef = erlang:start_timer(Timeout, self(), linger_timeout),
+ terminate_linger_before_loop(State, TimerRef, Transport:messages())
+ end;
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_before_loop(State, TimerRef, Messages) ->
+ %% We may already be in active mode when we do this
+ %% but it's OK because we are shutting down anyway.
+ case setopts_active(State) of
+ ok ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) ->
+ receive
+ {OK, Socket, _} when OK =:= element(1, Messages) ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ ok;
+ {Error, Socket, _} when Error =:= element(3, Messages) ->
+ ok;
+ {Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ terminate_linger_before_loop(State, TimerRef, Messages);
+ {timeout, TimerRef, linger_timeout} ->
+ ok;
+ _ ->
+ terminate_linger_loop(State, TimerRef, Messages)
+ end.
+
+%% @todo Don't send an RST_STREAM if one was already sent.
+reset_stream(State0=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID, Error) ->
+ Reason = case Error of
+ {internal_error, _, _} -> internal_error;
+ {stream_error, Reason0, _} -> Reason0
+ end,
+ Transport:send(Socket, cow_http2:rst_stream(StreamID, Reason)),
+ State1 = case cow_http2_machine:reset_stream(StreamID, HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ terminate_stream(State0#state{http2_machine=HTTP2Machine}, StreamID, Error);
+ {error, not_found} ->
+ terminate_stream(State0, StreamID, Error)
+ end,
+ case reset_rate(State1) of
+ {ok, State} ->
+ State;
+ error ->
+ terminate(State1, {connection_error, enhance_your_calm,
+ 'Stream reset rate larger than configuration allows. Flood? (CVE-2019-9514)'})
+ end.
+
+reset_rate(State0=#state{reset_rate_num=Num0, reset_rate_time=Time}) ->
+ case Num0 - 1 of
+ 0 ->
+ CurrentTime = erlang:monotonic_time(millisecond),
+ if
+ CurrentTime < Time ->
+ error;
+ true ->
+ %% When the option has a period of infinity we cannot reach this clause.
+ {ok, init_reset_rate_limiting(State0, CurrentTime)}
+ end;
+ Num ->
+ {ok, State0#state{reset_rate_num=Num}}
+ end.
+
+stop_stream(State=#state{http2_machine=HTTP2Machine}, StreamID) ->
+ case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
+ %% When the stream terminates normally (without sending RST_STREAM)
+ %% and no response was sent, we need to send a proper response back to the client.
+ %% We delay the termination of the stream until the response is fully sent.
+ {ok, idle, _} ->
+ info(stopping(State, StreamID), StreamID, {response, 204, #{}, <<>>});
+ %% When a response was sent but not terminated, we need to close the stream.
+ %% We delay the termination of the stream until the response is fully sent.
+ {ok, nofin, fin} ->
+ stopping(State, StreamID);
+ %% We only send a final DATA frame if there isn't one queued yet.
+ {ok, nofin, _} ->
+ info(stopping(State, StreamID), StreamID, {data, fin, <<>>});
+ %% When a response was sent fully we can terminate the stream,
+ %% regardless of the stream being in half-closed or closed state.
+ _ ->
+ terminate_stream(State, StreamID)
+ end.
+
+stopping(State=#state{streams=Streams}, StreamID) ->
+ #{StreamID := Stream} = Streams,
+ State#state{streams=Streams#{StreamID => Stream#stream{status=stopping}}}.
+
+%% If we finished sending data and the stream is stopping, terminate it.
+maybe_terminate_stream(State=#state{streams=Streams}, StreamID, fin) ->
+ case Streams of
+ #{StreamID := #stream{status=stopping}} ->
+ terminate_stream(State, StreamID);
+ _ ->
+ State
+ end;
+maybe_terminate_stream(State, _, _) ->
+ State.
+
+%% When the stream stops normally without reading the request
+%% body fully we need to tell the client to stop sending it.
+%% We do this by sending an RST_STREAM with reason NO_ERROR. (RFC7540 8.1.0)
+terminate_stream(State0=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID) ->
+ State = case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine0) of
+ {ok, fin, _} ->
+ Transport:send(Socket, cow_http2:rst_stream(StreamID, no_error)),
+ {ok, HTTP2Machine} = cow_http2_machine:reset_stream(StreamID, HTTP2Machine0),
+ State0#state{http2_machine=HTTP2Machine};
+ {error, closed} ->
+ State0
+ end,
+ terminate_stream(State, StreamID, normal).
+
+%% We remove the stream flow from the connection flow. Any further
+%% data received for this stream is therefore fully contained within
+%% the extra window we allocated for this stream.
+terminate_stream(State=#state{flow=Flow, streams=Streams0, children=Children0}, StreamID, Reason) ->
+ case maps:take(StreamID, Streams0) of
+ {#stream{flow=StreamFlow, state=StreamState}, Streams} ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ Children = cowboy_children:shutdown(Children0, StreamID),
+ State#state{flow=Flow - StreamFlow, streams=Streams, children=Children};
+ error ->
+ State
+ end.
+
+terminate_stream_handler(#state{opts=Opts}, StreamID, Reason, StreamState) ->
+ try
+ cowboy_stream:terminate(StreamID, Reason, StreamState)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(terminate,
+ [StreamID, Reason, StreamState],
+ Class, Exception, Stacktrace), Opts)
+ end.
+
+%% System callbacks.
+
+-spec system_continue(_, _, {#state{}, binary()}) -> ok.
+system_continue(_, _, {State, Buffer}) ->
+ loop(State, Buffer).
+
+-spec system_terminate(any(), _, _, {#state{}, binary()}) -> no_return().
+system_terminate(Reason0, _, _, {State, Buffer}) ->
+ Reason = {stop, {exit, Reason0}, 'sys:terminate/2,3 was called.'},
+ loop(initiate_closing(State, Reason), Buffer).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_loop.erl b/server/_build/default/lib/cowboy/src/cowboy_loop.erl
new file mode 100644
index 0000000..21eb96e
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_loop.erl
@@ -0,0 +1,108 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_loop).
+-behaviour(cowboy_sub_protocol).
+
+-export([upgrade/4]).
+-export([upgrade/5]).
+-export([loop/4]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback info(any(), Req, State)
+ -> {ok, Req, State}
+ | {ok, Req, State, hibernate}
+ | {stop, Req, State}
+ when Req::cowboy_req:req(), State::any().
+
+-callback terminate(any(), cowboy_req:req(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerState) ->
+ loop(Req, Env, Handler, HandlerState).
+
+-spec upgrade(Req, Env, module(), any(), hibernate)
+ -> {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerState, hibernate) ->
+ suspend(Req, Env, Handler, HandlerState).
+
+-spec loop(Req, Env, module(), any())
+ -> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+%% @todo Handle system messages.
+loop(Req=#{pid := Parent}, Env, Handler, HandlerState) ->
+ receive
+ %% System messages.
+ {'EXIT', Parent, Reason} ->
+ terminate(Req, Env, Handler, HandlerState, Reason);
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
+ {Req, Env, Handler, HandlerState});
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, [], ?MODULE),
+ loop(Req, Env, Handler, HandlerState);
+ Message ->
+ call(Req, Env, Handler, HandlerState, Message)
+ end.
+
+call(Req0, Env, Handler, HandlerState0, Message) ->
+ try Handler:info(Message, Req0, HandlerState0) of
+ {ok, Req, HandlerState} ->
+ loop(Req, Env, Handler, HandlerState);
+ {ok, Req, HandlerState, hibernate} ->
+ suspend(Req, Env, Handler, HandlerState);
+ {stop, Req, HandlerState} ->
+ terminate(Req, Env, Handler, HandlerState, stop)
+ catch Class:Reason:Stacktrace ->
+ cowboy_handler:terminate({crash, Class, Reason}, Req0, HandlerState0, Handler),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+suspend(Req, Env, Handler, HandlerState) ->
+ {suspend, ?MODULE, loop, [Req, Env, Handler, HandlerState]}.
+
+terminate(Req, Env, Handler, HandlerState, Reason) ->
+ Result = cowboy_handler:terminate(Reason, Req, HandlerState, Handler),
+ {ok, Req, Env#{result => Result}}.
+
+%% System callbacks.
+
+-spec system_continue(_, _, {Req, Env, module(), any()})
+ -> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+system_continue(_, _, {Req, Env, Handler, HandlerState}) ->
+ loop(Req, Env, Handler, HandlerState).
+
+-spec system_terminate(any(), _, _, {Req, Env, module(), any()})
+ -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+system_terminate(Reason, _, _, {Req, Env, Handler, HandlerState}) ->
+ terminate(Req, Env, Handler, HandlerState, Reason).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc}
+ when Misc::{cowboy_req:req(), cowboy_middleware:env(), module(), any()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl b/server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl
new file mode 100644
index 0000000..4107aac
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl
@@ -0,0 +1,331 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_metrics_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-type proc_metrics() :: #{pid() => #{
+ %% Time at which the process spawned.
+ spawn := integer(),
+
+ %% Time at which the process exited.
+ exit => integer(),
+
+ %% Reason for the process exit.
+ reason => any()
+}}.
+
+-type informational_metrics() :: #{
+ %% Informational response status.
+ status := cowboy:http_status(),
+
+ %% Headers sent with the informational response.
+ headers := cowboy:http_headers(),
+
+ %% Time when the informational response was sent.
+ time := integer()
+}.
+
+-type metrics() :: #{
+ %% The identifier for this listener.
+ ref := ranch:ref(),
+
+ %% The pid for this connection.
+ pid := pid(),
+
+ %% The streamid also indicates the total number of requests on
+ %% this connection (StreamID div 2 + 1).
+ streamid := cowboy_stream:streamid(),
+
+ %% The terminate reason is always useful.
+ reason := cowboy_stream:reason(),
+
+ %% A filtered Req object or a partial Req object
+ %% depending on how far the request got to.
+ req => cowboy_req:req(),
+ partial_req => cowboy_stream:partial_req(),
+
+ %% Response status.
+ resp_status := cowboy:http_status(),
+
+ %% Filtered response headers.
+ resp_headers := cowboy:http_headers(),
+
+ %% Start/end of the processing of the request.
+ %%
+ %% This represents the time from this stream handler's init
+ %% to terminate.
+ req_start => integer(),
+ req_end => integer(),
+
+ %% Start/end of the receiving of the request body.
+ %% Begins when the first packet has been received.
+ req_body_start => integer(),
+ req_body_end => integer(),
+
+ %% Start/end of the sending of the response.
+ %% Begins when we send the headers and ends on the final
+ %% packet of the response body. If everything is sent at
+ %% once these values are identical.
+ resp_start => integer(),
+ resp_end => integer(),
+
+ %% For early errors all we get is the time we received it.
+ early_error_time => integer(),
+
+ %% Start/end of spawned processes. This is where most of
+ %% the user code lies, excluding stream handlers. On a
+ %% default Cowboy configuration there should be only one
+ %% process: the request process.
+ procs => proc_metrics(),
+
+ %% Informational responses sent before the final response.
+ informational => [informational_metrics()],
+
+ %% Length of the request and response bodies. This does
+ %% not include the framing.
+ req_body_length => non_neg_integer(),
+ resp_body_length => non_neg_integer(),
+
+ %% Additional metadata set by the user.
+ user_data => map()
+}.
+-export_type([metrics/0]).
+
+-type metrics_callback() :: fun((metrics()) -> any()).
+-export_type([metrics_callback/0]).
+
+-record(state, {
+ next :: any(),
+ callback :: fun((metrics()) -> any()),
+ resp_headers_filter :: undefined | fun((cowboy:http_headers()) -> cowboy:http_headers()),
+ req :: map(),
+ resp_status :: undefined | cowboy:http_status(),
+ resp_headers :: undefined | cowboy:http_headers(),
+ ref :: ranch:ref(),
+ req_start :: integer(),
+ req_end :: undefined | integer(),
+ req_body_start :: undefined | integer(),
+ req_body_end :: undefined | integer(),
+ resp_start :: undefined | integer(),
+ resp_end :: undefined | integer(),
+ procs = #{} :: proc_metrics(),
+ informational = [] :: [informational_metrics()],
+ req_body_length = 0 :: non_neg_integer(),
+ resp_body_length = 0 :: non_neg_integer(),
+ user_data = #{} :: map()
+}).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {[{spawn, pid(), timeout()}], #state{}}.
+init(StreamID, Req=#{ref := Ref}, Opts=#{metrics_callback := Fun}) ->
+ ReqStart = erlang:monotonic_time(),
+ {Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),
+ FilteredReq = case maps:get(metrics_req_filter, Opts, undefined) of
+ undefined -> Req;
+ ReqFilter -> ReqFilter(Req)
+ end,
+ RespHeadersFilter = maps:get(metrics_resp_headers_filter, Opts, undefined),
+ {Commands, fold(Commands, #state{
+ next=Next,
+ callback=Fun,
+ resp_headers_filter=RespHeadersFilter,
+ req=FilteredReq,
+ ref=Ref,
+ req_start=ReqStart
+ })}.
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+data(StreamID, IsFin=fin, Data, State=#state{req_body_start=undefined}) ->
+ ReqBody = erlang:monotonic_time(),
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_start=ReqBody,
+ req_body_end=ReqBody,
+ req_body_length=byte_size(Data)
+ });
+data(StreamID, IsFin=fin, Data, State=#state{req_body_length=ReqBodyLen}) ->
+ ReqBodyEnd = erlang:monotonic_time(),
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_end=ReqBodyEnd,
+ req_body_length=ReqBodyLen + byte_size(Data)
+ });
+data(StreamID, IsFin, Data, State=#state{req_body_start=undefined}) ->
+ ReqBodyStart = erlang:monotonic_time(),
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_start=ReqBodyStart,
+ req_body_length=byte_size(Data)
+ });
+data(StreamID, IsFin, Data, State=#state{req_body_length=ReqBodyLen}) ->
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_length=ReqBodyLen + byte_size(Data)
+ }).
+
+do_data(StreamID, IsFin, Data, State0=#state{next=Next0}) ->
+ {Commands, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
+ {Commands, fold(Commands, State0#state{next=Next})}.
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+info(StreamID, Info={'EXIT', Pid, Reason}, State0=#state{procs=Procs}) ->
+ ProcEnd = erlang:monotonic_time(),
+ P = maps:get(Pid, Procs),
+ State = State0#state{procs=Procs#{Pid => P#{
+ exit => ProcEnd,
+ reason => Reason
+ }}},
+ do_info(StreamID, Info, State);
+info(StreamID, Info, State) ->
+ do_info(StreamID, Info, State).
+
+do_info(StreamID, Info, State0=#state{next=Next0}) ->
+ {Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ {Commands, fold(Commands, State0#state{next=Next})}.
+
+fold([], State) ->
+ State;
+fold([{spawn, Pid, _}|Tail], State0=#state{procs=Procs}) ->
+ ProcStart = erlang:monotonic_time(),
+ State = State0#state{procs=Procs#{Pid => #{spawn => ProcStart}}},
+ fold(Tail, State);
+fold([{inform, Status, Headers}|Tail],
+ State=#state{informational=Infos}) ->
+ Time = erlang:monotonic_time(),
+ fold(Tail, State#state{informational=[#{
+ status => Status,
+ headers => Headers,
+ time => Time
+ }|Infos]});
+fold([{response, Status, Headers, Body}|Tail],
+ State=#state{resp_headers_filter=RespHeadersFilter}) ->
+ Resp = erlang:monotonic_time(),
+ fold(Tail, State#state{
+ resp_status=Status,
+ resp_headers=case RespHeadersFilter of
+ undefined -> Headers;
+ _ -> RespHeadersFilter(Headers)
+ end,
+ resp_start=Resp,
+ resp_end=Resp,
+ resp_body_length=resp_body_length(Body)
+ });
+fold([{error_response, Status, Headers, Body}|Tail],
+ State=#state{resp_status=RespStatus}) ->
+ %% The error_response command only results in a response
+ %% if no response was sent before.
+ case RespStatus of
+ undefined ->
+ fold([{response, Status, Headers, Body}|Tail], State);
+ _ ->
+ fold(Tail, State)
+ end;
+fold([{headers, Status, Headers}|Tail],
+ State=#state{resp_headers_filter=RespHeadersFilter}) ->
+ RespStart = erlang:monotonic_time(),
+ fold(Tail, State#state{
+ resp_status=Status,
+ resp_headers=case RespHeadersFilter of
+ undefined -> Headers;
+ _ -> RespHeadersFilter(Headers)
+ end,
+ resp_start=RespStart
+ });
+%% @todo It might be worthwhile to keep the sendfile information around,
+%% especially if these frames ultimately result in a sendfile syscall.
+fold([{data, nofin, Data}|Tail], State=#state{resp_body_length=RespBodyLen}) ->
+ fold(Tail, State#state{
+ resp_body_length=RespBodyLen + resp_body_length(Data)
+ });
+fold([{data, fin, Data}|Tail], State=#state{resp_body_length=RespBodyLen}) ->
+ RespEnd = erlang:monotonic_time(),
+ fold(Tail, State#state{
+ resp_end=RespEnd,
+ resp_body_length=RespBodyLen + resp_body_length(Data)
+ });
+fold([{set_options, SetOpts}|Tail], State0=#state{user_data=OldUserData}) ->
+ State = case SetOpts of
+ #{metrics_user_data := NewUserData} ->
+ State0#state{user_data=maps:merge(OldUserData, NewUserData)};
+ _ ->
+ State0
+ end,
+ fold(Tail, State);
+fold([_|Tail], State) ->
+ fold(Tail, State).
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().
+terminate(StreamID, Reason, #state{next=Next, callback=Fun,
+ req=Req, resp_status=RespStatus, resp_headers=RespHeaders, ref=Ref,
+ req_start=ReqStart, req_body_start=ReqBodyStart,
+ req_body_end=ReqBodyEnd, resp_start=RespStart, resp_end=RespEnd,
+ procs=Procs, informational=Infos, user_data=UserData,
+ req_body_length=ReqBodyLen, resp_body_length=RespBodyLen}) ->
+ Res = cowboy_stream:terminate(StreamID, Reason, Next),
+ ReqEnd = erlang:monotonic_time(),
+ Metrics = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ reason => Reason,
+ req => Req,
+ resp_status => RespStatus,
+ resp_headers => RespHeaders,
+ req_start => ReqStart,
+ req_end => ReqEnd,
+ req_body_start => ReqBodyStart,
+ req_body_end => ReqBodyEnd,
+ resp_start => RespStart,
+ resp_end => RespEnd,
+ procs => Procs,
+ informational => lists:reverse(Infos),
+ req_body_length => ReqBodyLen,
+ resp_body_length => RespBodyLen,
+ user_data => UserData
+ },
+ Fun(Metrics),
+ Res.
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq=#{ref := Ref}, Resp0, Opts=#{metrics_callback := Fun}) ->
+ Time = erlang:monotonic_time(),
+ Resp = {response, RespStatus, RespHeaders, RespBody}
+ = cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp0, Opts),
+ %% As far as metrics go we are limited in what we can provide
+ %% in this case.
+ Metrics = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ reason => Reason,
+ partial_req => PartialReq,
+ resp_status => RespStatus,
+ resp_headers => RespHeaders,
+ early_error_time => Time,
+ resp_body_length => resp_body_length(RespBody)
+ },
+ Fun(Metrics),
+ Resp.
+
+resp_body_length({sendfile, _, Len, _}) ->
+ Len;
+resp_body_length(Data) ->
+ iolist_size(Data).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_middleware.erl b/server/_build/default/lib/cowboy/src/cowboy_middleware.erl
new file mode 100644
index 0000000..9a739f1
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_middleware.erl
@@ -0,0 +1,24 @@
+%% Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_middleware).
+
+-type env() :: #{atom() => any()}.
+-export_type([env/0]).
+
+-callback execute(Req, Env)
+ -> {ok, Req, Env}
+ | {suspend, module(), atom(), [any()]}
+ | {stop, Req}
+ when Req::cowboy_req:req(), Env::env().
diff --git a/server/_build/default/lib/cowboy/src/cowboy_req.erl b/server/_build/default/lib/cowboy/src/cowboy_req.erl
new file mode 100644
index 0000000..90c5a3a
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_req.erl
@@ -0,0 +1,1016 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_req).
+
+%% Request.
+-export([method/1]).
+-export([version/1]).
+-export([peer/1]).
+-export([sock/1]).
+-export([cert/1]).
+-export([scheme/1]).
+-export([host/1]).
+-export([host_info/1]).
+-export([port/1]).
+-export([path/1]).
+-export([path_info/1]).
+-export([qs/1]).
+-export([parse_qs/1]).
+-export([match_qs/2]).
+-export([uri/1]).
+-export([uri/2]).
+-export([binding/2]).
+-export([binding/3]).
+-export([bindings/1]).
+-export([header/2]).
+-export([header/3]).
+-export([headers/1]).
+-export([parse_header/2]).
+-export([parse_header/3]).
+-export([filter_cookies/2]).
+-export([parse_cookies/1]).
+-export([match_cookies/2]).
+
+%% Request body.
+-export([has_body/1]).
+-export([body_length/1]).
+-export([read_body/1]).
+-export([read_body/2]).
+-export([read_urlencoded_body/1]).
+-export([read_urlencoded_body/2]).
+-export([read_and_match_urlencoded_body/2]).
+-export([read_and_match_urlencoded_body/3]).
+
+%% Multipart.
+-export([read_part/1]).
+-export([read_part/2]).
+-export([read_part_body/1]).
+-export([read_part_body/2]).
+
+%% Response.
+-export([set_resp_cookie/3]).
+-export([set_resp_cookie/4]).
+-export([resp_header/2]).
+-export([resp_header/3]).
+-export([resp_headers/1]).
+-export([set_resp_header/3]).
+-export([set_resp_headers/2]).
+-export([has_resp_header/2]).
+-export([delete_resp_header/2]).
+-export([set_resp_body/2]).
+%% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.
+-export([has_resp_body/1]).
+-export([inform/2]).
+-export([inform/3]).
+-export([reply/2]).
+-export([reply/3]).
+-export([reply/4]).
+-export([stream_reply/2]).
+-export([stream_reply/3]).
+%% @todo stream_body/2 (nofin)
+-export([stream_body/3]).
+%% @todo stream_events/2 (nofin)
+-export([stream_events/3]).
+-export([stream_trailers/2]).
+-export([push/3]).
+-export([push/4]).
+
+%% Stream handlers.
+-export([cast/2]).
+
+%% Internal.
+-export([response_headers/2]).
+
+-type read_body_opts() :: #{
+ length => non_neg_integer() | infinity,
+ period => non_neg_integer(),
+ timeout => timeout()
+}.
+-export_type([read_body_opts/0]).
+
+%% While sendfile allows a Len of 0 that means "everything past Offset",
+%% Cowboy expects the real length as it is used as metadata.
+-type resp_body() :: iodata()
+ | {sendfile, non_neg_integer(), non_neg_integer(), file:name_all()}.
+-export_type([resp_body/0]).
+
+-type push_opts() :: #{
+ method => binary(),
+ scheme => binary(),
+ host => binary(),
+ port => inet:port_number(),
+ qs => binary()
+}.
+-export_type([push_opts/0]).
+
+-type req() :: #{
+ %% Public interface.
+ method := binary(),
+ version := cowboy:http_version() | atom(),
+ scheme := binary(),
+ host := binary(),
+ port := inet:port_number(),
+ path := binary(),
+ qs := binary(),
+ headers := cowboy:http_headers(),
+ peer := {inet:ip_address(), inet:port_number()},
+ sock := {inet:ip_address(), inet:port_number()},
+ cert := binary() | undefined,
+
+ %% Private interface.
+ ref := ranch:ref(),
+ pid := pid(),
+ streamid := cowboy_stream:streamid(),
+
+ host_info => cowboy_router:tokens(),
+ path_info => cowboy_router:tokens(),
+ bindings => cowboy_router:bindings(),
+
+ has_body := boolean(),
+ body_length := non_neg_integer() | undefined,
+ has_read_body => true,
+ multipart => {binary(), binary()} | done,
+
+ has_sent_resp => headers | true,
+ resp_cookies => #{iodata() => iodata()},
+ resp_headers => #{binary() => iodata()},
+ resp_body => resp_body(),
+
+ proxy_header => ranch_proxy_header:proxy_info(),
+ media_type => {binary(), binary(), [{binary(), binary()}]},
+ language => binary() | undefined,
+ charset => binary() | undefined,
+ range => {binary(), binary()
+ | [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]},
+ websocket_version => 7 | 8 | 13,
+
+ %% The user is encouraged to use the Req to store information
+ %% when no better solution is available.
+ _ => _
+}.
+-export_type([req/0]).
+
+%% Request.
+
+-spec method(req()) -> binary().
+method(#{method := Method}) ->
+ Method.
+
+-spec version(req()) -> cowboy:http_version().
+version(#{version := Version}) ->
+ Version.
+
+-spec peer(req()) -> {inet:ip_address(), inet:port_number()}.
+peer(#{peer := Peer}) ->
+ Peer.
+
+-spec sock(req()) -> {inet:ip_address(), inet:port_number()}.
+sock(#{sock := Sock}) ->
+ Sock.
+
+-spec cert(req()) -> binary() | undefined.
+cert(#{cert := Cert}) ->
+ Cert.
+
+-spec scheme(req()) -> binary().
+scheme(#{scheme := Scheme}) ->
+ Scheme.
+
+-spec host(req()) -> binary().
+host(#{host := Host}) ->
+ Host.
+
+%% @todo The host_info is undefined if cowboy_router isn't used. Do we want to crash?
+-spec host_info(req()) -> cowboy_router:tokens() | undefined.
+host_info(#{host_info := HostInfo}) ->
+ HostInfo.
+
+-spec port(req()) -> inet:port_number().
+port(#{port := Port}) ->
+ Port.
+
+-spec path(req()) -> binary().
+path(#{path := Path}) ->
+ Path.
+
+%% @todo The path_info is undefined if cowboy_router isn't used. Do we want to crash?
+-spec path_info(req()) -> cowboy_router:tokens() | undefined.
+path_info(#{path_info := PathInfo}) ->
+ PathInfo.
+
+-spec qs(req()) -> binary().
+qs(#{qs := Qs}) ->
+ Qs.
+
+%% @todo Might be useful to limit the number of keys.
+-spec parse_qs(req()) -> [{binary(), binary() | true}].
+parse_qs(#{qs := Qs}) ->
+ try
+ cow_qs:parse_qs(Qs)
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, qs,
+ 'Malformed query string; application/x-www-form-urlencoded expected.'
+ }, Stacktrace)
+ end.
+
+-spec match_qs(cowboy:fields(), req()) -> map().
+match_qs(Fields, Req) ->
+ case filter(Fields, kvlist_to_map(Fields, parse_qs(Req))) of
+ {ok, Map} ->
+ Map;
+ {error, Errors} ->
+ exit({request_error, {match_qs, Errors},
+ 'Query string validation constraints failed for the reasons provided.'})
+ end.
+
+-spec uri(req()) -> iodata().
+uri(Req) ->
+ uri(Req, #{}).
+
+-spec uri(req(), map()) -> iodata().
+uri(#{scheme := Scheme0, host := Host0, port := Port0,
+ path := Path0, qs := Qs0}, Opts) ->
+ Scheme = case maps:get(scheme, Opts, Scheme0) of
+ S = undefined -> S;
+ S -> iolist_to_binary(S)
+ end,
+ Host = maps:get(host, Opts, Host0),
+ Port = maps:get(port, Opts, Port0),
+ {Path, Qs} = case maps:get(path, Opts, Path0) of
+ <<"*">> -> {<<>>, <<>>};
+ P -> {P, maps:get(qs, Opts, Qs0)}
+ end,
+ Fragment = maps:get(fragment, Opts, undefined),
+ [uri_host(Scheme, Scheme0, Port, Host), uri_path(Path), uri_qs(Qs), uri_fragment(Fragment)].
+
+uri_host(_, _, _, undefined) -> <<>>;
+uri_host(Scheme, Scheme0, Port, Host) ->
+ case iolist_size(Host) of
+ 0 -> <<>>;
+ _ -> [uri_scheme(Scheme), <<"//">>, Host, uri_port(Scheme, Scheme0, Port)]
+ end.
+
+uri_scheme(undefined) -> <<>>;
+uri_scheme(Scheme) ->
+ case iolist_size(Scheme) of
+ 0 -> Scheme;
+ _ -> [Scheme, $:]
+ end.
+
+uri_port(_, _, undefined) -> <<>>;
+uri_port(undefined, <<"http">>, 80) -> <<>>;
+uri_port(undefined, <<"https">>, 443) -> <<>>;
+uri_port(<<"http">>, _, 80) -> <<>>;
+uri_port(<<"https">>, _, 443) -> <<>>;
+uri_port(_, _, Port) ->
+ [$:, integer_to_binary(Port)].
+
+uri_path(undefined) -> <<>>;
+uri_path(Path) -> Path.
+
+uri_qs(undefined) -> <<>>;
+uri_qs(Qs) ->
+ case iolist_size(Qs) of
+ 0 -> Qs;
+ _ -> [$?, Qs]
+ end.
+
+uri_fragment(undefined) -> <<>>;
+uri_fragment(Fragment) ->
+ case iolist_size(Fragment) of
+ 0 -> Fragment;
+ _ -> [$#, Fragment]
+ end.
+
+-ifdef(TEST).
+uri1_test() ->
+ <<"http://localhost/path">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 80,
+ path => <<"/path">>, qs => <<>>})),
+ <<"http://localhost:443/path">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 443,
+ path => <<"/path">>, qs => <<>>})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 8080,
+ path => <<"/path">>, qs => <<>>})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 8080,
+ path => <<"/path">>, qs => <<"dummy=2785">>})),
+ <<"https://localhost/path">> = iolist_to_binary(uri(#{
+ scheme => <<"https">>, host => <<"localhost">>, port => 443,
+ path => <<"/path">>, qs => <<>>})),
+ <<"https://localhost:8443/path">> = iolist_to_binary(uri(#{
+ scheme => <<"https">>, host => <<"localhost">>, port => 8443,
+ path => <<"/path">>, qs => <<>>})),
+ <<"https://localhost:8443/path?dummy=2785">> = iolist_to_binary(uri(#{
+ scheme => <<"https">>, host => <<"localhost">>, port => 8443,
+ path => <<"/path">>, qs => <<"dummy=2785">>})),
+ ok.
+
+uri2_test() ->
+ Req = #{
+ scheme => <<"http">>, host => <<"localhost">>, port => 8080,
+ path => <<"/path">>, qs => <<"dummy=2785">>
+ },
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{})),
+ %% Disable individual components.
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => undefined})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => undefined})),
+ <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => undefined})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => undefined})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => undefined})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => undefined})),
+ <<"http://localhost:8080">> = iolist_to_binary(uri(Req, #{path => undefined, qs => undefined})),
+ <<>> = iolist_to_binary(uri(Req, #{host => undefined, path => undefined, qs => undefined})),
+ %% Empty values.
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => <<>>})),
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => ""})),
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => [<<>>]})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => <<>>})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => ""})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => [<<>>]})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => <<>>})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => ""})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => [<<>>]})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => <<>>})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => ""})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => [<<>>]})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => <<>>})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => ""})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => [<<>>]})),
+ %% Port is integer() | undefined.
+ {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => <<>>}))),
+ {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => ""}))),
+ {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => [<<>>]}))),
+ %% Update components.
+ <<"https://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https"})),
+ <<"http://example.org:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => "example.org"})),
+ <<"http://localhost:123/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 123})),
+ <<"http://localhost:8080/custom?dummy=2785">> = iolist_to_binary(uri(Req, #{path => "/custom"})),
+ <<"http://localhost:8080/path?smart=42">> = iolist_to_binary(uri(Req, #{qs => "smart=42"})),
+ <<"http://localhost:8080/path?dummy=2785#intro">> = iolist_to_binary(uri(Req, #{fragment => "intro"})),
+ %% Interesting combinations.
+ <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 80})),
+ <<"https://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https", port => 443})),
+ ok.
+-endif.
+
+-spec binding(atom(), req()) -> any() | undefined.
+binding(Name, Req) ->
+ binding(Name, Req, undefined).
+
+-spec binding(atom(), req(), Default) -> any() | Default when Default::any().
+binding(Name, #{bindings := Bindings}, Default) when is_atom(Name) ->
+ case Bindings of
+ #{Name := Value} -> Value;
+ _ -> Default
+ end;
+binding(Name, _, Default) when is_atom(Name) ->
+ Default.
+
+-spec bindings(req()) -> cowboy_router:bindings().
+bindings(#{bindings := Bindings}) ->
+ Bindings;
+bindings(_) ->
+ #{}.
+
+-spec header(binary(), req()) -> binary() | undefined.
+header(Name, Req) ->
+ header(Name, Req, undefined).
+
+-spec header(binary(), req(), Default) -> binary() | Default when Default::any().
+header(Name, #{headers := Headers}, Default) ->
+ maps:get(Name, Headers, Default).
+
+-spec headers(req()) -> cowboy:http_headers().
+headers(#{headers := Headers}) ->
+ Headers.
+
+-spec parse_header(binary(), Req) -> any() when Req::req().
+parse_header(Name = <<"content-length">>, Req) ->
+ parse_header(Name, Req, 0);
+parse_header(Name = <<"cookie">>, Req) ->
+ parse_header(Name, Req, []);
+parse_header(Name, Req) ->
+ parse_header(Name, Req, undefined).
+
+-spec parse_header(binary(), Req, any()) -> any() when Req::req().
+parse_header(Name, Req, Default) ->
+ try
+ parse_header(Name, Req, Default, parse_header_fun(Name))
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, {header, Name},
+ 'Malformed header. Please consult the relevant specification.'
+ }, Stacktrace)
+ end.
+
+parse_header_fun(<<"accept">>) -> fun cow_http_hd:parse_accept/1;
+parse_header_fun(<<"accept-charset">>) -> fun cow_http_hd:parse_accept_charset/1;
+parse_header_fun(<<"accept-encoding">>) -> fun cow_http_hd:parse_accept_encoding/1;
+parse_header_fun(<<"accept-language">>) -> fun cow_http_hd:parse_accept_language/1;
+parse_header_fun(<<"access-control-request-headers">>) -> fun cow_http_hd:parse_access_control_request_headers/1;
+parse_header_fun(<<"access-control-request-method">>) -> fun cow_http_hd:parse_access_control_request_method/1;
+parse_header_fun(<<"authorization">>) -> fun cow_http_hd:parse_authorization/1;
+parse_header_fun(<<"connection">>) -> fun cow_http_hd:parse_connection/1;
+parse_header_fun(<<"content-encoding">>) -> fun cow_http_hd:parse_content_encoding/1;
+parse_header_fun(<<"content-language">>) -> fun cow_http_hd:parse_content_language/1;
+parse_header_fun(<<"content-length">>) -> fun cow_http_hd:parse_content_length/1;
+parse_header_fun(<<"content-type">>) -> fun cow_http_hd:parse_content_type/1;
+parse_header_fun(<<"cookie">>) -> fun cow_cookie:parse_cookie/1;
+parse_header_fun(<<"expect">>) -> fun cow_http_hd:parse_expect/1;
+parse_header_fun(<<"if-match">>) -> fun cow_http_hd:parse_if_match/1;
+parse_header_fun(<<"if-modified-since">>) -> fun cow_http_hd:parse_if_modified_since/1;
+parse_header_fun(<<"if-none-match">>) -> fun cow_http_hd:parse_if_none_match/1;
+parse_header_fun(<<"if-range">>) -> fun cow_http_hd:parse_if_range/1;
+parse_header_fun(<<"if-unmodified-since">>) -> fun cow_http_hd:parse_if_unmodified_since/1;
+parse_header_fun(<<"max-forwards">>) -> fun cow_http_hd:parse_max_forwards/1;
+parse_header_fun(<<"origin">>) -> fun cow_http_hd:parse_origin/1;
+parse_header_fun(<<"proxy-authorization">>) -> fun cow_http_hd:parse_proxy_authorization/1;
+parse_header_fun(<<"range">>) -> fun cow_http_hd:parse_range/1;
+parse_header_fun(<<"sec-websocket-extensions">>) -> fun cow_http_hd:parse_sec_websocket_extensions/1;
+parse_header_fun(<<"sec-websocket-protocol">>) -> fun cow_http_hd:parse_sec_websocket_protocol_req/1;
+parse_header_fun(<<"sec-websocket-version">>) -> fun cow_http_hd:parse_sec_websocket_version_req/1;
+parse_header_fun(<<"trailer">>) -> fun cow_http_hd:parse_trailer/1;
+parse_header_fun(<<"upgrade">>) -> fun cow_http_hd:parse_upgrade/1;
+parse_header_fun(<<"x-forwarded-for">>) -> fun cow_http_hd:parse_x_forwarded_for/1.
+
+parse_header(Name, Req, Default, ParseFun) ->
+ case header(Name, Req) of
+ undefined -> Default;
+ Value -> ParseFun(Value)
+ end.
+
+-spec filter_cookies([atom() | binary()], Req) -> Req when Req::req().
+filter_cookies(Names0, Req=#{headers := Headers}) ->
+ Names = [if
+ is_atom(N) -> atom_to_binary(N, utf8);
+ true -> N
+ end || N <- Names0],
+ case header(<<"cookie">>, Req) of
+ undefined -> Req;
+ Value0 ->
+ Cookies0 = binary:split(Value0, <<$;>>),
+ Cookies = lists:filter(fun(Cookie) ->
+ lists:member(cookie_name(Cookie), Names)
+ end, Cookies0),
+ Value = iolist_to_binary(lists:join($;, Cookies)),
+ Req#{headers => Headers#{<<"cookie">> => Value}}
+ end.
+
+%% This is a specialized function to extract a cookie name
+%% regardless of whether the name is valid or not. We skip
+%% whitespace at the beginning and take whatever's left to
+%% be the cookie name, up to the = sign.
+cookie_name(<<$\s, Rest/binary>>) -> cookie_name(Rest);
+cookie_name(<<$\t, Rest/binary>>) -> cookie_name(Rest);
+cookie_name(Name) -> cookie_name(Name, <<>>).
+
+cookie_name(<<>>, Name) -> Name;
+cookie_name(<<$=, _/bits>>, Name) -> Name;
+cookie_name(<<C, Rest/bits>>, Acc) -> cookie_name(Rest, <<Acc/binary, C>>).
+
+-spec parse_cookies(req()) -> [{binary(), binary()}].
+parse_cookies(Req) ->
+ parse_header(<<"cookie">>, Req).
+
+-spec match_cookies(cowboy:fields(), req()) -> map().
+match_cookies(Fields, Req) ->
+ case filter(Fields, kvlist_to_map(Fields, parse_cookies(Req))) of
+ {ok, Map} ->
+ Map;
+ {error, Errors} ->
+ exit({request_error, {match_cookies, Errors},
+ 'Cookie validation constraints failed for the reasons provided.'})
+ end.
+
+%% Request body.
+
+-spec has_body(req()) -> boolean().
+has_body(#{has_body := HasBody}) ->
+ HasBody.
+
+%% The length may not be known if HTTP/1.1 with a transfer-encoding;
+%% or HTTP/2 with no content-length header. The length is always
+%% known once the body has been completely read.
+-spec body_length(req()) -> undefined | non_neg_integer().
+body_length(#{body_length := Length}) ->
+ Length.
+
+-spec read_body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
+read_body(Req) ->
+ read_body(Req, #{}).
+
+-spec read_body(Req, read_body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
+read_body(Req=#{has_body := false}, _) ->
+ {ok, <<>>, Req};
+read_body(Req=#{has_read_body := true}, _) ->
+ {ok, <<>>, Req};
+read_body(Req, Opts) ->
+ Length = maps:get(length, Opts, 8000000),
+ Period = maps:get(period, Opts, 15000),
+ Timeout = maps:get(timeout, Opts, Period + 1000),
+ Ref = make_ref(),
+ cast({read_body, self(), Ref, Length, Period}, Req),
+ receive
+ {request_body, Ref, nofin, Body} ->
+ {more, Body, Req};
+ {request_body, Ref, fin, BodyLength, Body} ->
+ {ok, Body, set_body_length(Req, BodyLength)}
+ after Timeout ->
+ exit(timeout)
+ end.
+
+set_body_length(Req=#{headers := Headers}, BodyLength) ->
+ Req#{
+ headers => Headers#{<<"content-length">> => integer_to_binary(BodyLength)},
+ body_length => BodyLength,
+ has_read_body => true
+ }.
+
+-spec read_urlencoded_body(Req) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
+read_urlencoded_body(Req) ->
+ read_urlencoded_body(Req, #{length => 64000, period => 5000}).
+
+-spec read_urlencoded_body(Req, read_body_opts()) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
+read_urlencoded_body(Req0, Opts) ->
+ case read_body(Req0, Opts) of
+ {ok, Body, Req} ->
+ try
+ {ok, cow_qs:parse_qs(Body), Req}
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, urlencoded_body,
+ 'Malformed body; application/x-www-form-urlencoded expected.'
+ }, Stacktrace)
+ end;
+ {more, Body, _} ->
+ Length = maps:get(length, Opts, 64000),
+ if
+ byte_size(Body) < Length ->
+ exit({request_error, timeout,
+ 'The request body was not received within the configured time.'});
+ true ->
+ exit({request_error, payload_too_large,
+ 'The request body is larger than allowed by configuration.'})
+ end
+ end.
+
+-spec read_and_match_urlencoded_body(cowboy:fields(), Req)
+ -> {ok, map(), Req} when Req::req().
+read_and_match_urlencoded_body(Fields, Req) ->
+ read_and_match_urlencoded_body(Fields, Req, #{length => 64000, period => 5000}).
+
+-spec read_and_match_urlencoded_body(cowboy:fields(), Req, read_body_opts())
+ -> {ok, map(), Req} when Req::req().
+read_and_match_urlencoded_body(Fields, Req0, Opts) ->
+ {ok, Qs, Req} = read_urlencoded_body(Req0, Opts),
+ case filter(Fields, kvlist_to_map(Fields, Qs)) of
+ {ok, Map} ->
+ {ok, Map, Req};
+ {error, Errors} ->
+ exit({request_error, {read_and_match_urlencoded_body, Errors},
+ 'Urlencoded request body validation constraints failed for the reasons provided.'})
+ end.
+
+%% Multipart.
+
+-spec read_part(Req)
+ -> {ok, cowboy:http_headers(), Req} | {done, Req}
+ when Req::req().
+read_part(Req) ->
+ read_part(Req, #{length => 64000, period => 5000}).
+
+-spec read_part(Req, read_body_opts())
+ -> {ok, cowboy:http_headers(), Req} | {done, Req}
+ when Req::req().
+read_part(Req, Opts) ->
+ case maps:is_key(multipart, Req) of
+ true ->
+ {Data, Req2} = stream_multipart(Req, Opts, headers),
+ read_part(Data, Opts, Req2);
+ false ->
+ read_part(init_multipart(Req), Opts)
+ end.
+
+read_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) ->
+ try cow_multipart:parse_headers(Buffer, Boundary) of
+ more ->
+ {Data, Req2} = stream_multipart(Req, Opts, headers),
+ read_part(<< Buffer/binary, Data/binary >>, Opts, Req2);
+ {more, Buffer2} ->
+ {Data, Req2} = stream_multipart(Req, Opts, headers),
+ read_part(<< Buffer2/binary, Data/binary >>, Opts, Req2);
+ {ok, Headers0, Rest} ->
+ Headers = maps:from_list(Headers0),
+ %% Reject multipart content containing duplicate headers.
+ true = map_size(Headers) =:= length(Headers0),
+ {ok, Headers, Req#{multipart => {Boundary, Rest}}};
+ %% Ignore epilogue.
+ {done, _} ->
+ {done, Req#{multipart => done}}
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, {multipart, headers},
+ 'Malformed body; multipart expected.'
+ }, Stacktrace)
+ end.
+
+-spec read_part_body(Req)
+ -> {ok, binary(), Req} | {more, binary(), Req}
+ when Req::req().
+read_part_body(Req) ->
+ read_part_body(Req, #{}).
+
+-spec read_part_body(Req, read_body_opts())
+ -> {ok, binary(), Req} | {more, binary(), Req}
+ when Req::req().
+read_part_body(Req, Opts) ->
+ case maps:is_key(multipart, Req) of
+ true ->
+ read_part_body(<<>>, Opts, Req, <<>>);
+ false ->
+ read_part_body(init_multipart(Req), Opts)
+ end.
+
+read_part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) ->
+ Length = maps:get(length, Opts, 8000000),
+ case byte_size(Acc) > Length of
+ true ->
+ {more, Acc, Req#{multipart => {Boundary, Buffer}}};
+ false ->
+ {Data, Req2} = stream_multipart(Req, Opts, body),
+ case cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of
+ {ok, Body} ->
+ read_part_body(<<>>, Opts, Req2, << Acc/binary, Body/binary >>);
+ {ok, Body, Rest} ->
+ read_part_body(Rest, Opts, Req2, << Acc/binary, Body/binary >>);
+ done ->
+ {ok, Acc, Req2};
+ {done, Body} ->
+ {ok, << Acc/binary, Body/binary >>, Req2};
+ {done, Body, Rest} ->
+ {ok, << Acc/binary, Body/binary >>,
+ Req2#{multipart => {Boundary, Rest}}}
+ end
+ end.
+
+init_multipart(Req) ->
+ {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req),
+ case lists:keyfind(<<"boundary">>, 1, Params) of
+ {_, Boundary} ->
+ Req#{multipart => {Boundary, <<>>}};
+ false ->
+ exit({request_error, {multipart, boundary},
+ 'Missing boundary parameter for multipart media type.'})
+ end.
+
+stream_multipart(Req=#{multipart := done}, _, _) ->
+ {<<>>, Req};
+stream_multipart(Req=#{multipart := {_, <<>>}}, Opts, Type) ->
+ case read_body(Req, Opts) of
+ {more, Data, Req2} ->
+ {Data, Req2};
+ %% We crash when the data ends unexpectedly.
+ {ok, <<>>, _} ->
+ exit({request_error, {multipart, Type},
+ 'Malformed body; multipart expected.'});
+ {ok, Data, Req2} ->
+ {Data, Req2}
+ end;
+stream_multipart(Req=#{multipart := {Boundary, Buffer}}, _, _) ->
+ {Buffer, Req#{multipart => {Boundary, <<>>}}}.
+
+%% Response.
+
+-spec set_resp_cookie(iodata(), iodata(), Req)
+ -> Req when Req::req().
+set_resp_cookie(Name, Value, Req) ->
+ set_resp_cookie(Name, Value, Req, #{}).
+
+%% The cookie name cannot contain any of the following characters:
+%% =,;\s\t\r\n\013\014
+%%
+%% The cookie value cannot contain any of the following characters:
+%% ,; \t\r\n\013\014
+-spec set_resp_cookie(binary(), iodata(), Req, cow_cookie:cookie_opts())
+ -> Req when Req::req().
+set_resp_cookie(Name, Value, Req, Opts) ->
+ Cookie = cow_cookie:setcookie(Name, Value, Opts),
+ RespCookies = maps:get(resp_cookies, Req, #{}),
+ Req#{resp_cookies => RespCookies#{Name => Cookie}}.
+
+%% @todo We could add has_resp_cookie and delete_resp_cookie now.
+
+-spec set_resp_header(binary(), iodata(), Req)
+ -> Req when Req::req().
+set_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) ->
+ Req#{resp_headers => RespHeaders#{Name => Value}};
+set_resp_header(Name,Value, Req) ->
+ Req#{resp_headers => #{Name => Value}}.
+
+-spec set_resp_headers(cowboy:http_headers(), Req)
+ -> Req when Req::req().
+set_resp_headers(Headers, Req=#{resp_headers := RespHeaders}) ->
+ Req#{resp_headers => maps:merge(RespHeaders, Headers)};
+set_resp_headers(Headers, Req) ->
+ Req#{resp_headers => Headers}.
+
+-spec resp_header(binary(), req()) -> binary() | undefined.
+resp_header(Name, Req) ->
+ resp_header(Name, Req, undefined).
+
+-spec resp_header(binary(), req(), Default)
+ -> binary() | Default when Default::any().
+resp_header(Name, #{resp_headers := Headers}, Default) ->
+ maps:get(Name, Headers, Default);
+resp_header(_, #{}, Default) ->
+ Default.
+
+-spec resp_headers(req()) -> cowboy:http_headers().
+resp_headers(#{resp_headers := RespHeaders}) ->
+ RespHeaders;
+resp_headers(#{}) ->
+ #{}.
+
+-spec set_resp_body(resp_body(), Req) -> Req when Req::req().
+set_resp_body(Body, Req) ->
+ Req#{resp_body => Body}.
+
+-spec has_resp_header(binary(), req()) -> boolean().
+has_resp_header(Name, #{resp_headers := RespHeaders}) ->
+ maps:is_key(Name, RespHeaders);
+has_resp_header(_, _) ->
+ false.
+
+-spec has_resp_body(req()) -> boolean().
+has_resp_body(#{resp_body := {sendfile, _, _, _}}) ->
+ true;
+has_resp_body(#{resp_body := RespBody}) ->
+ iolist_size(RespBody) > 0;
+has_resp_body(_) ->
+ false.
+
+-spec delete_resp_header(binary(), Req)
+ -> Req when Req::req().
+delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->
+ Req#{resp_headers => maps:remove(Name, RespHeaders)};
+%% There are no resp headers so we have nothing to delete.
+delete_resp_header(_, Req) ->
+ Req.
+
+-spec inform(cowboy:http_status(), req()) -> ok.
+inform(Status, Req) ->
+ inform(Status, #{}, Req).
+
+-spec inform(cowboy:http_status(), cowboy:http_headers(), req()) -> ok.
+inform(_, _, #{has_sent_resp := _}) ->
+ error(function_clause); %% @todo Better error message.
+inform(Status, Headers, Req) when is_integer(Status); is_binary(Status) ->
+ cast({inform, Status, Headers}, Req).
+
+-spec reply(cowboy:http_status(), Req) -> Req when Req::req().
+reply(Status, Req) ->
+ reply(Status, #{}, Req).
+
+-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
+ -> Req when Req::req().
+reply(Status, Headers, Req=#{resp_body := Body}) ->
+ reply(Status, Headers, Body, Req);
+reply(Status, Headers, Req) ->
+ reply(Status, Headers, <<>>, Req).
+
+-spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req)
+ -> Req when Req::req().
+reply(_, _, _, #{has_sent_resp := _}) ->
+ error(function_clause); %% @todo Better error message.
+reply(Status, Headers, {sendfile, _, 0, _}, Req)
+ when is_integer(Status); is_binary(Status) ->
+ do_reply(Status, Headers#{
+ <<"content-length">> => <<"0">>
+ }, <<>>, Req);
+reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req)
+ when is_integer(Status); is_binary(Status) ->
+ do_reply(Status, Headers#{
+ <<"content-length">> => integer_to_binary(Len)
+ }, SendFile, Req);
+%% 204 responses must not include content-length. 304 responses may
+%% but only when set explicitly. (RFC7230 3.3.1, RFC7230 3.3.2)
+%% Neither status code must include a response body. (RFC7230 3.3)
+reply(Status, Headers, Body, Req)
+ when Status =:= 204; Status =:= 304 ->
+ 0 = iolist_size(Body),
+ do_reply(Status, Headers, Body, Req);
+reply(Status = <<"204",_/bits>>, Headers, Body, Req) ->
+ 0 = iolist_size(Body),
+ do_reply(Status, Headers, Body, Req);
+reply(Status = <<"304",_/bits>>, Headers, Body, Req) ->
+ 0 = iolist_size(Body),
+ do_reply(Status, Headers, Body, Req);
+reply(Status, Headers, Body, Req)
+ when is_integer(Status); is_binary(Status) ->
+ do_reply(Status, Headers#{
+ <<"content-length">> => integer_to_binary(iolist_size(Body))
+ }, Body, Req).
+
+%% Don't send any body for HEAD responses. While the protocol code is
+%% supposed to enforce this rule, we prefer to avoid copying too much
+%% data around if we can avoid it.
+do_reply(Status, Headers, _, Req=#{method := <<"HEAD">>}) ->
+ cast({response, Status, response_headers(Headers, Req), <<>>}, Req),
+ done_replying(Req, true);
+do_reply(Status, Headers, Body, Req) ->
+ cast({response, Status, response_headers(Headers, Req), Body}, Req),
+ done_replying(Req, true).
+
+done_replying(Req, HasSentResp) ->
+ maps:without([resp_cookies, resp_headers, resp_body], Req#{has_sent_resp => HasSentResp}).
+
+-spec stream_reply(cowboy:http_status(), Req) -> Req when Req::req().
+stream_reply(Status, Req) ->
+ stream_reply(Status, #{}, Req).
+
+-spec stream_reply(cowboy:http_status(), cowboy:http_headers(), Req)
+ -> Req when Req::req().
+stream_reply(_, _, #{has_sent_resp := _}) ->
+ error(function_clause);
+%% 204 and 304 responses must NOT send a body. We therefore
+%% transform the call to a full response and expect the user
+%% to NOT call stream_body/3 afterwards. (RFC7230 3.3)
+stream_reply(Status = 204, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status = <<"204",_/bits>>, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status = 304, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status = <<"304",_/bits>>, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status, Headers=#{}, Req) when is_integer(Status); is_binary(Status) ->
+ cast({headers, Status, response_headers(Headers, Req)}, Req),
+ done_replying(Req, headers).
+
+-spec stream_body(resp_body(), fin | nofin, req()) -> ok.
+%% Error out if headers were not sent.
+%% Don't send any body for HEAD responses.
+stream_body(_, _, #{method := <<"HEAD">>, has_sent_resp := headers}) ->
+ ok;
+%% Don't send a message if the data is empty, except for the
+%% very last message with IsFin=fin. When using sendfile this
+%% is converted to a data tuple, however.
+stream_body({sendfile, _, 0, _}, nofin, _) ->
+ ok;
+stream_body({sendfile, _, 0, _}, IsFin=fin, Req=#{has_sent_resp := headers}) ->
+ stream_body({data, self(), IsFin, <<>>}, Req);
+stream_body({sendfile, O, B, P}, IsFin, Req=#{has_sent_resp := headers})
+ when is_integer(O), O >= 0, is_integer(B), B > 0 ->
+ stream_body({data, self(), IsFin, {sendfile, O, B, P}}, Req);
+stream_body(Data, IsFin=nofin, Req=#{has_sent_resp := headers})
+ when not is_tuple(Data) ->
+ case iolist_size(Data) of
+ 0 -> ok;
+ _ -> stream_body({data, self(), IsFin, Data}, Req)
+ end;
+stream_body(Data, IsFin, Req=#{has_sent_resp := headers})
+ when not is_tuple(Data) ->
+ stream_body({data, self(), IsFin, Data}, Req).
+
+%% @todo Do we need a timeout?
+stream_body(Msg, Req=#{pid := Pid}) ->
+ cast(Msg, Req),
+ receive {data_ack, Pid} -> ok end.
+
+-spec stream_events(cow_sse:event() | [cow_sse:event()], fin | nofin, req()) -> ok.
+stream_events(Event, IsFin, Req) when is_map(Event) ->
+ stream_events([Event], IsFin, Req);
+stream_events(Events, IsFin, Req=#{has_sent_resp := headers}) ->
+ stream_body({data, self(), IsFin, cow_sse:events(Events)}, Req).
+
+-spec stream_trailers(cowboy:http_headers(), req()) -> ok.
+stream_trailers(Trailers, Req=#{has_sent_resp := headers}) ->
+ cast({trailers, Trailers}, Req).
+
+-spec push(iodata(), cowboy:http_headers(), req()) -> ok.
+push(Path, Headers, Req) ->
+ push(Path, Headers, Req, #{}).
+
+%% @todo Optimization: don't send anything at all for HTTP/1.0 and HTTP/1.1.
+%% @todo Path, Headers, Opts, everything should be in proper binary,
+%% or normalized when creating the Req object.
+-spec push(iodata(), cowboy:http_headers(), req(), push_opts()) -> ok.
+push(Path, Headers, Req=#{scheme := Scheme0, host := Host0, port := Port0}, Opts) ->
+ Method = maps:get(method, Opts, <<"GET">>),
+ Scheme = maps:get(scheme, Opts, Scheme0),
+ Host = maps:get(host, Opts, Host0),
+ Port = maps:get(port, Opts, Port0),
+ Qs = maps:get(qs, Opts, <<>>),
+ cast({push, Method, Scheme, Host, Port, Path, Qs, Headers}, Req).
+
+%% Stream handlers.
+
+-spec cast(any(), req()) -> ok.
+cast(Msg, #{pid := Pid, streamid := StreamID}) ->
+ Pid ! {{Pid, StreamID}, Msg},
+ ok.
+
+%% Internal.
+
+%% @todo What about set-cookie headers set through set_resp_header or reply?
+-spec response_headers(Headers, req()) -> Headers when Headers::cowboy:http_headers().
+response_headers(Headers0, Req) ->
+ RespHeaders = maps:get(resp_headers, Req, #{}),
+ Headers = maps:merge(#{
+ <<"date">> => cowboy_clock:rfc1123(),
+ <<"server">> => <<"Cowboy">>
+ }, maps:merge(RespHeaders, Headers0)),
+ %% The set-cookie header is special; we can only send one cookie per header.
+ %% We send the list of values for many cookies in one key of the map,
+ %% and let the protocols deal with it directly.
+ case maps:get(resp_cookies, Req, undefined) of
+ undefined -> Headers;
+ RespCookies -> Headers#{<<"set-cookie">> => maps:values(RespCookies)}
+ end.
+
+%% Create map, convert keys to atoms and group duplicate keys into lists.
+%% Keys that are not found in the user provided list are entirely skipped.
+%% @todo Can probably be done directly while parsing.
+kvlist_to_map(Fields, KvList) ->
+ Keys = [case K of
+ {Key, _} -> Key;
+ {Key, _, _} -> Key;
+ Key -> Key
+ end || K <- Fields],
+ kvlist_to_map(Keys, KvList, #{}).
+
+kvlist_to_map(_, [], Map) ->
+ Map;
+kvlist_to_map(Keys, [{Key, Value}|Tail], Map) ->
+ try binary_to_existing_atom(Key, utf8) of
+ Atom ->
+ case lists:member(Atom, Keys) of
+ true ->
+ case maps:find(Atom, Map) of
+ {ok, MapValue} when is_list(MapValue) ->
+ kvlist_to_map(Keys, Tail,
+ Map#{Atom => [Value|MapValue]});
+ {ok, MapValue} ->
+ kvlist_to_map(Keys, Tail,
+ Map#{Atom => [Value, MapValue]});
+ error ->
+ kvlist_to_map(Keys, Tail,
+ Map#{Atom => Value})
+ end;
+ false ->
+ kvlist_to_map(Keys, Tail, Map)
+ end
+ catch error:badarg ->
+ kvlist_to_map(Keys, Tail, Map)
+ end.
+
+filter(Fields, Map0) ->
+ filter(Fields, Map0, #{}).
+
+%% Loop through fields, if value is missing and no default,
+%% record the error; else if value is missing and has a
+%% default, set default; otherwise apply constraints. If
+%% constraint fails, record the error.
+%%
+%% When there is an error at the end, crash.
+filter([], Map, Errors) ->
+ case maps:size(Errors) of
+ 0 -> {ok, Map};
+ _ -> {error, Errors}
+ end;
+filter([{Key, Constraints}|Tail], Map, Errors) ->
+ filter_constraints(Tail, Map, Errors, Key, maps:get(Key, Map), Constraints);
+filter([{Key, Constraints, Default}|Tail], Map, Errors) ->
+ case maps:find(Key, Map) of
+ {ok, Value} ->
+ filter_constraints(Tail, Map, Errors, Key, Value, Constraints);
+ error ->
+ filter(Tail, Map#{Key => Default}, Errors)
+ end;
+filter([Key|Tail], Map, Errors) ->
+ case maps:is_key(Key, Map) of
+ true ->
+ filter(Tail, Map, Errors);
+ false ->
+ filter(Tail, Map, Errors#{Key => required})
+ end.
+
+filter_constraints(Tail, Map, Errors, Key, Value0, Constraints) ->
+ case cowboy_constraints:validate(Value0, Constraints) of
+ {ok, Value} ->
+ filter(Tail, Map#{Key => Value}, Errors);
+ {error, Reason} ->
+ filter(Tail, Map, Errors#{Key => Reason})
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_rest.erl b/server/_build/default/lib/cowboy/src/cowboy_rest.erl
new file mode 100644
index 0000000..7d0fe80
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_rest.erl
@@ -0,0 +1,1637 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Originally based on the Webmachine Diagram from Alan Dean and
+%% Justin Sheehy.
+-module(cowboy_rest).
+-behaviour(cowboy_sub_protocol).
+
+-export([upgrade/4]).
+-export([upgrade/5]).
+
+-type switch_handler() :: {switch_handler, module()}
+ | {switch_handler, module(), any()}.
+
+%% Common handler callbacks.
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback terminate(any(), cowboy_req:req(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+%% REST handler callbacks.
+
+-callback allowed_methods(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([allowed_methods/2]).
+
+-callback allow_missing_post(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([allow_missing_post/2]).
+
+-callback charsets_provided(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([charsets_provided/2]).
+
+-callback content_types_accepted(Req, State)
+ -> {[{'*' | binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([content_types_accepted/2]).
+
+-callback content_types_provided(Req, State)
+ -> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([content_types_provided/2]).
+
+-callback delete_completed(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([delete_completed/2]).
+
+-callback delete_resource(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([delete_resource/2]).
+
+-callback expires(Req, State)
+ -> {calendar:datetime() | binary() | undefined, Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([expires/2]).
+
+-callback forbidden(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([forbidden/2]).
+
+-callback generate_etag(Req, State)
+ -> {binary() | {weak | strong, binary()}, Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([generate_etag/2]).
+
+-callback is_authorized(Req, State)
+ -> {true | {false, iodata()}, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([is_authorized/2]).
+
+-callback is_conflict(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([is_conflict/2]).
+
+-callback known_methods(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([known_methods/2]).
+
+-callback languages_provided(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([languages_provided/2]).
+
+-callback last_modified(Req, State)
+ -> {calendar:datetime(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([last_modified/2]).
+
+-callback malformed_request(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([malformed_request/2]).
+
+-callback moved_permanently(Req, State)
+ -> {{true, iodata()} | false, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([moved_permanently/2]).
+
+-callback moved_temporarily(Req, State)
+ -> {{true, iodata()} | false, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([moved_temporarily/2]).
+
+-callback multiple_choices(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([multiple_choices/2]).
+
+-callback options(Req, State)
+ -> {ok, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([options/2]).
+
+-callback previously_existed(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([previously_existed/2]).
+
+-callback range_satisfiable(Req, State)
+ -> {boolean() | {false, non_neg_integer() | iodata()}, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([range_satisfiable/2]).
+
+-callback ranges_provided(Req, State)
+ -> {[{binary(), atom()}], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([ranges_provided/2]).
+
+-callback rate_limited(Req, State)
+ -> {{true, non_neg_integer() | calendar:datetime()} | false, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([rate_limited/2]).
+
+-callback resource_exists(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([resource_exists/2]).
+
+-callback service_available(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([service_available/2]).
+
+-callback uri_too_long(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([uri_too_long/2]).
+
+-callback valid_content_headers(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([valid_content_headers/2]).
+
+-callback valid_entity_length(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([valid_entity_length/2]).
+
+-callback variances(Req, State)
+ -> {[binary()], Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([variances/2]).
+
+%% End of REST callbacks. Whew!
+
+-record(state, {
+ method = undefined :: binary(),
+
+ %% Handler.
+ handler :: atom(),
+ handler_state :: any(),
+
+ %% Allowed methods. Only used for OPTIONS requests.
+ allowed_methods :: [binary()] | undefined,
+
+ %% Media type.
+ content_types_p = [] ::
+ [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
+ atom()}],
+ content_type_a :: undefined
+ | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
+ atom()},
+
+ %% Language.
+ languages_p = [] :: [binary()],
+ language_a :: undefined | binary(),
+
+ %% Charset.
+ charsets_p = undefined :: undefined | [binary()],
+ charset_a :: undefined | binary(),
+
+ %% Range units.
+ ranges_a = [] :: [{binary(), atom()}],
+
+ %% Whether the resource exists.
+ exists = false :: boolean(),
+
+ %% Cached resource calls.
+ etag :: undefined | no_call | {strong | weak, binary()},
+ last_modified :: undefined | no_call | calendar:datetime(),
+ expires :: undefined | no_call | calendar:datetime() | binary()
+}).
+
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req0, Env, Handler, HandlerState0) ->
+ Method = cowboy_req:method(Req0),
+ case service_available(Req0, #state{method=Method,
+ handler=Handler, handler_state=HandlerState0}) of
+ {ok, Req, Result} ->
+ {ok, Req, Env#{result => Result}};
+ {Mod, Req, HandlerState} ->
+ Mod:upgrade(Req, Env, Handler, HandlerState);
+ {Mod, Req, HandlerState, Opts} ->
+ Mod:upgrade(Req, Env, Handler, HandlerState, Opts)
+ end.
+
+-spec upgrade(Req, Env, module(), any(), any())
+ -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+%% cowboy_rest takes no options.
+upgrade(Req, Env, Handler, HandlerState, _Opts) ->
+ upgrade(Req, Env, Handler, HandlerState).
+
+service_available(Req, State) ->
+ expect(Req, State, service_available, true, fun known_methods/2, 503).
+
+%% known_methods/2 should return a list of binary methods.
+known_methods(Req, State=#state{method=Method}) ->
+ case call(Req, State, known_methods) of
+ no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
+ Method =:= <<"POST">>; Method =:= <<"PUT">>;
+ Method =:= <<"PATCH">>; Method =:= <<"DELETE">>;
+ Method =:= <<"OPTIONS">> ->
+ next(Req, State, fun uri_too_long/2);
+ no_call ->
+ next(Req, State, 501);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {List, Req2, State2} ->
+ case lists:member(Method, List) of
+ true -> next(Req2, State2, fun uri_too_long/2);
+ false -> next(Req2, State2, 501)
+ end
+ end.
+
+uri_too_long(Req, State) ->
+ expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
+
+%% allowed_methods/2 should return a list of binary methods.
+allowed_methods(Req, State=#state{method=Method}) ->
+ case call(Req, State, allowed_methods) of
+ no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
+ next(Req, State, fun malformed_request/2);
+ no_call when Method =:= <<"OPTIONS">> ->
+ next(Req, State#state{allowed_methods=
+ [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]},
+ fun malformed_request/2);
+ no_call ->
+ method_not_allowed(Req, State,
+ [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {List, Req2, State2} ->
+ case lists:member(Method, List) of
+ true when Method =:= <<"OPTIONS">> ->
+ next(Req2, State2#state{allowed_methods=List},
+ fun malformed_request/2);
+ true ->
+ next(Req2, State2, fun malformed_request/2);
+ false ->
+ method_not_allowed(Req2, State2, List)
+ end
+ end.
+
+method_not_allowed(Req, State, []) ->
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
+ respond(Req2, State, 405);
+method_not_allowed(Req, State, Methods) ->
+ << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>,
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
+ respond(Req2, State, 405).
+
+malformed_request(Req, State) ->
+ expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
+
+%% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
+is_authorized(Req, State) ->
+ case call(Req, State, is_authorized) of
+ no_call ->
+ forbidden(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {true, Req2, State2} ->
+ forbidden(Req2, State2);
+ {{false, AuthHead}, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"www-authenticate">>, AuthHead, Req2),
+ respond(Req3, State2, 401)
+ end.
+
+forbidden(Req, State) ->
+ expect(Req, State, forbidden, false, fun rate_limited/2, 403).
+
+rate_limited(Req, State) ->
+ case call(Req, State, rate_limited) of
+ no_call ->
+ valid_content_headers(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {false, Req2, State2} ->
+ valid_content_headers(Req2, State2);
+ {{true, RetryAfter0}, Req2, State2} ->
+ RetryAfter = if
+ is_integer(RetryAfter0), RetryAfter0 >= 0 ->
+ integer_to_binary(RetryAfter0);
+ is_tuple(RetryAfter0) ->
+ cowboy_clock:rfc1123(RetryAfter0)
+ end,
+ Req3 = cowboy_req:set_resp_header(<<"retry-after">>, RetryAfter, Req2),
+ respond(Req3, State2, 429)
+ end.
+
+valid_content_headers(Req, State) ->
+ expect(Req, State, valid_content_headers, true,
+ fun valid_entity_length/2, 501).
+
+valid_entity_length(Req, State) ->
+ expect(Req, State, valid_entity_length, true, fun options/2, 413).
+
+%% If you need to add additional headers to the response at this point,
+%% you should do it directly in the options/2 call using set_resp_headers.
+options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) ->
+ case call(Req, State, options) of
+ no_call when Methods =:= [] ->
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
+ respond(Req2, State, 200);
+ no_call ->
+ << ", ", Allow/binary >>
+ = << << ", ", M/binary >> || M <- Methods >>,
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
+ respond(Req2, State, 200);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {ok, Req2, State2} ->
+ respond(Req2, State2, 200)
+ end;
+options(Req, State) ->
+ content_types_provided(Req, State).
+
+%% content_types_provided/2 should return a list of content types and their
+%% associated callback function as a tuple: {{Type, SubType, Params}, Fun}.
+%% Type and SubType are the media type as binary. Params is a list of
+%% Key/Value tuple, with Key and Value a binary. Fun is the name of the
+%% callback that will be used to return the content of the response. It is
+%% given as an atom.
+%%
+%% An example of such return value would be:
+%% {{<<"text">>, <<"html">>, []}, to_html}
+%%
+%% Note that it is also possible to return a binary content type that will
+%% then be parsed by Cowboy. However note that while this may make your
+%% resources a little more readable, this is a lot less efficient.
+%%
+%% An example of such return value would be:
+%% {<<"text/html">>, to_html}
+content_types_provided(Req, State) ->
+ case call(Req, State, content_types_provided) of
+ no_call ->
+ State2 = State#state{
+ content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
+ try cowboy_req:parse_header(<<"accept">>, Req) of
+ undefined ->
+ languages_provided(
+ Req#{media_type => {<<"text">>, <<"html">>, []}},
+ State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
+ Accept ->
+ choose_media_type(Req, State2, prioritize_accept(Accept))
+ catch _:_ ->
+ respond(Req, State2, 400)
+ end;
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {[], Req2, State2} ->
+ not_acceptable(Req2, State2);
+ {CTP, Req2, State2} ->
+ CTP2 = [normalize_content_types(P) || P <- CTP],
+ State3 = State2#state{content_types_p=CTP2},
+ try cowboy_req:parse_header(<<"accept">>, Req2) of
+ undefined ->
+ {PMT0, _Fun} = HeadCTP = hd(CTP2),
+ %% We replace the wildcard by an empty list of parameters.
+ PMT = case PMT0 of
+ {Type, SubType, '*'} -> {Type, SubType, []};
+ _ -> PMT0
+ end,
+ languages_provided(
+ Req2#{media_type => PMT},
+ State3#state{content_type_a=HeadCTP});
+ Accept ->
+ choose_media_type(Req2, State3, prioritize_accept(Accept))
+ catch _:_ ->
+ respond(Req2, State3, 400)
+ end
+ end.
+
+normalize_content_types({ContentType, Callback})
+ when is_binary(ContentType) ->
+ {cow_http_hd:parse_content_type(ContentType), Callback};
+normalize_content_types(Normalized) ->
+ Normalized.
+
+prioritize_accept(Accept) ->
+ lists:sort(
+ fun ({MediaTypeA, Quality, _AcceptParamsA},
+ {MediaTypeB, Quality, _AcceptParamsB}) ->
+ %% Same quality, check precedence in more details.
+ prioritize_mediatype(MediaTypeA, MediaTypeB);
+ ({_MediaTypeA, QualityA, _AcceptParamsA},
+ {_MediaTypeB, QualityB, _AcceptParamsB}) ->
+ %% Just compare the quality.
+ QualityA > QualityB
+ end, Accept).
+
+%% Media ranges can be overridden by more specific media ranges or
+%% specific media types. If more than one media range applies to a given
+%% type, the most specific reference has precedence.
+%%
+%% We always choose B over A when we can't decide between the two.
+prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->
+ case TypeB of
+ TypeA ->
+ case SubTypeB of
+ SubTypeA -> length(ParamsA) > length(ParamsB);
+ <<"*">> -> true;
+ _Any -> false
+ end;
+ <<"*">> -> true;
+ _Any -> false
+ end.
+
+%% Ignoring the rare AcceptParams. Not sure what should be done about them.
+choose_media_type(Req, State, []) ->
+ not_acceptable(Req, State);
+choose_media_type(Req, State=#state{content_types_p=CTP},
+ [MediaType|Tail]) ->
+ match_media_type(Req, State, Tail, CTP, MediaType).
+
+match_media_type(Req, State, Accept, [], _MediaType) ->
+ choose_media_type(Req, State, Accept);
+match_media_type(Req, State, Accept, CTP,
+ MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
+ match_media_type_params(Req, State, Accept, CTP, MediaType);
+match_media_type(Req, State, Accept,
+ CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
+ MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
+ when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
+ match_media_type_params(Req, State, Accept, CTP, MediaType);
+match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
+ match_media_type(Req, State, Accept, Tail, MediaType).
+
+match_media_type_params(Req, State, Accept,
+ [Provided = {{TP, STP, '*'}, _Fun}|Tail],
+ MediaType = {{TA, _STA, Params_A0}, _QA, _APA}) ->
+ case lists:keytake(<<"charset">>, 1, Params_A0) of
+ {value, {_, Charset}, Params_A} when TA =:= <<"text">> ->
+ %% When we match against a wildcard, the media type is text
+ %% and has a charset parameter, we call charsets_provided
+ %% and check that the charset is provided. If the callback
+ %% is not exported, we accept inconditionally but ignore
+ %% the given charset so as to not send a wrong value back.
+ case call(Req, State, charsets_provided) of
+ no_call ->
+ languages_provided(Req#{media_type => {TP, STP, Params_A0}},
+ State#state{content_type_a=Provided});
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {CP, Req2, State2} ->
+ State3 = State2#state{charsets_p=CP},
+ case lists:member(Charset, CP) of
+ false ->
+ match_media_type(Req2, State3, Accept, Tail, MediaType);
+ true ->
+ languages_provided(Req2#{media_type => {TP, STP, Params_A}},
+ State3#state{content_type_a=Provided,
+ charset_a=Charset})
+ end
+ end;
+ _ ->
+ languages_provided(Req#{media_type => {TP, STP, Params_A0}},
+ State#state{content_type_a=Provided})
+ end;
+match_media_type_params(Req, State, Accept,
+ [Provided = {PMT = {TP, STP, Params_P0}, Fun}|Tail],
+ MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
+ case lists:sort(Params_P0) =:= lists:sort(Params_A) of
+ true when TP =:= <<"text">> ->
+ %% When a charset was provided explicitly in both the charset header
+ %% and the media types provided and the negotiation is successful,
+ %% we keep the charset and don't call charsets_provided. This only
+ %% applies to text media types, however.
+ {Charset, Params_P} = case lists:keytake(<<"charset">>, 1, Params_P0) of
+ false -> {undefined, Params_P0};
+ {value, {_, Charset0}, Params_P1} -> {Charset0, Params_P1}
+ end,
+ languages_provided(Req#{media_type => {TP, STP, Params_P}},
+ State#state{content_type_a={{TP, STP, Params_P}, Fun},
+ charset_a=Charset});
+ true ->
+ languages_provided(Req#{media_type => PMT},
+ State#state{content_type_a=Provided});
+ false ->
+ match_media_type(Req, State, Accept, Tail, MediaType)
+ end.
+
+%% languages_provided should return a list of binary values indicating
+%% which languages are accepted by the resource.
+%%
+%% @todo I suppose we should also ask the resource if it wants to
+%% set a language itself or if it wants it to be automatically chosen.
+languages_provided(Req, State) ->
+ case call(Req, State, languages_provided) of
+ no_call ->
+ charsets_provided(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {[], Req2, State2} ->
+ not_acceptable(Req2, State2);
+ {LP, Req2, State2} ->
+ State3 = State2#state{languages_p=LP},
+ case cowboy_req:parse_header(<<"accept-language">>, Req2) of
+ undefined ->
+ set_language(Req2, State3#state{language_a=hd(LP)});
+ AcceptLanguage ->
+ AcceptLanguage2 = prioritize_languages(AcceptLanguage),
+ choose_language(Req2, State3, AcceptLanguage2)
+ end
+ end.
+
+%% A language-range matches a language-tag if it exactly equals the tag,
+%% or if it exactly equals a prefix of the tag such that the first tag
+%% character following the prefix is "-". The special range "*", if
+%% present in the Accept-Language field, matches every tag not matched
+%% by any other range present in the Accept-Language field.
+%%
+%% @todo The last sentence probably means we should always put '*'
+%% at the end of the list.
+prioritize_languages(AcceptLanguages) ->
+ lists:sort(
+ fun ({_TagA, QualityA}, {_TagB, QualityB}) ->
+ QualityA > QualityB
+ end, AcceptLanguages).
+
+choose_language(Req, State, []) ->
+ not_acceptable(Req, State);
+choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->
+ match_language(Req, State, Tail, LP, Language).
+
+match_language(Req, State, Accept, [], _Language) ->
+ choose_language(Req, State, Accept);
+match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->
+ set_language(Req, State#state{language_a=Provided});
+match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->
+ set_language(Req, State#state{language_a=Provided});
+match_language(Req, State, Accept, [Provided|Tail],
+ Language = {Tag, _Quality}) ->
+ Length = byte_size(Tag),
+ case Provided of
+ << Tag:Length/binary, $-, _Any/bits >> ->
+ set_language(Req, State#state{language_a=Provided});
+ _Any ->
+ match_language(Req, State, Accept, Tail, Language)
+ end.
+
+set_language(Req, State=#state{language_a=Language}) ->
+ Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
+ charsets_provided(Req2#{language => Language}, State).
+
+%% charsets_provided should return a list of binary values indicating
+%% which charsets are accepted by the resource.
+%%
+%% A charset may have been selected while negotiating the accept header.
+%% There's no need to select one again.
+charsets_provided(Req, State=#state{charset_a=Charset})
+ when Charset =/= undefined ->
+ set_content_type(Req, State);
+%% If charsets_p is defined, use it instead of calling charsets_provided
+%% again. We also call this clause during normal execution to avoid
+%% duplicating code.
+charsets_provided(Req, State=#state{charsets_p=[]}) ->
+ not_acceptable(Req, State);
+charsets_provided(Req, State=#state{charsets_p=CP})
+ when CP =/= undefined ->
+ case cowboy_req:parse_header(<<"accept-charset">>, Req) of
+ undefined ->
+ set_content_type(Req, State#state{charset_a=hd(CP)});
+ AcceptCharset0 ->
+ AcceptCharset = prioritize_charsets(AcceptCharset0),
+ choose_charset(Req, State, AcceptCharset)
+ end;
+charsets_provided(Req, State) ->
+ case call(Req, State, charsets_provided) of
+ no_call ->
+ set_content_type(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {CP, Req2, State2} ->
+ charsets_provided(Req2, State2#state{charsets_p=CP})
+ end.
+
+prioritize_charsets(AcceptCharsets) ->
+ lists:sort(
+ fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->
+ QualityA > QualityB
+ end, AcceptCharsets).
+
+choose_charset(Req, State, []) ->
+ not_acceptable(Req, State);
+%% A q-value of 0 means not acceptable.
+choose_charset(Req, State, [{_, 0}|Tail]) ->
+ choose_charset(Req, State, Tail);
+choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
+ match_charset(Req, State, Tail, CP, Charset).
+
+match_charset(Req, State, Accept, [], _Charset) ->
+ choose_charset(Req, State, Accept);
+match_charset(Req, State, _Accept, [Provided|_], {<<"*">>, _}) ->
+ set_content_type(Req, State#state{charset_a=Provided});
+match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->
+ set_content_type(Req, State#state{charset_a=Provided});
+match_charset(Req, State, Accept, [_|Tail], Charset) ->
+ match_charset(Req, State, Accept, Tail, Charset).
+
+set_content_type(Req, State=#state{
+ content_type_a={{Type, SubType, Params}, _Fun},
+ charset_a=Charset}) ->
+ ParamsBin = set_content_type_build_params(Params, []),
+ ContentType = [Type, <<"/">>, SubType, ParamsBin],
+ ContentType2 = case {Type, Charset} of
+ {<<"text">>, Charset} when Charset =/= undefined ->
+ [ContentType, <<"; charset=">>, Charset];
+ _ ->
+ ContentType
+ end,
+ Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
+ encodings_provided(Req2#{charset => Charset}, State).
+
+set_content_type_build_params('*', []) ->
+ <<>>;
+set_content_type_build_params([], []) ->
+ <<>>;
+set_content_type_build_params([], Acc) ->
+ lists:reverse(Acc);
+set_content_type_build_params([{Attr, Value}|Tail], Acc) ->
+ set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]).
+
+%% @todo Match for identity as we provide nothing else for now.
+%% @todo Don't forget to set the Content-Encoding header when we reply a body
+%% and the found encoding is something other than identity.
+encodings_provided(Req, State) ->
+ ranges_provided(Req, State).
+
+not_acceptable(Req, State) ->
+ respond(Req, State, 406).
+
+ranges_provided(Req, State) ->
+ case call(Req, State, ranges_provided) of
+ no_call ->
+ variances(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {[], Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, <<"none">>, Req2),
+ variances(Req3, State2#state{ranges_a=[]});
+ {RP, Req2, State2} ->
+ <<", ", AcceptRanges/binary>> = <<<<", ", R/binary>> || {R, _} <- RP>>,
+ Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, AcceptRanges, Req2),
+ variances(Req3, State2#state{ranges_a=RP})
+ end.
+
+%% variances/2 should return a list of headers that will be added
+%% to the Vary response header. The Accept, Accept-Language,
+%% Accept-Charset and Accept-Encoding headers do not need to be
+%% specified.
+%%
+%% @todo Do Accept-Encoding too when we handle it.
+%% @todo Does the order matter?
+variances(Req, State=#state{content_types_p=CTP,
+ languages_p=LP, charsets_p=CP}) ->
+ Variances = case CTP of
+ [] -> [];
+ [_] -> [];
+ [_|_] -> [<<"accept">>]
+ end,
+ Variances2 = case LP of
+ [] -> Variances;
+ [_] -> Variances;
+ [_|_] -> [<<"accept-language">>|Variances]
+ end,
+ Variances3 = case CP of
+ undefined -> Variances2;
+ [] -> Variances2;
+ [_] -> Variances2;
+ [_|_] -> [<<"accept-charset">>|Variances2]
+ end,
+ try variances(Req, State, Variances3) of
+ {Variances4, Req2, State2} ->
+ case [[<<", ">>, V] || V <- Variances4] of
+ [] ->
+ resource_exists(Req2, State2);
+ [[<<", ">>, H]|Variances5] ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"vary">>, [H|Variances5], Req2),
+ resource_exists(Req3, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+variances(Req, State, Variances) ->
+ case unsafe_call(Req, State, variances) of
+ no_call ->
+ {Variances, Req, State};
+ {HandlerVariances, Req2, State2} ->
+ {Variances ++ HandlerVariances, Req2, State2}
+ end.
+
+resource_exists(Req, State) ->
+ expect(Req, State, resource_exists, true,
+ fun if_match_exists/2, fun if_match_must_not_exist/2).
+
+if_match_exists(Req, State) ->
+ State2 = State#state{exists=true},
+ case cowboy_req:parse_header(<<"if-match">>, Req) of
+ undefined ->
+ if_unmodified_since_exists(Req, State2);
+ '*' ->
+ if_unmodified_since_exists(Req, State2);
+ ETagsList ->
+ if_match(Req, State2, ETagsList)
+ end.
+
+if_match(Req, State, EtagsList) ->
+ try generate_etag(Req, State) of
+ %% Strong Etag comparison: weak Etag never matches.
+ {{weak, _}, Req2, State2} ->
+ precondition_failed(Req2, State2);
+ {Etag, Req2, State2} ->
+ case lists:member(Etag, EtagsList) of
+ true -> if_none_match_exists(Req2, State2);
+ %% Etag may be `undefined' which cannot be a member.
+ false -> precondition_failed(Req2, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+if_match_must_not_exist(Req, State) ->
+ case cowboy_req:header(<<"if-match">>, Req) of
+ undefined -> is_put_to_missing_resource(Req, State);
+ _ -> precondition_failed(Req, State)
+ end.
+
+if_unmodified_since_exists(Req, State) ->
+ try cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
+ undefined ->
+ if_none_match_exists(Req, State);
+ IfUnmodifiedSince ->
+ if_unmodified_since(Req, State, IfUnmodifiedSince)
+ catch _:_ ->
+ if_none_match_exists(Req, State)
+ end.
+
+%% If LastModified is the atom 'no_call', we continue.
+if_unmodified_since(Req, State, IfUnmodifiedSince) ->
+ try last_modified(Req, State) of
+ {LastModified, Req2, State2} ->
+ case LastModified > IfUnmodifiedSince of
+ true -> precondition_failed(Req2, State2);
+ false -> if_none_match_exists(Req2, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+if_none_match_exists(Req, State) ->
+ case cowboy_req:parse_header(<<"if-none-match">>, Req) of
+ undefined ->
+ if_modified_since_exists(Req, State);
+ '*' ->
+ precondition_is_head_get(Req, State);
+ EtagsList ->
+ if_none_match(Req, State, EtagsList)
+ end.
+
+if_none_match(Req, State, EtagsList) ->
+ try generate_etag(Req, State) of
+ {Etag, Req2, State2} ->
+ case Etag of
+ undefined ->
+ precondition_failed(Req2, State2);
+ Etag ->
+ case is_weak_match(Etag, EtagsList) of
+ true -> precondition_is_head_get(Req2, State2);
+ false -> method(Req2, State2)
+ end
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% Weak Etag comparison: only check the opaque tag.
+is_weak_match(_, []) ->
+ false;
+is_weak_match({_, Tag}, [{_, Tag}|_]) ->
+ true;
+is_weak_match(Etag, [_|Tail]) ->
+ is_weak_match(Etag, Tail).
+
+precondition_is_head_get(Req, State=#state{method=Method})
+ when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
+ not_modified(Req, State);
+precondition_is_head_get(Req, State) ->
+ precondition_failed(Req, State).
+
+if_modified_since_exists(Req, State) ->
+ try cowboy_req:parse_header(<<"if-modified-since">>, Req) of
+ undefined ->
+ method(Req, State);
+ IfModifiedSince ->
+ if_modified_since_now(Req, State, IfModifiedSince)
+ catch _:_ ->
+ method(Req, State)
+ end.
+
+if_modified_since_now(Req, State, IfModifiedSince) ->
+ case IfModifiedSince > erlang:universaltime() of
+ true -> method(Req, State);
+ false -> if_modified_since(Req, State, IfModifiedSince)
+ end.
+
+if_modified_since(Req, State, IfModifiedSince) ->
+ try last_modified(Req, State) of
+ {undefined, Req2, State2} ->
+ method(Req2, State2);
+ {LastModified, Req2, State2} ->
+ case LastModified > IfModifiedSince of
+ true -> method(Req2, State2);
+ false -> not_modified(Req2, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+not_modified(Req, State) ->
+ Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
+ try set_resp_etag(Req2, State) of
+ {Req3, State2} ->
+ try set_resp_expires(Req3, State2) of
+ {Req4, State3} ->
+ respond(Req4, State3, 304)
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State2, Class, Reason, Stacktrace)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+precondition_failed(Req, State) ->
+ respond(Req, State, 412).
+
+is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
+ moved_permanently(Req, State, fun is_conflict/2);
+is_put_to_missing_resource(Req, State) ->
+ previously_existed(Req, State).
+
+%% moved_permanently/2 should return either false or {true, Location}
+%% with Location the full new URI of the resource.
+moved_permanently(Req, State, OnFalse) ->
+ case call(Req, State, moved_permanently) of
+ {{true, Location}, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, Location, Req2),
+ respond(Req3, State2, 301);
+ {false, Req2, State2} ->
+ OnFalse(Req2, State2);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ no_call ->
+ OnFalse(Req, State)
+ end.
+
+previously_existed(Req, State) ->
+ expect(Req, State, previously_existed, false,
+ fun (R, S) -> is_post_to_missing_resource(R, S, 404) end,
+ fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).
+
+%% moved_temporarily/2 should return either false or {true, Location}
+%% with Location the full new URI of the resource.
+moved_temporarily(Req, State) ->
+ case call(Req, State, moved_temporarily) of
+ {{true, Location}, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, Location, Req2),
+ respond(Req3, State2, 307);
+ {false, Req2, State2} ->
+ is_post_to_missing_resource(Req2, State2, 410);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ no_call ->
+ is_post_to_missing_resource(Req, State, 410)
+ end.
+
+is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
+ allow_missing_post(Req, State, OnFalse);
+is_post_to_missing_resource(Req, State, OnFalse) ->
+ respond(Req, State, OnFalse).
+
+allow_missing_post(Req, State, OnFalse) ->
+ expect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse).
+
+method(Req, State=#state{method= <<"DELETE">>}) ->
+ delete_resource(Req, State);
+method(Req, State=#state{method= <<"PUT">>}) ->
+ is_conflict(Req, State);
+method(Req, State=#state{method=Method})
+ when Method =:= <<"POST">>; Method =:= <<"PATCH">> ->
+ accept_resource(Req, State);
+method(Req, State=#state{method=Method})
+ when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
+ set_resp_body_etag(Req, State);
+method(Req, State) ->
+ multiple_choices(Req, State).
+
+%% delete_resource/2 should start deleting the resource and return.
+delete_resource(Req, State) ->
+ expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
+
+%% delete_completed/2 indicates whether the resource has been deleted yet.
+delete_completed(Req, State) ->
+ expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
+
+is_conflict(Req, State) ->
+ expect(Req, State, is_conflict, false, fun accept_resource/2, 409).
+
+%% content_types_accepted should return a list of media types and their
+%% associated callback functions in the same format as content_types_provided.
+%%
+%% The callback will then be called and is expected to process the content
+%% pushed to the resource in the request body.
+%%
+%% content_types_accepted SHOULD return a different list
+%% for each HTTP method.
+accept_resource(Req, State) ->
+ case call(Req, State, content_types_accepted) of
+ no_call ->
+ respond(Req, State, 415);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {CTA, Req2, State2} ->
+ CTA2 = [normalize_content_types(P) || P <- CTA],
+ try cowboy_req:parse_header(<<"content-type">>, Req2) of
+ %% We do not match against the boundary parameter for multipart.
+ {Type = <<"multipart">>, SubType, Params} ->
+ ContentType = {Type, SubType, lists:keydelete(<<"boundary">>, 1, Params)},
+ choose_content_type(Req2, State2, ContentType, CTA2);
+ ContentType ->
+ choose_content_type(Req2, State2, ContentType, CTA2)
+ catch _:_ ->
+ respond(Req2, State2, 415)
+ end
+ end.
+
+%% The special content type '*' will always match. It can be used as a
+%% catch-all content type for accepting any kind of request content.
+%% Note that because it will always match, it should be the last of the
+%% list of content types, otherwise it'll shadow the ones following.
+choose_content_type(Req, State, _ContentType, []) ->
+ respond(Req, State, 415);
+choose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail])
+ when Accepted =:= '*'; Accepted =:= ContentType ->
+ process_content_type(Req, State, Fun);
+%% The special parameter '*' will always match any kind of content type
+%% parameters.
+%% Note that because it will always match, it should be the last of the
+%% list for specific content type, otherwise it'll shadow the ones following.
+choose_content_type(Req, State, {Type, SubType, Param},
+ [{{Type, SubType, AcceptedParam}, Fun}|_Tail])
+ when AcceptedParam =:= '*'; AcceptedParam =:= Param ->
+ process_content_type(Req, State, Fun);
+choose_content_type(Req, State, ContentType, [_Any|Tail]) ->
+ choose_content_type(Req, State, ContentType, Tail).
+
+process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->
+ try case call(Req, State, Fun) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {true, Req2, State2} when Exists ->
+ next(Req2, State2, fun has_resp_body/2);
+ {true, Req2, State2} ->
+ next(Req2, State2, fun maybe_created/2);
+ {false, Req2, State2} ->
+ respond(Req2, State2, 400);
+ {{created, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, ResURL, Req2),
+ respond(Req3, State2, 201);
+ {{see_other, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, ResURL, Req2),
+ respond(Req3, State2, 303);
+ {{true, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, ResURL, Req2),
+ if
+ Exists -> respond(Req3, State2, 303);
+ true -> respond(Req3, State2, 201)
+ end
+ end catch Class:Reason = {case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% If PUT was used then the resource has been created at the current URL.
+%% Otherwise, if a location header has been set then the resource has been
+%% created at a new URL. If not, send a 200 or 204 as expected from a
+%% POST or PATCH request.
+maybe_created(Req, State=#state{method= <<"PUT">>}) ->
+ respond(Req, State, 201);
+maybe_created(Req, State) ->
+ case cowboy_req:has_resp_header(<<"location">>, Req) of
+ true -> respond(Req, State, 201);
+ false -> has_resp_body(Req, State)
+ end.
+
+has_resp_body(Req, State) ->
+ case cowboy_req:has_resp_body(Req) of
+ true -> multiple_choices(Req, State);
+ false -> respond(Req, State, 204)
+ end.
+
+%% Set the Etag header if any for the response provided.
+set_resp_body_etag(Req, State) ->
+ try set_resp_etag(Req, State) of
+ {Req2, State2} ->
+ set_resp_body_last_modified(Req2, State2)
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% Set the Last-Modified header if any for the response provided.
+set_resp_body_last_modified(Req, State) ->
+ try last_modified(Req, State) of
+ {LastModified, Req2, State2} ->
+ case LastModified of
+ LastModified when is_atom(LastModified) ->
+ set_resp_body_expires(Req2, State2);
+ LastModified ->
+ LastModifiedBin = cowboy_clock:rfc1123(LastModified),
+ Req3 = cowboy_req:set_resp_header(
+ <<"last-modified">>, LastModifiedBin, Req2),
+ set_resp_body_expires(Req3, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% Set the Expires header if any for the response provided.
+set_resp_body_expires(Req, State) ->
+ try set_resp_expires(Req, State) of
+ {Req2, State2} ->
+ if_range(Req2, State2)
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% When both the if-range and range headers are set, we perform
+%% a strong comparison. If it fails, we send a full response.
+if_range(Req=#{headers := #{<<"if-range">> := _, <<"range">> := _}},
+ State=#state{etag=Etag}) ->
+ try cowboy_req:parse_header(<<"if-range">>, Req) of
+ %% Strong etag comparison is an exact match with the generate_etag result.
+ Etag={strong, _} ->
+ range(Req, State);
+ %% We cannot do a strong date comparison because we have
+ %% no way of knowing whether the representation changed
+ %% twice during the second covered by the presented
+ %% validator. (RFC7232 2.2.2)
+ _ ->
+ set_resp_body(Req, State)
+ catch _:_ ->
+ set_resp_body(Req, State)
+ end;
+if_range(Req, State) ->
+ range(Req, State).
+
+range(Req, State=#state{ranges_a=[]}) ->
+ set_resp_body(Req, State);
+range(Req, State) ->
+ try cowboy_req:parse_header(<<"range">>, Req) of
+ undefined ->
+ set_resp_body(Req, State);
+ %% @todo Maybe change parse_header to return <<"bytes">> in 3.0.
+ {bytes, BytesRange} ->
+ choose_range(Req, State, {<<"bytes">>, BytesRange});
+ Range ->
+ choose_range(Req, State, Range)
+ catch _:_ ->
+ %% We send a 416 response back when we can't parse the
+ %% range header at all. I'm not sure this is the right
+ %% way to go but at least this can help clients identify
+ %% what went wrong when their range requests never work.
+ range_not_satisfiable(Req, State, undefined)
+ end.
+
+choose_range(Req, State=#state{ranges_a=RangesAccepted}, Range={RangeUnit, _}) ->
+ case lists:keyfind(RangeUnit, 1, RangesAccepted) of
+ {_, Callback} ->
+ %% We pass the selected range onward in the Req.
+ range_satisfiable(Req#{range => Range}, State, Callback);
+ false ->
+ set_resp_body(Req, State)
+ end.
+
+range_satisfiable(Req, State, Callback) ->
+ case call(Req, State, range_satisfiable) of
+ no_call ->
+ set_ranged_body(Req, State, Callback);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {true, Req2, State2} ->
+ set_ranged_body(Req2, State2, Callback);
+ {false, Req2, State2} ->
+ range_not_satisfiable(Req2, State2, undefined);
+ {{false, Int}, Req2, State2} when is_integer(Int) ->
+ range_not_satisfiable(Req2, State2, [<<"*/">>, integer_to_binary(Int)]);
+ {{false, Iodata}, Req2, State2} when is_binary(Iodata); is_list(Iodata) ->
+ range_not_satisfiable(Req2, State2, Iodata)
+ end.
+
+%% When the callback selected is 'auto' and the range unit
+%% is bytes, we call the normal provide callback and split
+%% the content automatically.
+set_ranged_body(Req=#{range := {<<"bytes">>, _}}, State, auto) ->
+ set_ranged_body_auto(Req, State);
+set_ranged_body(Req, State, Callback) ->
+ set_ranged_body_callback(Req, State, Callback).
+
+set_ranged_body_auto(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->
+ try case call(Req, State, Callback) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {Body, Req2, State2} ->
+ maybe_set_ranged_body_auto(Req2, State2, Body)
+ end catch Class:{case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
+ 'A callback specified in content_types_provided/2 is not exported.'},
+ Stacktrace)
+ end.
+
+maybe_set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->
+ Size = case Body of
+ {sendfile, _, Bytes, _} -> Bytes;
+ _ -> iolist_size(Body)
+ end,
+ Checks = [case Range of
+ {From, infinity} -> From < Size;
+ {From, To} -> (From < Size) andalso (From =< To) andalso (To =< Size);
+ Neg -> (Neg =/= 0) andalso (-Neg < Size)
+ end || Range <- Ranges],
+ case lists:usort(Checks) of
+ [true] -> set_ranged_body_auto(Req, State, Body);
+ _ -> range_not_satisfiable(Req, State, [<<"*/">>, integer_to_binary(Size)])
+ end.
+
+%% We might also want to have some checks about range order,
+%% number of ranges, and perhaps also join ranges that are
+%% too close into one contiguous range. Some of these can
+%% be done before calling the ProvideCallback.
+
+set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->
+ Parts = [ranged_partition(Range, Body) || Range <- Ranges],
+ case Parts of
+ [OnePart] -> set_one_ranged_body(Req, State, OnePart);
+ _ when is_tuple(Body) -> send_multipart_ranged_body(Req, State, Parts);
+ _ -> set_multipart_ranged_body(Req, State, Parts)
+ end.
+
+ranged_partition(Range, {sendfile, Offset0, Bytes0, Path}) ->
+ {From, To, Offset, Bytes} = case Range of
+ {From0, infinity} -> {From0, Bytes0 - 1, Offset0 + From0, Bytes0 - From0};
+ {From0, To0} -> {From0, To0, Offset0 + From0, 1 + To0 - From0};
+ Neg -> {Bytes0 + Neg, Bytes0 - 1, Offset0 + Bytes0 + Neg, -Neg}
+ end,
+ {{From, To, Bytes0}, {sendfile, Offset, Bytes, Path}};
+ranged_partition(Range, Data0) ->
+ Total = iolist_size(Data0),
+ {From, To, Data} = case Range of
+ {From0, infinity} ->
+ {_, Data1} = cow_iolists:split(From0, Data0),
+ {From0, Total - 1, Data1};
+ {From0, To0} ->
+ {_, Data1} = cow_iolists:split(From0, Data0),
+ {Data2, _} = cow_iolists:split(To0 - From0 + 1, Data1),
+ {From0, To0, Data2};
+ Neg ->
+ {_, Data1} = cow_iolists:split(Total + Neg, Data0),
+ {Total + Neg, Total - 1, Data1}
+ end,
+ {{From, To, Total}, Data}.
+
+-ifdef(TEST).
+ranged_partition_test_() ->
+ Tests = [
+ %% Sendfile with open-ended range.
+ {{0, infinity}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
+ {{6, infinity}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
+ {{11, infinity}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
+ %% Sendfile with open-ended range. Sendfile tuple has an offset originally.
+ {{0, infinity}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
+ {{6, infinity}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
+ {{11, infinity}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
+ %% Sendfile with a specific range.
+ {{0, 11}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
+ {{6, 11}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
+ {{11, 11}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
+ {{1, 10}, {sendfile, 0, 12, "t"}, {{1, 10, 12}, {sendfile, 1, 10, "t"}}},
+ %% Sendfile with a specific range. Sendfile tuple has an offset originally.
+ {{0, 11}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
+ {{6, 11}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
+ {{11, 11}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
+ {{1, 10}, {sendfile, 3, 12, "t"}, {{1, 10, 12}, {sendfile, 4, 10, "t"}}},
+ %% Sendfile with negative range.
+ {-12, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
+ {-6, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
+ {-1, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
+ %% Sendfile with negative range. Sendfile tuple has an offset originally.
+ {-12, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
+ {-6, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
+ {-1, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
+ %% Iodata with open-ended range.
+ {{0, infinity}, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}},
+ {{6, infinity}, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}},
+ {{11, infinity}, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}},
+ %% Iodata with a specific range. The resulting data is
+ %% wrapped in a list because of how cow_iolists:split/2 works.
+ {{0, 11}, <<"Hello world!">>, {{0, 11, 12}, [<<"Hello world!">>]}},
+ {{6, 11}, <<"Hello world!">>, {{6, 11, 12}, [<<"world!">>]}},
+ {{11, 11}, <<"Hello world!">>, {{11, 11, 12}, [<<"!">>]}},
+ {{1, 10}, <<"Hello world!">>, {{1, 10, 12}, [<<"ello world">>]}},
+ %% Iodata with negative range.
+ {-12, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}},
+ {-6, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}},
+ {-1, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}}
+ ],
+ [{iolist_to_binary(io_lib:format("range ~p data ~p", [VR, VD])),
+ fun() -> R = ranged_partition(VR, VD) end} || {VR, VD, R} <- Tests].
+-endif.
+
+set_ranged_body_callback(Req, State=#state{handler=Handler}, Callback) ->
+ try case call(Req, State, Callback) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ %% When we receive a single range, we send it directly.
+ {[OneRange], Req2, State2} ->
+ set_one_ranged_body(Req2, State2, OneRange);
+ %% When we receive multiple ranges we have to send them as multipart/byteranges.
+ %% This also applies to non-bytes units. (RFC7233 A) If users don't want to use
+ %% this for non-bytes units they can always return a single range with a binary
+ %% content-range information.
+ {Ranges, Req2, State2} when length(Ranges) > 1 ->
+ %% We have to check whether there are sendfile tuples in the
+ %% ranges to be sent. If there are we must use stream_reply.
+ HasSendfile = [] =/= [true || {_, {sendfile, _, _, _}} <- Ranges],
+ case HasSendfile of
+ true -> send_multipart_ranged_body(Req2, State2, Ranges);
+ false -> set_multipart_ranged_body(Req2, State2, Ranges)
+ end
+ end catch Class:{case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
+ 'A callback specified in ranges_provided/2 is not exported.'},
+ Stacktrace)
+ end.
+
+set_one_ranged_body(Req0, State, OneRange) ->
+ {ContentRange, Body} = prepare_range(Req0, OneRange),
+ Req1 = cowboy_req:set_resp_header(<<"content-range">>, ContentRange, Req0),
+ Req = cowboy_req:set_resp_body(Body, Req1),
+ respond(Req, State, 206).
+
+set_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->
+ Boundary = cow_multipart:boundary(),
+ ContentType = cowboy_req:resp_header(<<"content-type">>, Req),
+ {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),
+ FirstPartHead = cow_multipart:first_part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, FirstContentRange}
+ ]),
+ MoreParts = [begin
+ {NextContentRange, NextPartBody} = prepare_range(Req, NextRange),
+ NextPartHead = cow_multipart:part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, NextContentRange}
+ ]),
+ [NextPartHead, NextPartBody]
+ end || NextRange <- MoreRanges],
+ Body = [FirstPartHead, FirstPartBody, MoreParts, cow_multipart:close(Boundary)],
+ Req2 = cowboy_req:set_resp_header(<<"content-type">>,
+ [<<"multipart/byteranges; boundary=">>, Boundary], Req),
+ Req3 = cowboy_req:set_resp_body(Body, Req2),
+ respond(Req3, State, 206).
+
+%% Similar to set_multipart_ranged_body except we have to stream
+%% the data because the parts contain sendfile tuples.
+send_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->
+ Boundary = cow_multipart:boundary(),
+ ContentType = cowboy_req:resp_header(<<"content-type">>, Req),
+ Req2 = cowboy_req:set_resp_header(<<"content-type">>,
+ [<<"multipart/byteranges; boundary=">>, Boundary], Req),
+ Req3 = cowboy_req:stream_reply(206, Req2),
+ {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),
+ FirstPartHead = cow_multipart:first_part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, FirstContentRange}
+ ]),
+ cowboy_req:stream_body(FirstPartHead, nofin, Req3),
+ cowboy_req:stream_body(FirstPartBody, nofin, Req3),
+ _ = [begin
+ {NextContentRange, NextPartBody} = prepare_range(Req, NextRange),
+ NextPartHead = cow_multipart:part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, NextContentRange}
+ ]),
+ cowboy_req:stream_body(NextPartHead, nofin, Req3),
+ cowboy_req:stream_body(NextPartBody, nofin, Req3),
+ [NextPartHead, NextPartBody]
+ end || NextRange <- MoreRanges],
+ cowboy_req:stream_body(cow_multipart:close(Boundary), fin, Req3),
+ terminate(Req3, State).
+
+prepare_range(#{range := {RangeUnit, _}}, {{From, To, Total0}, Body}) ->
+ Total = case Total0 of
+ '*' -> <<"*">>;
+ _ -> integer_to_binary(Total0)
+ end,
+ ContentRange = [RangeUnit, $\s, integer_to_binary(From),
+ $-, integer_to_binary(To), $/, Total],
+ {ContentRange, Body};
+prepare_range(#{range := {RangeUnit, _}}, {RangeData, Body}) ->
+ {[RangeUnit, $\s, RangeData], Body}.
+
+%% We send the content-range header when we can on error.
+range_not_satisfiable(Req, State, undefined) ->
+ respond(Req, State, 416);
+range_not_satisfiable(Req0=#{range := {RangeUnit, _}}, State, RangeData) ->
+ Req = cowboy_req:set_resp_header(<<"content-range">>,
+ [RangeUnit, $\s, RangeData], Req0),
+ respond(Req, State, 416).
+
+%% Set the response headers and call the callback found using
+%% content_types_provided/2 to obtain the request body and add
+%% it to the response.
+set_resp_body(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->
+ try case call(Req, State, Callback) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {Body, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_body(Body, Req2),
+ multiple_choices(Req3, State2)
+ end catch Class:{case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
+ 'A callback specified in content_types_provided/2 is not exported.'},
+ Stacktrace)
+ end.
+
+multiple_choices(Req, State) ->
+ expect(Req, State, multiple_choices, false, 200, 300).
+
+%% Response utility functions.
+
+set_resp_etag(Req, State) ->
+ {Etag, Req2, State2} = generate_etag(Req, State),
+ case Etag of
+ undefined ->
+ {Req2, State2};
+ Etag ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"etag">>, encode_etag(Etag), Req2),
+ {Req3, State2}
+ end.
+
+-spec encode_etag({strong | weak, binary()}) -> iolist().
+encode_etag({strong, Etag}) -> [$",Etag,$"];
+encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
+
+set_resp_expires(Req, State) ->
+ {Expires, Req2, State2} = expires(Req, State),
+ case Expires of
+ Expires when is_atom(Expires) ->
+ {Req2, State2};
+ Expires when is_binary(Expires) ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"expires">>, Expires, Req2),
+ {Req3, State2};
+ Expires ->
+ ExpiresBin = cowboy_clock:rfc1123(Expires),
+ Req3 = cowboy_req:set_resp_header(
+ <<"expires">>, ExpiresBin, Req2),
+ {Req3, State2}
+ end.
+
+%% Info retrieval. No logic.
+
+generate_etag(Req, State=#state{etag=no_call}) ->
+ {undefined, Req, State};
+generate_etag(Req, State=#state{etag=undefined}) ->
+ case unsafe_call(Req, State, generate_etag) of
+ no_call ->
+ {undefined, Req, State#state{etag=no_call}};
+ {Etag, Req2, State2} when is_binary(Etag) ->
+ Etag2 = cow_http_hd:parse_etag(Etag),
+ {Etag2, Req2, State2#state{etag=Etag2}};
+ {Etag, Req2, State2} ->
+ {Etag, Req2, State2#state{etag=Etag}}
+ end;
+generate_etag(Req, State=#state{etag=Etag}) ->
+ {Etag, Req, State}.
+
+last_modified(Req, State=#state{last_modified=no_call}) ->
+ {undefined, Req, State};
+last_modified(Req, State=#state{last_modified=undefined}) ->
+ case unsafe_call(Req, State, last_modified) of
+ no_call ->
+ {undefined, Req, State#state{last_modified=no_call}};
+ {LastModified, Req2, State2} ->
+ {LastModified, Req2, State2#state{last_modified=LastModified}}
+ end;
+last_modified(Req, State=#state{last_modified=LastModified}) ->
+ {LastModified, Req, State}.
+
+expires(Req, State=#state{expires=no_call}) ->
+ {undefined, Req, State};
+expires(Req, State=#state{expires=undefined}) ->
+ case unsafe_call(Req, State, expires) of
+ no_call ->
+ {undefined, Req, State#state{expires=no_call}};
+ {Expires, Req2, State2} ->
+ {Expires, Req2, State2#state{expires=Expires}}
+ end;
+expires(Req, State=#state{expires=Expires}) ->
+ {Expires, Req, State}.
+
+%% REST primitives.
+
+expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
+ case call(Req, State, Callback) of
+ no_call ->
+ next(Req, State, OnTrue);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {Expected, Req2, State2} ->
+ next(Req2, State2, OnTrue);
+ {_Unexpected, Req2, State2} ->
+ next(Req2, State2, OnFalse)
+ end.
+
+call(Req0, State=#state{handler=Handler,
+ handler_state=HandlerState0}, Callback) ->
+ case erlang:function_exported(Handler, Callback, 2) of
+ true ->
+ try Handler:Callback(Req0, HandlerState0) of
+ no_call ->
+ no_call;
+ {Result, Req, HandlerState} ->
+ {Result, Req, State#state{handler_state=HandlerState}}
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req0, State, Class, Reason, Stacktrace)
+ end;
+ false ->
+ no_call
+ end.
+
+unsafe_call(Req0, State=#state{handler=Handler,
+ handler_state=HandlerState0}, Callback) ->
+ case erlang:function_exported(Handler, Callback, 2) of
+ false ->
+ no_call;
+ true ->
+ case Handler:Callback(Req0, HandlerState0) of
+ no_call ->
+ no_call;
+ {Result, Req, HandlerState} ->
+ {Result, Req, State#state{handler_state=HandlerState}}
+ end
+ end.
+
+next(Req, State, Next) when is_function(Next) ->
+ Next(Req, State);
+next(Req, State, StatusCode) when is_integer(StatusCode) ->
+ respond(Req, State, StatusCode).
+
+respond(Req0, State, StatusCode) ->
+ %% We remove the content-type header when there is no body,
+ %% except when the status code is 200 because it might have
+ %% been intended (for example sending an empty file).
+ Req = case cowboy_req:has_resp_body(Req0) of
+ true when StatusCode =:= 200 -> Req0;
+ true -> Req0;
+ false -> cowboy_req:delete_resp_header(<<"content-type">>, Req0)
+ end,
+ terminate(cowboy_req:reply(StatusCode, Req), State).
+
+switch_handler({switch_handler, Mod}, Req, #state{handler_state=HandlerState}) ->
+ {Mod, Req, HandlerState};
+switch_handler({switch_handler, Mod, Opts}, Req, #state{handler_state=HandlerState}) ->
+ {Mod, Req, HandlerState, Opts}.
+
+-spec error_terminate(cowboy_req:req(), #state{}, atom(), any(), any()) -> no_return().
+error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason, Stacktrace) ->
+ cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler),
+ erlang:raise(Class, Reason, Stacktrace).
+
+terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
+ Result = cowboy_handler:terminate(normal, Req, HandlerState, Handler),
+ {ok, Req, Result}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_router.erl b/server/_build/default/lib/cowboy/src/cowboy_router.erl
new file mode 100644
index 0000000..0b7fe41
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_router.erl
@@ -0,0 +1,603 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Routing middleware.
+%%
+%% Resolve the handler to be used for the request based on the
+%% routing information found in the <em>dispatch</em> environment value.
+%% When found, the handler module and associated data are added to
+%% the environment as the <em>handler</em> and <em>handler_opts</em> values
+%% respectively.
+%%
+%% If the route cannot be found, processing stops with either
+%% a 400 or a 404 reply.
+-module(cowboy_router).
+-behaviour(cowboy_middleware).
+
+-export([compile/1]).
+-export([execute/2]).
+
+-type bindings() :: #{atom() => any()}.
+-type tokens() :: [binary()].
+-export_type([bindings/0]).
+-export_type([tokens/0]).
+
+-type route_match() :: '_' | iodata().
+-type route_path() :: {Path::route_match(), Handler::module(), Opts::any()}
+ | {Path::route_match(), cowboy:fields(), Handler::module(), Opts::any()}.
+-type route_rule() :: {Host::route_match(), Paths::[route_path()]}
+ | {Host::route_match(), cowboy:fields(), Paths::[route_path()]}.
+-type routes() :: [route_rule()].
+-export_type([routes/0]).
+
+-type dispatch_match() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].
+-type dispatch_path() :: {dispatch_match(), cowboy:fields(), module(), any()}.
+-type dispatch_rule() :: {Host::dispatch_match(), cowboy:fields(), Paths::[dispatch_path()]}.
+-opaque dispatch_rules() :: [dispatch_rule()].
+-export_type([dispatch_rules/0]).
+
+-spec compile(routes()) -> dispatch_rules().
+compile(Routes) ->
+ compile(Routes, []).
+
+compile([], Acc) ->
+ lists:reverse(Acc);
+compile([{Host, Paths}|Tail], Acc) ->
+ compile([{Host, [], Paths}|Tail], Acc);
+compile([{HostMatch, Fields, Paths}|Tail], Acc) ->
+ HostRules = case HostMatch of
+ '_' -> '_';
+ _ -> compile_host(HostMatch)
+ end,
+ PathRules = compile_paths(Paths, []),
+ Hosts = case HostRules of
+ '_' -> [{'_', Fields, PathRules}];
+ _ -> [{R, Fields, PathRules} || R <- HostRules]
+ end,
+ compile(Tail, Hosts ++ Acc).
+
+compile_host(HostMatch) when is_list(HostMatch) ->
+ compile_host(list_to_binary(HostMatch));
+compile_host(HostMatch) when is_binary(HostMatch) ->
+ compile_rules(HostMatch, $., [], [], <<>>).
+
+compile_paths([], Acc) ->
+ lists:reverse(Acc);
+compile_paths([{PathMatch, Handler, Opts}|Tail], Acc) ->
+ compile_paths([{PathMatch, [], Handler, Opts}|Tail], Acc);
+compile_paths([{PathMatch, Fields, Handler, Opts}|Tail], Acc)
+ when is_list(PathMatch) ->
+ compile_paths([{iolist_to_binary(PathMatch),
+ Fields, Handler, Opts}|Tail], Acc);
+compile_paths([{'_', Fields, Handler, Opts}|Tail], Acc) ->
+ compile_paths(Tail, [{'_', Fields, Handler, Opts}] ++ Acc);
+compile_paths([{<<"*">>, Fields, Handler, Opts}|Tail], Acc) ->
+ compile_paths(Tail, [{<<"*">>, Fields, Handler, Opts}|Acc]);
+compile_paths([{<< $/, PathMatch/bits >>, Fields, Handler, Opts}|Tail],
+ Acc) ->
+ PathRules = compile_rules(PathMatch, $/, [], [], <<>>),
+ Paths = [{lists:reverse(R), Fields, Handler, Opts} || R <- PathRules],
+ compile_paths(Tail, Paths ++ Acc);
+compile_paths([{PathMatch, _, _, _}|_], _) ->
+ error({badarg, "The following route MUST begin with a slash: "
+ ++ binary_to_list(PathMatch)}).
+
+compile_rules(<<>>, _, Segments, Rules, <<>>) ->
+ [Segments|Rules];
+compile_rules(<<>>, _, Segments, Rules, Acc) ->
+ [[Acc|Segments]|Rules];
+compile_rules(<< S, Rest/bits >>, S, Segments, Rules, <<>>) ->
+ compile_rules(Rest, S, Segments, Rules, <<>>);
+compile_rules(<< S, Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_rules(Rest, S, [Acc|Segments], Rules, <<>>);
+%% Colon on path segment start is special, otherwise allow.
+compile_rules(<< $:, Rest/bits >>, S, Segments, Rules, <<>>) ->
+ {NameBin, Rest2} = compile_binding(Rest, S, <<>>),
+ Name = binary_to_atom(NameBin, utf8),
+ compile_rules(Rest2, S, Segments, Rules, Name);
+compile_rules(<< $[, $., $., $., $], Rest/bits >>, S, Segments, Rules, Acc)
+ when Acc =:= <<>> ->
+ compile_rules(Rest, S, ['...'|Segments], Rules, Acc);
+compile_rules(<< $[, $., $., $., $], Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_rules(Rest, S, ['...', Acc|Segments], Rules, Acc);
+compile_rules(<< $[, S, Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_brackets(Rest, S, [Acc|Segments], Rules);
+compile_rules(<< $[, Rest/bits >>, S, Segments, Rules, <<>>) ->
+ compile_brackets(Rest, S, Segments, Rules);
+%% Open bracket in the middle of a segment.
+compile_rules(<< $[, _/bits >>, _, _, _, _) ->
+ error(badarg);
+%% Missing an open bracket.
+compile_rules(<< $], _/bits >>, _, _, _, _) ->
+ error(badarg);
+compile_rules(<< C, Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_rules(Rest, S, Segments, Rules, << Acc/binary, C >>).
+
+%% Everything past $: until the segment separator ($. for hosts,
+%% $/ for paths) or $[ or $] or end of binary is the binding name.
+compile_binding(<<>>, _, <<>>) ->
+ error(badarg);
+compile_binding(Rest = <<>>, _, Acc) ->
+ {Acc, Rest};
+compile_binding(Rest = << C, _/bits >>, S, Acc)
+ when C =:= S; C =:= $[; C =:= $] ->
+ {Acc, Rest};
+compile_binding(<< C, Rest/bits >>, S, Acc) ->
+ compile_binding(Rest, S, << Acc/binary, C >>).
+
+compile_brackets(Rest, S, Segments, Rules) ->
+ {Bracket, Rest2} = compile_brackets_split(Rest, <<>>, 0),
+ Rules1 = compile_rules(Rest2, S, Segments, [], <<>>),
+ Rules2 = compile_rules(<< Bracket/binary, Rest2/binary >>,
+ S, Segments, [], <<>>),
+ Rules ++ Rules2 ++ Rules1.
+
+%% Missing a close bracket.
+compile_brackets_split(<<>>, _, _) ->
+ error(badarg);
+%% Make sure we don't confuse the closing bracket we're looking for.
+compile_brackets_split(<< C, Rest/bits >>, Acc, N) when C =:= $[ ->
+ compile_brackets_split(Rest, << Acc/binary, C >>, N + 1);
+compile_brackets_split(<< C, Rest/bits >>, Acc, N) when C =:= $], N > 0 ->
+ compile_brackets_split(Rest, << Acc/binary, C >>, N - 1);
+%% That's the right one.
+compile_brackets_split(<< $], Rest/bits >>, Acc, 0) ->
+ {Acc, Rest};
+compile_brackets_split(<< C, Rest/bits >>, Acc, N) ->
+ compile_brackets_split(Rest, << Acc/binary, C >>, N).
+
+-spec execute(Req, Env)
+ -> {ok, Req, Env} | {stop, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req=#{host := Host, path := Path}, Env=#{dispatch := Dispatch0}) ->
+ Dispatch = case Dispatch0 of
+ {persistent_term, Key} -> persistent_term:get(Key);
+ _ -> Dispatch0
+ end,
+ case match(Dispatch, Host, Path) of
+ {ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
+ {ok, Req#{
+ host_info => HostInfo,
+ path_info => PathInfo,
+ bindings => Bindings
+ }, Env#{
+ handler => Handler,
+ handler_opts => HandlerOpts
+ }};
+ {error, notfound, host} ->
+ {stop, cowboy_req:reply(400, Req)};
+ {error, badrequest, path} ->
+ {stop, cowboy_req:reply(400, Req)};
+ {error, notfound, path} ->
+ {stop, cowboy_req:reply(404, Req)}
+ end.
+
+%% Internal.
+
+%% Match hostname tokens and path tokens against dispatch rules.
+%%
+%% It is typically used for matching tokens for the hostname and path of
+%% the request against a global dispatch rule for your listener.
+%%
+%% Dispatch rules are a list of <em>{Hostname, PathRules}</em> tuples, with
+%% <em>PathRules</em> being a list of <em>{Path, HandlerMod, HandlerOpts}</em>.
+%%
+%% <em>Hostname</em> and <em>Path</em> are match rules and can be either the
+%% atom <em>'_'</em>, which matches everything, `<<"*">>', which match the
+%% wildcard path, or a list of tokens.
+%%
+%% Each token can be either a binary, the atom <em>'_'</em>,
+%% the atom '...' or a named atom. A binary token must match exactly,
+%% <em>'_'</em> matches everything for a single token, <em>'...'</em> matches
+%% everything for the rest of the tokens and a named atom will bind the
+%% corresponding token value and return it.
+%%
+%% The list of hostname tokens is reversed before matching. For example, if
+%% we were to match "www.ninenines.eu", we would first match "eu", then
+%% "ninenines", then "www". This means that in the context of hostnames,
+%% the <em>'...'</em> atom matches properly the lower levels of the domain
+%% as would be expected.
+%%
+%% When a result is found, this function will return the handler module and
+%% options found in the dispatch list, a key-value list of bindings and
+%% the tokens that were matched by the <em>'...'</em> atom for both the
+%% hostname and path.
+-spec match(dispatch_rules(), Host::binary() | tokens(), Path::binary())
+ -> {ok, module(), any(), bindings(),
+ HostInfo::undefined | tokens(),
+ PathInfo::undefined | tokens()}
+ | {error, notfound, host} | {error, notfound, path}
+ | {error, badrequest, path}.
+match([], _, _) ->
+ {error, notfound, host};
+%% If the host is '_' then there can be no constraints.
+match([{'_', [], PathMatchs}|_Tail], _, Path) ->
+ match_path(PathMatchs, undefined, Path, #{});
+match([{HostMatch, Fields, PathMatchs}|Tail], Tokens, Path)
+ when is_list(Tokens) ->
+ case list_match(Tokens, HostMatch, #{}) of
+ false ->
+ match(Tail, Tokens, Path);
+ {true, Bindings, HostInfo} ->
+ HostInfo2 = case HostInfo of
+ undefined -> undefined;
+ _ -> lists:reverse(HostInfo)
+ end,
+ case check_constraints(Fields, Bindings) of
+ {ok, Bindings2} ->
+ match_path(PathMatchs, HostInfo2, Path, Bindings2);
+ nomatch ->
+ match(Tail, Tokens, Path)
+ end
+ end;
+match(Dispatch, Host, Path) ->
+ match(Dispatch, split_host(Host), Path).
+
+-spec match_path([dispatch_path()],
+ HostInfo::undefined | tokens(), binary() | tokens(), bindings())
+ -> {ok, module(), any(), bindings(),
+ HostInfo::undefined | tokens(),
+ PathInfo::undefined | tokens()}
+ | {error, notfound, path} | {error, badrequest, path}.
+match_path([], _, _, _) ->
+ {error, notfound, path};
+%% If the path is '_' then there can be no constraints.
+match_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
+ {ok, Handler, Opts, Bindings, HostInfo, undefined};
+match_path([{<<"*">>, _, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
+ {ok, Handler, Opts, Bindings, HostInfo, undefined};
+match_path([_|Tail], HostInfo, <<"*">>, Bindings) ->
+ match_path(Tail, HostInfo, <<"*">>, Bindings);
+match_path([{PathMatch, Fields, Handler, Opts}|Tail], HostInfo, Tokens,
+ Bindings) when is_list(Tokens) ->
+ case list_match(Tokens, PathMatch, Bindings) of
+ false ->
+ match_path(Tail, HostInfo, Tokens, Bindings);
+ {true, PathBinds, PathInfo} ->
+ case check_constraints(Fields, PathBinds) of
+ {ok, PathBinds2} ->
+ {ok, Handler, Opts, PathBinds2, HostInfo, PathInfo};
+ nomatch ->
+ match_path(Tail, HostInfo, Tokens, Bindings)
+ end
+ end;
+match_path(_Dispatch, _HostInfo, badrequest, _Bindings) ->
+ {error, badrequest, path};
+match_path(Dispatch, HostInfo, Path, Bindings) ->
+ match_path(Dispatch, HostInfo, split_path(Path), Bindings).
+
+check_constraints([], Bindings) ->
+ {ok, Bindings};
+check_constraints([Field|Tail], Bindings) when is_atom(Field) ->
+ check_constraints(Tail, Bindings);
+check_constraints([Field|Tail], Bindings) ->
+ Name = element(1, Field),
+ case Bindings of
+ #{Name := Value0} ->
+ Constraints = element(2, Field),
+ case cowboy_constraints:validate(Value0, Constraints) of
+ {ok, Value} ->
+ check_constraints(Tail, Bindings#{Name => Value});
+ {error, _} ->
+ nomatch
+ end;
+ _ ->
+ check_constraints(Tail, Bindings)
+ end.
+
+-spec split_host(binary()) -> tokens().
+split_host(Host) ->
+ split_host(Host, []).
+
+split_host(Host, Acc) ->
+ case binary:match(Host, <<".">>) of
+ nomatch when Host =:= <<>> ->
+ Acc;
+ nomatch ->
+ [Host|Acc];
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Host,
+ false = byte_size(Segment) == 0,
+ split_host(Rest, [Segment|Acc])
+ end.
+
+%% Following RFC2396, this function may return path segments containing any
+%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped
+%% and part of a path segment.
+-spec split_path(binary()) -> tokens() | badrequest.
+split_path(<< $/, Path/bits >>) ->
+ split_path(Path, []);
+split_path(_) ->
+ badrequest.
+
+split_path(Path, Acc) ->
+ try
+ case binary:match(Path, <<"/">>) of
+ nomatch when Path =:= <<>> ->
+ remove_dot_segments(lists:reverse([cow_uri:urldecode(S) || S <- Acc]), []);
+ nomatch ->
+ remove_dot_segments(lists:reverse([cow_uri:urldecode(S) || S <- [Path|Acc]]), []);
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Path,
+ split_path(Rest, [Segment|Acc])
+ end
+ catch error:_ ->
+ badrequest
+ end.
+
+remove_dot_segments([], Acc) ->
+ lists:reverse(Acc);
+remove_dot_segments([<<".">>|Segments], Acc) ->
+ remove_dot_segments(Segments, Acc);
+remove_dot_segments([<<"..">>|Segments], Acc=[]) ->
+ remove_dot_segments(Segments, Acc);
+remove_dot_segments([<<"..">>|Segments], [_|Acc]) ->
+ remove_dot_segments(Segments, Acc);
+remove_dot_segments([S|Segments], Acc) ->
+ remove_dot_segments(Segments, [S|Acc]).
+
+-ifdef(TEST).
+remove_dot_segments_test_() ->
+ Tests = [
+ {[<<"a">>, <<"b">>, <<"c">>, <<".">>, <<"..">>, <<"..">>, <<"g">>], [<<"a">>, <<"g">>]},
+ {[<<"mid">>, <<"content=5">>, <<"..">>, <<"6">>], [<<"mid">>, <<"6">>]},
+ {[<<"..">>, <<"a">>], [<<"a">>]}
+ ],
+ [fun() -> R = remove_dot_segments(S, []) end || {S, R} <- Tests].
+-endif.
+
+-spec list_match(tokens(), dispatch_match(), bindings())
+ -> {true, bindings(), undefined | tokens()} | false.
+%% Atom '...' matches any trailing path, stop right now.
+list_match(List, ['...'], Binds) ->
+ {true, Binds, List};
+%% Atom '_' matches anything, continue.
+list_match([_E|Tail], ['_'|TailMatch], Binds) ->
+ list_match(Tail, TailMatch, Binds);
+%% Both values match, continue.
+list_match([E|Tail], [E|TailMatch], Binds) ->
+ list_match(Tail, TailMatch, Binds);
+%% Bind E to the variable name V and continue,
+%% unless V was already defined and E isn't identical to the previous value.
+list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->
+ case Binds of
+ %% @todo This isn't right, the constraint must be applied FIRST
+ %% otherwise we can't check for example ints in both host/path.
+ #{V := E} ->
+ list_match(Tail, TailMatch, Binds);
+ #{V := _} ->
+ false;
+ _ ->
+ list_match(Tail, TailMatch, Binds#{V => E})
+ end;
+%% Match complete.
+list_match([], [], Binds) ->
+ {true, Binds, undefined};
+%% Values don't match, stop.
+list_match(_List, _Match, _Binds) ->
+ false.
+
+%% Tests.
+
+-ifdef(TEST).
+compile_test_() ->
+ Tests = [
+ %% Match any host and path.
+ {[{'_', [{'_', h, o}]}],
+ [{'_', [], [{'_', [], h, o}]}]},
+ {[{"cowboy.example.org",
+ [{"/", ha, oa}, {"/path/to/resource", hb, ob}]}],
+ [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [
+ {[], [], ha, oa},
+ {[<<"path">>, <<"to">>, <<"resource">>], [], hb, ob}]}]},
+ {[{'_', [{"/path/to/resource/", h, o}]}],
+ [{'_', [], [{[<<"path">>, <<"to">>, <<"resource">>], [], h, o}]}]},
+ % Cyrillic from a latin1 encoded file.
+ {[{'_', [{[47,208,191,209,131,209,130,209,140,47,208,186,47,209,128,
+ 208,181,209,129,209,131,209,128,209,129,209,131,47], h, o}]}],
+ [{'_', [], [{[<<208,191,209,131,209,130,209,140>>, <<208,186>>,
+ <<209,128,208,181,209,129,209,131,209,128,209,129,209,131>>],
+ [], h, o}]}]},
+ {[{"cowboy.example.org.", [{'_', h, o}]}],
+ [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
+ {[{".cowboy.example.org", [{'_', h, o}]}],
+ [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
+ % Cyrillic from a latin1 encoded file.
+ {[{[208,189,208,181,208,186,208,184,208,185,46,209,129,208,176,
+ 208,185,209,130,46,209,128,209,132,46], [{'_', h, o}]}],
+ [{[<<209,128,209,132>>, <<209,129,208,176,208,185,209,130>>,
+ <<208,189,208,181,208,186,208,184,208,185>>],
+ [], [{'_', [], h, o}]}]},
+ {[{":subdomain.example.org", [{"/hats/:name/prices", h, o}]}],
+ [{[<<"org">>, <<"example">>, subdomain], [], [
+ {[<<"hats">>, name, <<"prices">>], [], h, o}]}]},
+ {[{"ninenines.:_", [{"/hats/:_", h, o}]}],
+ [{['_', <<"ninenines">>], [], [{[<<"hats">>, '_'], [], h, o}]}]},
+ {[{"[www.]ninenines.eu",
+ [{"/horses", h, o}, {"/hats/[page/:number]", h, o}]}], [
+ {[<<"eu">>, <<"ninenines">>], [], [
+ {[<<"horses">>], [], h, o},
+ {[<<"hats">>], [], h, o},
+ {[<<"hats">>, <<"page">>, number], [], h, o}]},
+ {[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
+ {[<<"horses">>], [], h, o},
+ {[<<"hats">>], [], h, o},
+ {[<<"hats">>, <<"page">>, number], [], h, o}]}]},
+ {[{'_', [{"/hats/:page/:number", h, o}]}], [{'_', [], [
+ {[<<"hats">>, page, number], [], h, o}]}]},
+ {[{'_', [{"/hats/[page/[:number]]", h, o}]}], [{'_', [], [
+ {[<<"hats">>], [], h, o},
+ {[<<"hats">>, <<"page">>], [], h, o},
+ {[<<"hats">>, <<"page">>, number], [], h, o}]}]},
+ {[{"[...]ninenines.eu", [{"/hats/[...]", h, o}]}],
+ [{[<<"eu">>, <<"ninenines">>, '...'], [], [
+ {[<<"hats">>, '...'], [], h, o}]}]},
+ %% Path segment containing a colon.
+ {[{'_', [{"/foo/bar:blah", h, o}]}], [{'_', [], [
+ {[<<"foo">>, <<"bar:blah">>], [], h, o}]}]}
+ ],
+ [{lists:flatten(io_lib:format("~p", [Rt])),
+ fun() -> Rs = compile(Rt) end} || {Rt, Rs} <- Tests].
+
+split_host_test_() ->
+ Tests = [
+ {<<"">>, []},
+ {<<"*">>, [<<"*">>]},
+ {<<"cowboy.ninenines.eu">>,
+ [<<"eu">>, <<"ninenines">>, <<"cowboy">>]},
+ {<<"ninenines.eu">>,
+ [<<"eu">>, <<"ninenines">>]},
+ {<<"ninenines.eu.">>,
+ [<<"eu">>, <<"ninenines">>]},
+ {<<"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z">>,
+ [<<"z">>, <<"y">>, <<"x">>, <<"w">>, <<"v">>, <<"u">>, <<"t">>,
+ <<"s">>, <<"r">>, <<"q">>, <<"p">>, <<"o">>, <<"n">>, <<"m">>,
+ <<"l">>, <<"k">>, <<"j">>, <<"i">>, <<"h">>, <<"g">>, <<"f">>,
+ <<"e">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>]}
+ ],
+ [{H, fun() -> R = split_host(H) end} || {H, R} <- Tests].
+
+split_path_test_() ->
+ Tests = [
+ {<<"/">>, []},
+ {<<"/extend//cowboy">>, [<<"extend">>, <<>>, <<"cowboy">>]},
+ {<<"/users">>, [<<"users">>]},
+ {<<"/users/42/friends">>, [<<"users">>, <<"42">>, <<"friends">>]},
+ {<<"/users/a%20b/c%21d">>, [<<"users">>, <<"a b">>, <<"c!d">>]}
+ ],
+ [{P, fun() -> R = split_path(P) end} || {P, R} <- Tests].
+
+match_test_() ->
+ Dispatch = [
+ {[<<"eu">>, <<"ninenines">>, '_', <<"www">>], [], [
+ {[<<"users">>, '_', <<"mails">>], [], match_any_subdomain_users, []}
+ ]},
+ {[<<"eu">>, <<"ninenines">>], [], [
+ {[<<"users">>, id, <<"friends">>], [], match_extend_users_friends, []},
+ {'_', [], match_extend, []}
+ ]},
+ {[var, <<"ninenines">>], [], [
+ {[<<"threads">>, var], [], match_duplicate_vars,
+ [we, {expect, two}, var, here]}
+ ]},
+ {[ext, <<"erlang">>], [], [
+ {'_', [], match_erlang_ext, []}
+ ]},
+ {'_', [], [
+ {[<<"users">>, id, <<"friends">>], [], match_users_friends, []},
+ {'_', [], match_any, []}
+ ]}
+ ],
+ Tests = [
+ {<<"any">>, <<"/">>, {ok, match_any, [], #{}}},
+ {<<"www.any.ninenines.eu">>, <<"/users/42/mails">>,
+ {ok, match_any_subdomain_users, [], #{}}},
+ {<<"www.ninenines.eu">>, <<"/users/42/mails">>,
+ {ok, match_any, [], #{}}},
+ {<<"www.ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}}},
+ {<<"www.any.ninenines.eu">>, <<"/not_users/42/mails">>,
+ {error, notfound, path}},
+ {<<"ninenines.eu">>, <<"/">>,
+ {ok, match_extend, [], #{}}},
+ {<<"ninenines.eu">>, <<"/users/42/friends">>,
+ {ok, match_extend_users_friends, [], #{id => <<"42">>}}},
+ {<<"erlang.fr">>, '_',
+ {ok, match_erlang_ext, [], #{ext => <<"fr">>}}},
+ {<<"any">>, <<"/users/444/friends">>,
+ {ok, match_users_friends, [], #{id => <<"444">>}}},
+ {<<"any">>, <<"/users//friends">>,
+ {ok, match_users_friends, [], #{id => <<>>}}}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
+ {ok, Handler, Opts, Binds, undefined, undefined}
+ = match(Dispatch, H, P)
+ end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].
+
+match_info_test_() ->
+ Dispatch = [
+ {[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
+ {[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], [], match_path, []}
+ ]},
+ {[<<"eu">>, <<"ninenines">>, '...'], [], [
+ {'_', [], match_any, []}
+ ]}
+ ],
+ Tests = [
+ {<<"ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}, [], undefined}},
+ {<<"bugs.ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}, [<<"bugs">>], undefined}},
+ {<<"cowboy.bugs.ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}, [<<"cowboy">>, <<"bugs">>], undefined}},
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next">>,
+ {ok, match_path, [], #{}, undefined, []}},
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/path_info">>,
+ {ok, match_path, [], #{}, undefined, [<<"path_info">>]}},
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/foo/bar">>,
+ {ok, match_path, [], #{}, undefined, [<<"foo">>, <<"bar">>]}}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
+ R = match(Dispatch, H, P)
+ end} || {H, P, R} <- Tests].
+
+match_constraints_test() ->
+ Dispatch0 = [{'_', [],
+ [{[<<"path">>, value], [{value, int}], match, []}]}],
+ {ok, _, [], #{value := 123}, _, _} = match(Dispatch0,
+ <<"ninenines.eu">>, <<"/path/123">>),
+ {ok, _, [], #{value := 123}, _, _} = match(Dispatch0,
+ <<"ninenines.eu">>, <<"/path/123/">>),
+ {error, notfound, path} = match(Dispatch0,
+ <<"ninenines.eu">>, <<"/path/NaN/">>),
+ Dispatch1 = [{'_', [],
+ [{[<<"path">>, value, <<"more">>], [{value, nonempty}], match, []}]}],
+ {ok, _, [], #{value := <<"something">>}, _, _} = match(Dispatch1,
+ <<"ninenines.eu">>, <<"/path/something/more">>),
+ {error, notfound, path} = match(Dispatch1,
+ <<"ninenines.eu">>, <<"/path//more">>),
+ Dispatch2 = [{'_', [], [{[<<"path">>, username],
+ [{username, fun(_, Value) ->
+ case cowboy_bstr:to_lower(Value) of
+ Value -> {ok, Value};
+ _ -> {error, not_lowercase}
+ end end}],
+ match, []}]}],
+ {ok, _, [], #{username := <<"essen">>}, _, _} = match(Dispatch2,
+ <<"ninenines.eu">>, <<"/path/essen">>),
+ {error, notfound, path} = match(Dispatch2,
+ <<"ninenines.eu">>, <<"/path/ESSEN">>),
+ ok.
+
+match_same_bindings_test() ->
+ Dispatch = [{[same, same], [], [{'_', [], match, []}]}],
+ {ok, _, [], #{same := <<"eu">>}, _, _} = match(Dispatch,
+ <<"eu.eu">>, <<"/">>),
+ {error, notfound, host} = match(Dispatch,
+ <<"ninenines.eu">>, <<"/">>),
+ Dispatch2 = [{[<<"eu">>, <<"ninenines">>, user], [],
+ [{[<<"path">>, user], [], match, []}]}],
+ {ok, _, [], #{user := <<"essen">>}, _, _} = match(Dispatch2,
+ <<"essen.ninenines.eu">>, <<"/path/essen">>),
+ {ok, _, [], #{user := <<"essen">>}, _, _} = match(Dispatch2,
+ <<"essen.ninenines.eu">>, <<"/path/essen/">>),
+ {error, notfound, path} = match(Dispatch2,
+ <<"essen.ninenines.eu">>, <<"/path/notessen">>),
+ Dispatch3 = [{'_', [], [{[same, same], [], match, []}]}],
+ {ok, _, [], #{same := <<"path">>}, _, _} = match(Dispatch3,
+ <<"ninenines.eu">>, <<"/path/path">>),
+ {error, notfound, path} = match(Dispatch3,
+ <<"ninenines.eu">>, <<"/path/to">>),
+ ok.
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_static.erl b/server/_build/default/lib/cowboy/src/cowboy_static.erl
new file mode 100644
index 0000000..b0cf146
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_static.erl
@@ -0,0 +1,418 @@
+%% Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2011, Magnus Klaar <magnus.klaar@gmail.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_static).
+
+-export([init/2]).
+-export([malformed_request/2]).
+-export([forbidden/2]).
+-export([content_types_provided/2]).
+-export([charsets_provided/2]).
+-export([ranges_provided/2]).
+-export([resource_exists/2]).
+-export([last_modified/2]).
+-export([generate_etag/2]).
+-export([get_file/2]).
+
+-type extra_charset() :: {charset, module(), function()} | {charset, binary()}.
+-type extra_etag() :: {etag, module(), function()} | {etag, false}.
+-type extra_mimetypes() :: {mimetypes, module(), function()}
+ | {mimetypes, binary() | {binary(), binary(), [{binary(), binary()}]}}.
+-type extra() :: [extra_charset() | extra_etag() | extra_mimetypes()].
+-type opts() :: {file | dir, string() | binary()}
+ | {file | dir, string() | binary(), extra()}
+ | {priv_file | priv_dir, atom(), string() | binary()}
+ | {priv_file | priv_dir, atom(), string() | binary(), extra()}.
+-export_type([opts/0]).
+
+-include_lib("kernel/include/file.hrl").
+
+-type state() :: {binary(), {direct | archive, #file_info{}}
+ | {error, atom()}, extra()}.
+
+%% Resolve the file that will be sent and get its file information.
+%% If the handler is configured to manage a directory, check that the
+%% requested file is inside the configured directory.
+
+-spec init(Req, opts()) -> {cowboy_rest, Req, error | state()} when Req::cowboy_req:req().
+init(Req, {Name, Path}) ->
+ init_opts(Req, {Name, Path, []});
+init(Req, {Name, App, Path})
+ when Name =:= priv_file; Name =:= priv_dir ->
+ init_opts(Req, {Name, App, Path, []});
+init(Req, Opts) ->
+ init_opts(Req, Opts).
+
+init_opts(Req, {priv_file, App, Path, Extra}) ->
+ {PrivPath, HowToAccess} = priv_path(App, Path),
+ init_info(Req, absname(PrivPath), HowToAccess, Extra);
+init_opts(Req, {file, Path, Extra}) ->
+ init_info(Req, absname(Path), direct, Extra);
+init_opts(Req, {priv_dir, App, Path, Extra}) ->
+ {PrivPath, HowToAccess} = priv_path(App, Path),
+ init_dir(Req, PrivPath, HowToAccess, Extra);
+init_opts(Req, {dir, Path, Extra}) ->
+ init_dir(Req, Path, direct, Extra).
+
+priv_path(App, Path) ->
+ case code:priv_dir(App) of
+ {error, bad_name} ->
+ error({badarg, "Can't resolve the priv_dir of application "
+ ++ atom_to_list(App)});
+ PrivDir when is_list(Path) ->
+ {
+ PrivDir ++ "/" ++ Path,
+ how_to_access_app_priv(PrivDir)
+ };
+ PrivDir when is_binary(Path) ->
+ {
+ << (list_to_binary(PrivDir))/binary, $/, Path/binary >>,
+ how_to_access_app_priv(PrivDir)
+ }
+ end.
+
+how_to_access_app_priv(PrivDir) ->
+ %% If the priv directory is not a directory, it must be
+ %% inside an Erlang application .ez archive. We call
+ %% how_to_access_app_priv1() to find the corresponding archive.
+ case filelib:is_dir(PrivDir) of
+ true -> direct;
+ false -> how_to_access_app_priv1(PrivDir)
+ end.
+
+how_to_access_app_priv1(Dir) ->
+ %% We go "up" by one path component at a time and look for a
+ %% regular file.
+ Archive = filename:dirname(Dir),
+ case Archive of
+ Dir ->
+ %% filename:dirname() returned its argument:
+ %% we reach the root directory. We found no
+ %% archive so we return 'direct': the given priv
+ %% directory doesn't exist.
+ direct;
+ _ ->
+ case filelib:is_regular(Archive) of
+ true -> {archive, Archive};
+ false -> how_to_access_app_priv1(Archive)
+ end
+ end.
+
+absname(Path) when is_list(Path) ->
+ filename:absname(list_to_binary(Path));
+absname(Path) when is_binary(Path) ->
+ filename:absname(Path).
+
+init_dir(Req, Path, HowToAccess, Extra) when is_list(Path) ->
+ init_dir(Req, list_to_binary(Path), HowToAccess, Extra);
+init_dir(Req, Path, HowToAccess, Extra) ->
+ Dir = fullpath(filename:absname(Path)),
+ case cowboy_req:path_info(Req) of
+ %% When dir/priv_dir are used and there is no path_info
+ %% this is a configuration error and we abort immediately.
+ undefined ->
+ {ok, cowboy_req:reply(500, Req), error};
+ PathInfo ->
+ case validate_reserved(PathInfo) of
+ error ->
+ {cowboy_rest, Req, error};
+ ok ->
+ Filepath = filename:join([Dir|PathInfo]),
+ Len = byte_size(Dir),
+ case fullpath(Filepath) of
+ << Dir:Len/binary, $/, _/binary >> ->
+ init_info(Req, Filepath, HowToAccess, Extra);
+ << Dir:Len/binary >> ->
+ init_info(Req, Filepath, HowToAccess, Extra);
+ _ ->
+ {cowboy_rest, Req, error}
+ end
+ end
+ end.
+
+validate_reserved([]) ->
+ ok;
+validate_reserved([P|Tail]) ->
+ case validate_reserved1(P) of
+ ok -> validate_reserved(Tail);
+ error -> error
+ end.
+
+%% We always reject forward slash, backward slash and NUL as
+%% those have special meanings across the supported platforms.
+%% We could support the backward slash on some platforms but
+%% for the sake of consistency and simplicity we don't.
+validate_reserved1(<<>>) ->
+ ok;
+validate_reserved1(<<$/, _/bits>>) ->
+ error;
+validate_reserved1(<<$\\, _/bits>>) ->
+ error;
+validate_reserved1(<<0, _/bits>>) ->
+ error;
+validate_reserved1(<<_, Rest/bits>>) ->
+ validate_reserved1(Rest).
+
+fullpath(Path) ->
+ fullpath(filename:split(Path), []).
+fullpath([], Acc) ->
+ filename:join(lists:reverse(Acc));
+fullpath([<<".">>|Tail], Acc) ->
+ fullpath(Tail, Acc);
+fullpath([<<"..">>|Tail], Acc=[_]) ->
+ fullpath(Tail, Acc);
+fullpath([<<"..">>|Tail], [_|Acc]) ->
+ fullpath(Tail, Acc);
+fullpath([Segment|Tail], Acc) ->
+ fullpath(Tail, [Segment|Acc]).
+
+init_info(Req, Path, HowToAccess, Extra) ->
+ Info = read_file_info(Path, HowToAccess),
+ {cowboy_rest, Req, {Path, Info, Extra}}.
+
+read_file_info(Path, direct) ->
+ case file:read_file_info(Path, [{time, universal}]) of
+ {ok, Info} -> {direct, Info};
+ Error -> Error
+ end;
+read_file_info(Path, {archive, Archive}) ->
+ case file:read_file_info(Archive, [{time, universal}]) of
+ {ok, ArchiveInfo} ->
+ %% The Erlang application archive is fine.
+ %% Now check if the requested file is in that
+ %% archive. We also need the file_info to merge
+ %% them with the archive's one.
+ PathS = binary_to_list(Path),
+ case erl_prim_loader:read_file_info(PathS) of
+ {ok, ContainedFileInfo} ->
+ Info = fix_archived_file_info(
+ ArchiveInfo,
+ ContainedFileInfo),
+ {archive, Info};
+ error ->
+ {error, enoent}
+ end;
+ Error ->
+ Error
+ end.
+
+fix_archived_file_info(ArchiveInfo, ContainedFileInfo) ->
+ %% We merge the archive and content #file_info because we are
+ %% interested by the timestamps of the archive, but the type and
+ %% size of the contained file/directory.
+ %%
+ %% We reset the access to 'read', because we won't rewrite the
+ %% archive.
+ ArchiveInfo#file_info{
+ size = ContainedFileInfo#file_info.size,
+ type = ContainedFileInfo#file_info.type,
+ access = read
+ }.
+
+-ifdef(TEST).
+fullpath_test_() ->
+ Tests = [
+ {<<"/home/cowboy">>, <<"/home/cowboy">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/./">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/./././././.">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/abc/..">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/abc/../">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/abc/./../.">>},
+ {<<"/">>, <<"/home/cowboy/../../../../../..">>},
+ {<<"/etc/passwd">>, <<"/home/cowboy/../../etc/passwd">>}
+ ],
+ [{P, fun() -> R = fullpath(P) end} || {R, P} <- Tests].
+
+good_path_check_test_() ->
+ Tests = [
+ <<"/home/cowboy/file">>,
+ <<"/home/cowboy/file/">>,
+ <<"/home/cowboy/./file">>,
+ <<"/home/cowboy/././././././file">>,
+ <<"/home/cowboy/abc/../file">>,
+ <<"/home/cowboy/abc/../file">>,
+ <<"/home/cowboy/abc/./.././file">>
+ ],
+ [{P, fun() ->
+ case fullpath(P) of
+ << "/home/cowboy/", _/bits >> -> ok
+ end
+ end} || P <- Tests].
+
+bad_path_check_test_() ->
+ Tests = [
+ <<"/home/cowboy/../../../../../../file">>,
+ <<"/home/cowboy/../../etc/passwd">>
+ ],
+ [{P, fun() ->
+ error = case fullpath(P) of
+ << "/home/cowboy/", _/bits >> -> ok;
+ _ -> error
+ end
+ end} || P <- Tests].
+
+good_path_win32_check_test_() ->
+ Tests = case os:type() of
+ {unix, _} ->
+ [];
+ {win32, _} ->
+ [
+ <<"c:/home/cowboy/file">>,
+ <<"c:/home/cowboy/file/">>,
+ <<"c:/home/cowboy/./file">>,
+ <<"c:/home/cowboy/././././././file">>,
+ <<"c:/home/cowboy/abc/../file">>,
+ <<"c:/home/cowboy/abc/../file">>,
+ <<"c:/home/cowboy/abc/./.././file">>
+ ]
+ end,
+ [{P, fun() ->
+ case fullpath(P) of
+ << "c:/home/cowboy/", _/bits >> -> ok
+ end
+ end} || P <- Tests].
+
+bad_path_win32_check_test_() ->
+ Tests = case os:type() of
+ {unix, _} ->
+ [];
+ {win32, _} ->
+ [
+ <<"c:/home/cowboy/../../secretfile.bat">>,
+ <<"c:/home/cowboy/c:/secretfile.bat">>,
+ <<"c:/home/cowboy/..\\..\\secretfile.bat">>,
+ <<"c:/home/cowboy/c:\\secretfile.bat">>
+ ]
+ end,
+ [{P, fun() ->
+ error = case fullpath(P) of
+ << "c:/home/cowboy/", _/bits >> -> ok;
+ _ -> error
+ end
+ end} || P <- Tests].
+-endif.
+
+%% Reject requests that tried to access a file outside
+%% the target directory, or used reserved characters.
+
+-spec malformed_request(Req, State)
+ -> {boolean(), Req, State}.
+malformed_request(Req, State) ->
+ {State =:= error, Req, State}.
+
+%% Directories, files that can't be accessed at all and
+%% files with no read flag are forbidden.
+
+-spec forbidden(Req, State)
+ -> {boolean(), Req, State}
+ when State::state().
+forbidden(Req, State={_, {_, #file_info{type=directory}}, _}) ->
+ {true, Req, State};
+forbidden(Req, State={_, {error, eacces}, _}) ->
+ {true, Req, State};
+forbidden(Req, State={_, {_, #file_info{access=Access}}, _})
+ when Access =:= write; Access =:= none ->
+ {true, Req, State};
+forbidden(Req, State) ->
+ {false, Req, State}.
+
+%% Detect the mimetype of the file.
+
+-spec content_types_provided(Req, State)
+ -> {[{binary(), get_file}], Req, State}
+ when State::state().
+content_types_provided(Req, State={Path, _, Extra}) when is_list(Extra) ->
+ case lists:keyfind(mimetypes, 1, Extra) of
+ false ->
+ {[{cow_mimetypes:web(Path), get_file}], Req, State};
+ {mimetypes, Module, Function} ->
+ {[{Module:Function(Path), get_file}], Req, State};
+ {mimetypes, Type} ->
+ {[{Type, get_file}], Req, State}
+ end.
+
+%% Detect the charset of the file.
+
+-spec charsets_provided(Req, State)
+ -> {[binary()], Req, State}
+ when State::state().
+charsets_provided(Req, State={Path, _, Extra}) ->
+ case lists:keyfind(charset, 1, Extra) of
+ %% We simulate the callback not being exported.
+ false ->
+ no_call;
+ {charset, Module, Function} ->
+ {[Module:Function(Path)], Req, State};
+ {charset, Charset} when is_binary(Charset) ->
+ {[Charset], Req, State}
+ end.
+
+%% Enable support for range requests.
+
+-spec ranges_provided(Req, State)
+ -> {[{binary(), auto}], Req, State}
+ when State::state().
+ranges_provided(Req, State) ->
+ {[{<<"bytes">>, auto}], Req, State}.
+
+%% Assume the resource doesn't exist if it's not a regular file.
+
+-spec resource_exists(Req, State)
+ -> {boolean(), Req, State}
+ when State::state().
+resource_exists(Req, State={_, {_, #file_info{type=regular}}, _}) ->
+ {true, Req, State};
+resource_exists(Req, State) ->
+ {false, Req, State}.
+
+%% Generate an etag for the file.
+
+-spec generate_etag(Req, State)
+ -> {{strong | weak, binary()}, Req, State}
+ when State::state().
+generate_etag(Req, State={Path, {_, #file_info{size=Size, mtime=Mtime}},
+ Extra}) ->
+ case lists:keyfind(etag, 1, Extra) of
+ false ->
+ {generate_default_etag(Size, Mtime), Req, State};
+ {etag, Module, Function} ->
+ {Module:Function(Path, Size, Mtime), Req, State};
+ {etag, false} ->
+ {undefined, Req, State}
+ end.
+
+generate_default_etag(Size, Mtime) ->
+ {strong, integer_to_binary(erlang:phash2({Size, Mtime}, 16#ffffffff))}.
+
+%% Return the time of last modification of the file.
+
+-spec last_modified(Req, State)
+ -> {calendar:datetime(), Req, State}
+ when State::state().
+last_modified(Req, State={_, {_, #file_info{mtime=Modified}}, _}) ->
+ {Modified, Req, State}.
+
+%% Stream the file.
+
+-spec get_file(Req, State)
+ -> {{sendfile, 0, non_neg_integer(), binary()}, Req, State}
+ when State::state().
+get_file(Req, State={Path, {direct, #file_info{size=Size}}, _}) ->
+ {{sendfile, 0, Size, Path}, Req, State};
+get_file(Req, State={Path, {archive, _}, _}) ->
+ PathS = binary_to_list(Path),
+ {ok, Bin, _} = erl_prim_loader:get_file(PathS),
+ {Bin, Req, State}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_stream.erl b/server/_build/default/lib/cowboy/src/cowboy_stream.erl
new file mode 100644
index 0000000..2dad6d0
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_stream.erl
@@ -0,0 +1,193 @@
+%% Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_stream).
+
+-type state() :: any().
+-type human_reason() :: atom().
+
+-type streamid() :: any().
+-export_type([streamid/0]).
+
+-type fin() :: fin | nofin.
+-export_type([fin/0]).
+
+%% @todo Perhaps it makes more sense to have resp_body in this module?
+
+-type resp_command()
+ :: {response, cowboy:http_status(), cowboy:http_headers(), cowboy_req:resp_body()}.
+-export_type([resp_command/0]).
+
+-type commands() :: [{inform, cowboy:http_status(), cowboy:http_headers()}
+ | resp_command()
+ | {headers, cowboy:http_status(), cowboy:http_headers()}
+ | {data, fin(), cowboy_req:resp_body()}
+ | {trailers, cowboy:http_headers()}
+ | {push, binary(), binary(), binary(), inet:port_number(),
+ binary(), binary(), cowboy:http_headers()}
+ | {flow, pos_integer()}
+ | {spawn, pid(), timeout()}
+ | {error_response, cowboy:http_status(), cowboy:http_headers(), iodata()}
+ | {switch_protocol, cowboy:http_headers(), module(), state()}
+ | {internal_error, any(), human_reason()}
+ | {set_options, map()}
+ | {log, logger:level(), io:format(), list()}
+ | stop].
+-export_type([commands/0]).
+
+-type reason() :: normal | switch_protocol
+ | {internal_error, timeout | {error | exit | throw, any()}, human_reason()}
+ | {socket_error, closed | atom(), human_reason()}
+ | {stream_error, cow_http2:error(), human_reason()}
+ | {connection_error, cow_http2:error(), human_reason()}
+ | {stop, cow_http2:frame() | {exit, any()}, human_reason()}.
+-export_type([reason/0]).
+
+-type partial_req() :: map(). %% @todo Take what's in cowboy_req with everything? optional.
+-export_type([partial_req/0]).
+
+-callback init(streamid(), cowboy_req:req(), cowboy:opts()) -> {commands(), state()}.
+-callback data(streamid(), fin(), binary(), State) -> {commands(), State} when State::state().
+-callback info(streamid(), any(), State) -> {commands(), State} when State::state().
+-callback terminate(streamid(), reason(), state()) -> any().
+-callback early_error(streamid(), reason(), partial_req(), Resp, cowboy:opts())
+ -> Resp when Resp::resp_command().
+
+%% @todo To optimize the number of active timers we could have a command
+%% that enables a timeout that is called in the absence of any other call,
+%% similar to what gen_server does. However the nice thing about this is
+%% that the connection process can keep a single timer around (the same
+%% one that would be used to detect half-closed sockets) and use this
+%% timer and other events to trigger the timeout in streams at their
+%% intended time.
+%%
+%% This same timer can be used to try and send PING frames to help detect
+%% that the connection is indeed unresponsive.
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+-export([make_error_log/5]).
+
+%% Note that this and other functions in this module do NOT catch
+%% exceptions. We want the exception to go all the way down to the
+%% protocol code.
+%%
+%% OK the failure scenario is not so clear. The problem is
+%% that the failure at any point in init/3 will result in the
+%% corresponding state being lost. I am unfortunately not
+%% confident we can do anything about this. If the crashing
+%% handler just created a process, we'll never know about it.
+%% Therefore at this time I choose to leave all failure handling
+%% to the protocol process.
+%%
+%% Note that a failure in init/3 will result in terminate/3
+%% NOT being called. This is because the state is not available.
+
+-spec init(streamid(), cowboy_req:req(), cowboy:opts())
+ -> {commands(), {module(), state()} | undefined}.
+init(StreamID, Req, Opts) ->
+ case maps:get(stream_handlers, Opts, [cowboy_stream_h]) of
+ [] ->
+ {[], undefined};
+ [Handler|Tail] ->
+ %% We call the next handler and remove it from the list of
+ %% stream handlers. This means that handlers that run after
+ %% it have no knowledge it exists. Should user require this
+ %% knowledge they can just define a separate option that will
+ %% be left untouched.
+ {Commands, State} = Handler:init(StreamID, Req, Opts#{stream_handlers => Tail}),
+ {Commands, {Handler, State}}
+ end.
+
+-spec data(streamid(), fin(), binary(), {Handler, State} | undefined)
+ -> {commands(), {Handler, State} | undefined}
+ when Handler::module(), State::state().
+data(_, _, _, undefined) ->
+ {[], undefined};
+data(StreamID, IsFin, Data, {Handler, State0}) ->
+ {Commands, State} = Handler:data(StreamID, IsFin, Data, State0),
+ {Commands, {Handler, State}}.
+
+-spec info(streamid(), any(), {Handler, State} | undefined)
+ -> {commands(), {Handler, State} | undefined}
+ when Handler::module(), State::state().
+info(_, _, undefined) ->
+ {[], undefined};
+info(StreamID, Info, {Handler, State0}) ->
+ {Commands, State} = Handler:info(StreamID, Info, State0),
+ {Commands, {Handler, State}}.
+
+-spec terminate(streamid(), reason(), {module(), state()} | undefined) -> ok.
+terminate(_, _, undefined) ->
+ ok;
+terminate(StreamID, Reason, {Handler, State}) ->
+ _ = Handler:terminate(StreamID, Reason, State),
+ ok.
+
+-spec early_error(streamid(), reason(), partial_req(), Resp, cowboy:opts())
+ -> Resp when Resp::resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ case maps:get(stream_handlers, Opts, [cowboy_stream_h]) of
+ [] ->
+ Resp;
+ [Handler|Tail] ->
+ %% This is the same behavior as in init/3.
+ Handler:early_error(StreamID, Reason,
+ PartialReq, Resp, Opts#{stream_handlers => Tail})
+ end.
+
+-spec make_error_log(init | data | info | terminate | early_error,
+ list(), error | exit | throw, any(), list())
+ -> {log, error, string(), list()}.
+make_error_log(init, [StreamID, Req, Opts], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:init(~p, Req, Opts)~n"
+ "Stacktrace: ~p~n"
+ "Req: ~p~n"
+ "Opts: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Req, Opts]};
+make_error_log(data, [StreamID, IsFin, Data, State], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:data(~p, ~p, Data, State)~n"
+ "Stacktrace: ~p~n"
+ "Data: ~p~n"
+ "State: ~p~n",
+ [Class, Exception, StreamID, IsFin, Stacktrace, Data, State]};
+make_error_log(info, [StreamID, Msg, State], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:info(~p, Msg, State)~n"
+ "Stacktrace: ~p~n"
+ "Msg: ~p~n"
+ "State: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Msg, State]};
+make_error_log(terminate, [StreamID, Reason, State], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:terminate(~p, Reason, State)~n"
+ "Stacktrace: ~p~n"
+ "Reason: ~p~n"
+ "State: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Reason, State]};
+make_error_log(early_error, [StreamID, Reason, PartialReq, Resp, Opts],
+ Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:early_error(~p, Reason, PartialReq, Resp, Opts)~n"
+ "Stacktrace: ~p~n"
+ "Reason: ~p~n"
+ "PartialReq: ~p~n"
+ "Resp: ~p~n"
+ "Opts: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Reason, PartialReq, Resp, Opts]}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_stream_h.erl b/server/_build/default/lib/cowboy/src/cowboy_stream_h.erl
new file mode 100644
index 0000000..f516f3d
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_stream_h.erl
@@ -0,0 +1,324 @@
+%% Copyright (c) 2016-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_stream_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-export([request_process/3]).
+-export([resume/5]).
+
+-record(state, {
+ next :: any(),
+ ref = undefined :: ranch:ref(),
+ pid = undefined :: pid(),
+ expect = undefined :: undefined | continue,
+ read_body_pid = undefined :: pid() | undefined,
+ read_body_ref = undefined :: reference() | undefined,
+ read_body_timer_ref = undefined :: reference() | undefined,
+ read_body_length = 0 :: non_neg_integer() | infinity | auto,
+ read_body_is_fin = nofin :: nofin | {fin, non_neg_integer()},
+ read_body_buffer = <<>> :: binary(),
+ body_length = 0 :: non_neg_integer(),
+ stream_body_pid = undefined :: pid() | undefined,
+ stream_body_status = normal :: normal | blocking | blocked
+}).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {[{spawn, pid(), timeout()}], #state{}}.
+init(StreamID, Req=#{ref := Ref}, Opts) ->
+ Env = maps:get(env, Opts, #{}),
+ Middlewares = maps:get(middlewares, Opts, [cowboy_router, cowboy_handler]),
+ Shutdown = maps:get(shutdown_timeout, Opts, 5000),
+ Pid = proc_lib:spawn_link(?MODULE, request_process, [Req, Env, Middlewares]),
+ Expect = expect(Req),
+ {Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),
+ {[{spawn, Pid, Shutdown}|Commands],
+ #state{next=Next, ref=Ref, pid=Pid, expect=Expect}}.
+
+%% Ignore the expect header in HTTP/1.0.
+expect(#{version := 'HTTP/1.0'}) ->
+ undefined;
+expect(Req) ->
+ try cowboy_req:parse_header(<<"expect">>, Req) of
+ Expect ->
+ Expect
+ catch _:_ ->
+ undefined
+ end.
+
+%% If we receive data and stream is waiting for data:
+%% If we accumulated enough data or IsFin=fin, send it.
+%% If we are in auto mode, send it and update flow control.
+%% If not, buffer it.
+%% If not, buffer it.
+%%
+%% We always reset the expect field when we receive data,
+%% since the client started sending the request body before
+%% we could send a 100 continue response.
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+%% Stream isn't waiting for data.
+data(StreamID, IsFin, Data, State=#state{
+ read_body_ref=undefined, read_body_buffer=Buffer, body_length=BodyLen}) ->
+ do_data(StreamID, IsFin, Data, [], State#state{
+ expect=undefined,
+ read_body_is_fin=IsFin,
+ read_body_buffer= << Buffer/binary, Data/binary >>,
+ body_length=BodyLen + byte_size(Data)
+ });
+%% Stream is waiting for data using auto mode.
+%%
+%% There is no buffering done in auto mode.
+data(StreamID, IsFin, Data, State=#state{read_body_pid=Pid, read_body_ref=Ref,
+ read_body_length=auto, body_length=BodyLen}) ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Data),
+ do_data(StreamID, IsFin, Data, [{flow, byte_size(Data)}], State#state{
+ read_body_ref=undefined,
+ %% @todo This is wrong, it's missing byte_size(Data).
+ body_length=BodyLen
+ });
+%% Stream is waiting for data but we didn't receive enough to send yet.
+data(StreamID, IsFin=nofin, Data, State=#state{
+ read_body_length=ReadLen, read_body_buffer=Buffer, body_length=BodyLen})
+ when byte_size(Data) + byte_size(Buffer) < ReadLen ->
+ do_data(StreamID, IsFin, Data, [], State#state{
+ expect=undefined,
+ read_body_buffer= << Buffer/binary, Data/binary >>,
+ body_length=BodyLen + byte_size(Data)
+ });
+%% Stream is waiting for data and we received enough to send.
+data(StreamID, IsFin, Data, State=#state{read_body_pid=Pid, read_body_ref=Ref,
+ read_body_timer_ref=TRef, read_body_buffer=Buffer, body_length=BodyLen0}) ->
+ BodyLen = BodyLen0 + byte_size(Data),
+ ok = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
+ send_request_body(Pid, Ref, IsFin, BodyLen, <<Buffer/binary, Data/binary>>),
+ do_data(StreamID, IsFin, Data, [], State#state{
+ expect=undefined,
+ read_body_ref=undefined,
+ read_body_timer_ref=undefined,
+ read_body_buffer= <<>>,
+ body_length=BodyLen
+ }).
+
+do_data(StreamID, IsFin, Data, Commands1, State=#state{next=Next0}) ->
+ {Commands2, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
+ {Commands1 ++ Commands2, State#state{next=Next}}.
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+info(StreamID, Info={'EXIT', Pid, normal}, State=#state{pid=Pid}) ->
+ do_info(StreamID, Info, [stop], State);
+info(StreamID, Info={'EXIT', Pid, {{request_error, Reason, _HumanReadable}, _}},
+ State=#state{pid=Pid}) ->
+ Status = case Reason of
+ timeout -> 408;
+ payload_too_large -> 413;
+ _ -> 400
+ end,
+ %% @todo Headers? Details in body? Log the crash? More stuff in debug only?
+ do_info(StreamID, Info, [
+ {error_response, Status, #{<<"content-length">> => <<"0">>}, <<>>},
+ stop
+ ], State);
+info(StreamID, Exit={'EXIT', Pid, {Reason, Stacktrace}}, State=#state{ref=Ref, pid=Pid}) ->
+ Commands0 = [{internal_error, Exit, 'Stream process crashed.'}],
+ Commands = case Reason of
+ normal -> Commands0;
+ shutdown -> Commands0;
+ {shutdown, _} -> Commands0;
+ _ -> [{log, error,
+ "Ranch listener ~p, connection process ~p, stream ~p "
+ "had its request process ~p exit with reason "
+ "~999999p and stacktrace ~999999p~n",
+ [Ref, self(), StreamID, Pid, Reason, Stacktrace]}
+ |Commands0]
+ end,
+ do_info(StreamID, Exit, [
+ {error_response, 500, #{<<"content-length">> => <<"0">>}, <<>>}
+ |Commands], State);
+%% Request body, auto mode, no body buffered.
+info(StreamID, Info={read_body, Pid, Ref, auto, infinity}, State=#state{read_body_buffer= <<>>}) ->
+ do_info(StreamID, Info, [], State#state{
+ read_body_pid=Pid,
+ read_body_ref=Ref,
+ read_body_length=auto
+ });
+%% Request body, auto mode, body buffered or complete.
+info(StreamID, Info={read_body, Pid, Ref, auto, infinity}, State=#state{
+ read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen}) ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
+ do_info(StreamID, Info, [{flow, byte_size(Buffer)}],
+ State#state{read_body_buffer= <<>>});
+%% Request body, body buffered large enough or complete.
+%%
+%% We do not send a 100 continue response if the client
+%% already started sending the body.
+info(StreamID, Info={read_body, Pid, Ref, Length, _}, State=#state{
+ read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen})
+ when IsFin =:= fin; byte_size(Buffer) >= Length ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
+ do_info(StreamID, Info, [], State#state{read_body_buffer= <<>>});
+%% Request body, not enough to send yet.
+info(StreamID, Info={read_body, Pid, Ref, Length, Period}, State=#state{expect=Expect}) ->
+ Commands = case Expect of
+ continue -> [{inform, 100, #{}}, {flow, Length}];
+ undefined -> [{flow, Length}]
+ end,
+ TRef = erlang:send_after(Period, self(), {{self(), StreamID}, {read_body_timeout, Ref}}),
+ do_info(StreamID, Info, Commands, State#state{
+ read_body_pid=Pid,
+ read_body_ref=Ref,
+ read_body_timer_ref=TRef,
+ read_body_length=Length
+ });
+%% Request body reading timeout; send what we got.
+info(StreamID, Info={read_body_timeout, Ref}, State=#state{read_body_pid=Pid, read_body_ref=Ref,
+ read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen}) ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
+ do_info(StreamID, Info, [], State#state{
+ read_body_ref=undefined,
+ read_body_timer_ref=undefined,
+ read_body_buffer= <<>>
+ });
+info(StreamID, Info={read_body_timeout, _}, State) ->
+ do_info(StreamID, Info, [], State);
+%% Response.
+%%
+%% We reset the expect field when a 100 continue response
+%% is sent or when any final response is sent.
+info(StreamID, Inform={inform, Status, _}, State0) ->
+ State = case cow_http:status_to_integer(Status) of
+ 100 -> State0#state{expect=undefined};
+ _ -> State0
+ end,
+ do_info(StreamID, Inform, [Inform], State);
+info(StreamID, Response={response, _, _, _}, State) ->
+ do_info(StreamID, Response, [Response], State#state{expect=undefined});
+info(StreamID, Headers={headers, _, _}, State) ->
+ do_info(StreamID, Headers, [Headers], State#state{expect=undefined});
+%% Sending data involves the data message, the stream_buffer_full alarm
+%% and the connection_buffer_full alarm. We stop sending acks when an alarm is on.
+%%
+%% We only apply backpressure when the message includes a pid. Otherwise
+%% it is a message from Cowboy, or the user circumventing the backpressure.
+%%
+%% We currently do not support sending data from multiple processes concurrently.
+info(StreamID, Data={data, _, _}, State) ->
+ do_info(StreamID, Data, [Data], State);
+info(StreamID, Data0={data, Pid, _, _}, State0=#state{stream_body_status=Status}) ->
+ State = case Status of
+ normal ->
+ Pid ! {data_ack, self()},
+ State0;
+ blocking ->
+ State0#state{stream_body_pid=Pid, stream_body_status=blocked};
+ blocked ->
+ State0
+ end,
+ Data = erlang:delete_element(2, Data0),
+ do_info(StreamID, Data, [Data], State);
+info(StreamID, Alarm={alarm, Name, on}, State0=#state{stream_body_status=Status})
+ when Name =:= connection_buffer_full; Name =:= stream_buffer_full ->
+ State = case Status of
+ normal -> State0#state{stream_body_status=blocking};
+ _ -> State0
+ end,
+ do_info(StreamID, Alarm, [], State);
+info(StreamID, Alarm={alarm, Name, off}, State=#state{stream_body_pid=Pid, stream_body_status=Status})
+ when Name =:= connection_buffer_full; Name =:= stream_buffer_full ->
+ _ = case Status of
+ normal -> ok;
+ blocking -> ok;
+ blocked -> Pid ! {data_ack, self()}
+ end,
+ do_info(StreamID, Alarm, [], State#state{stream_body_pid=undefined, stream_body_status=normal});
+info(StreamID, Trailers={trailers, _}, State) ->
+ do_info(StreamID, Trailers, [Trailers], State);
+info(StreamID, Push={push, _, _, _, _, _, _, _}, State) ->
+ do_info(StreamID, Push, [Push], State);
+info(StreamID, SwitchProtocol={switch_protocol, _, _, _}, State) ->
+ do_info(StreamID, SwitchProtocol, [SwitchProtocol], State#state{expect=undefined});
+%% Convert the set_options message to a command.
+info(StreamID, SetOptions={set_options, _}, State) ->
+ do_info(StreamID, SetOptions, [SetOptions], State);
+%% Unknown message, either stray or meant for a handler down the line.
+info(StreamID, Info, State) ->
+ do_info(StreamID, Info, [], State).
+
+do_info(StreamID, Info, Commands1, State0=#state{next=Next0}) ->
+ {Commands2, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ {Commands1 ++ Commands2, State0#state{next=Next}}.
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> ok.
+terminate(StreamID, Reason, #state{next=Next}) ->
+ cowboy_stream:terminate(StreamID, Reason, Next).
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
+
+send_request_body(Pid, Ref, nofin, _, Data) ->
+ Pid ! {request_body, Ref, nofin, Data},
+ ok;
+send_request_body(Pid, Ref, fin, BodyLen, Data) ->
+ Pid ! {request_body, Ref, fin, BodyLen, Data},
+ ok.
+
+%% Request process.
+
+%% We add the stacktrace to exit exceptions here in order
+%% to simplify the debugging of errors. The proc_lib library
+%% already adds the stacktrace to other types of exceptions.
+-spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
+request_process(Req, Env, Middlewares) ->
+ try
+ execute(Req, Env, Middlewares)
+ catch
+ exit:Reason={shutdown, _}:Stacktrace ->
+ erlang:raise(exit, Reason, Stacktrace);
+ exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->
+ erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)
+ end.
+
+execute(_, _, []) ->
+ ok;
+execute(Req, Env, [Middleware|Tail]) ->
+ case Middleware:execute(Req, Env) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module, Function, Args} ->
+ proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
+ {stop, _Req2} ->
+ ok
+ end.
+
+-spec resume(cowboy_middleware:env(), [module()], module(), atom(), [any()]) -> ok.
+resume(Env, Tail, Module, Function, Args) ->
+ case apply(Module, Function, Args) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module2, Function2, Args2} ->
+ proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module2, Function2, Args2]);
+ {stop, _Req2} ->
+ ok
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl b/server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl
new file mode 100644
index 0000000..6714289
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl
@@ -0,0 +1,24 @@
+%% Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2013, James Fish <james@fishcakez.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_sub_protocol).
+
+-callback upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+
+-callback upgrade(Req, Env, module(), any(), any())
+ -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
diff --git a/server/_build/default/lib/cowboy/src/cowboy_sup.erl b/server/_build/default/lib/cowboy/src/cowboy_sup.erl
new file mode 100644
index 0000000..d3ac3b0
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_sup.erl
@@ -0,0 +1,30 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_sup).
+-behaviour(supervisor).
+
+-export([start_link/0]).
+-export([init/1]).
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+-spec init([])
+ -> {ok, {{supervisor:strategy(), 10, 10}, [supervisor:child_spec()]}}.
+init([]) ->
+ Procs = [{cowboy_clock, {cowboy_clock, start_link, []},
+ permanent, 5000, worker, [cowboy_clock]}],
+ {ok, {{one_for_one, 10, 10}, Procs}}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_tls.erl b/server/_build/default/lib/cowboy/src/cowboy_tls.erl
new file mode 100644
index 0000000..c049ecb
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_tls.erl
@@ -0,0 +1,56 @@
+%% Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_tls).
+-behavior(ranch_protocol).
+
+-export([start_link/3]).
+-export([start_link/4]).
+-export([connection_process/4]).
+
+%% Ranch 1.
+-spec start_link(ranch:ref(), ssl:sslsocket(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, _Socket, Transport, Opts) ->
+ start_link(Ref, Transport, Opts).
+
+%% Ranch 2.
+-spec start_link(ranch:ref(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, Transport, Opts) ->
+ Pid = proc_lib:spawn_link(?MODULE, connection_process,
+ [self(), Ref, Transport, Opts]),
+ {ok, Pid}.
+
+-spec connection_process(pid(), ranch:ref(), module(), cowboy:opts()) -> ok.
+connection_process(Parent, Ref, Transport, Opts) ->
+ ProxyInfo = case maps:get(proxy_header, Opts, false) of
+ true ->
+ {ok, ProxyInfo0} = ranch:recv_proxy_header(Ref, 1000),
+ ProxyInfo0;
+ false ->
+ undefined
+ end,
+ {ok, Socket} = ranch:handshake(Ref),
+ case ssl:negotiated_protocol(Socket) of
+ {ok, <<"h2">>} ->
+ init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http2);
+ _ -> %% http/1.1 or no protocol negotiated.
+ init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http)
+ end.
+
+init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->
+ _ = case maps:get(connection_type, Opts, supervisor) of
+ worker -> ok;
+ supervisor -> process_flag(trap_exit, true)
+ end,
+ Protocol:init(Parent, Ref, Socket, Transport, ProxyInfo, Opts).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl b/server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl
new file mode 100644
index 0000000..9a19ae1
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl
@@ -0,0 +1,192 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_tracer_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-export([set_trace_patterns/0]).
+
+-export([tracer_process/3]).
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type match_predicate()
+ :: fun((cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) -> boolean()).
+
+-type tracer_match_specs() :: [match_predicate()
+ | {method, binary()}
+ | {host, binary()}
+ | {path, binary()}
+ | {path_start, binary()}
+ | {header, binary()}
+ | {header, binary(), binary()}
+ | {peer_ip, inet:ip_address()}
+].
+-export_type([tracer_match_specs/0]).
+
+-type tracer_callback() :: fun((init | terminate | tuple(), any()) -> any()).
+-export_type([tracer_callback/0]).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {cowboy_stream:commands(), any()}.
+init(StreamID, Req, Opts) ->
+ init_tracer(StreamID, Req, Opts),
+ cowboy_stream:init(StreamID, Req, Opts).
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::any().
+data(StreamID, IsFin, Data, Next) ->
+ cowboy_stream:data(StreamID, IsFin, Data, Next).
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::any().
+info(StreamID, Info, Next) ->
+ cowboy_stream:info(StreamID, Info, Next).
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), any()) -> any().
+terminate(StreamID, Reason, Next) ->
+ cowboy_stream:terminate(StreamID, Reason, Next).
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
+
+%% API.
+
+%% These trace patterns are most likely not suitable for production.
+-spec set_trace_patterns() -> ok.
+set_trace_patterns() ->
+ erlang:trace_pattern({'_', '_', '_'}, [{'_', [], [{return_trace}]}], [local]),
+ erlang:trace_pattern(on_load, [{'_', [], [{return_trace}]}], [local]),
+ ok.
+
+%% Internal.
+
+init_tracer(StreamID, Req, Opts=#{tracer_match_specs := List, tracer_callback := _}) ->
+ case match(List, StreamID, Req, Opts) of
+ false ->
+ ok;
+ true ->
+ start_tracer(StreamID, Req, Opts)
+ end;
+%% When the options tracer_match_specs or tracer_callback
+%% are not provided we do not enable tracing.
+init_tracer(_, _, _) ->
+ ok.
+
+match([], _, _, _) ->
+ true;
+match([Predicate|Tail], StreamID, Req, Opts) when is_function(Predicate) ->
+ case Predicate(StreamID, Req, Opts) of
+ true -> match(Tail, StreamID, Req, Opts);
+ false -> false
+ end;
+match([{method, Value}|Tail], StreamID, Req=#{method := Value}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match([{host, Value}|Tail], StreamID, Req=#{host := Value}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match([{path, Value}|Tail], StreamID, Req=#{path := Value}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match([{path_start, PathStart}|Tail], StreamID, Req=#{path := Path}, Opts) ->
+ Len = byte_size(PathStart),
+ case Path of
+ <<PathStart:Len/binary, _/bits>> -> match(Tail, StreamID, Req, Opts);
+ _ -> false
+ end;
+match([{header, Name}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->
+ case Headers of
+ #{Name := _} -> match(Tail, StreamID, Req, Opts);
+ _ -> false
+ end;
+match([{header, Name, Value}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->
+ case Headers of
+ #{Name := Value} -> match(Tail, StreamID, Req, Opts);
+ _ -> false
+ end;
+match([{peer_ip, IP}|Tail], StreamID, Req=#{peer := {IP, _}}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match(_, _, _, _) ->
+ false.
+
+%% We only start the tracer if one wasn't started before.
+start_tracer(StreamID, Req, Opts) ->
+ case erlang:trace_info(self(), tracer) of
+ {tracer, []} ->
+ TracerPid = proc_lib:spawn_link(?MODULE, tracer_process, [StreamID, Req, Opts]),
+ %% The default flags are probably not suitable for production.
+ Flags = maps:get(tracer_flags, Opts, [
+ send, 'receive', call, return_to,
+ procs, ports, monotonic_timestamp,
+ %% The set_on_spawn flag is necessary to catch events
+ %% from request processes.
+ set_on_spawn
+ ]),
+ erlang:trace(self(), true, [{tracer, TracerPid}|Flags]),
+ ok;
+ _ ->
+ ok
+ end.
+
+%% Tracer process.
+
+-spec tracer_process(_, _, _) -> no_return().
+tracer_process(StreamID, Req=#{pid := Parent}, Opts=#{tracer_callback := Fun}) ->
+ %% This is necessary because otherwise the tracer could stop
+ %% before it has finished processing the events in its queue.
+ process_flag(trap_exit, true),
+ State = Fun(init, {StreamID, Req, Opts}),
+ tracer_loop(Parent, Opts, State).
+
+tracer_loop(Parent, Opts=#{tracer_callback := Fun}, State0) ->
+ receive
+ Msg when element(1, Msg) =:= trace; element(1, Msg) =:= trace_ts ->
+ State = Fun(Msg, State0),
+ tracer_loop(Parent, Opts, State);
+ {'EXIT', Parent, Reason} ->
+ tracer_terminate(Reason, Opts, State0);
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {Opts, State0});
+ Msg ->
+ cowboy:log(warning, "~p: Tracer process received stray message ~9999p~n",
+ [?MODULE, Msg], Opts),
+ tracer_loop(Parent, Opts, State0)
+ end.
+
+-spec tracer_terminate(_, _, _) -> no_return().
+tracer_terminate(Reason, #{tracer_callback := Fun}, State) ->
+ _ = Fun(terminate, State),
+ exit(Reason).
+
+%% System callbacks.
+
+-spec system_continue(pid(), _, {cowboy:opts(), any()}) -> no_return().
+system_continue(Parent, _, {Opts, State}) ->
+ tracer_loop(Parent, Opts, State).
+
+-spec system_terminate(any(), _, _, _) -> no_return().
+system_terminate(Reason, _, _, {Opts, State}) ->
+ tracer_terminate(Reason, Opts, State).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::any().
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_websocket.erl b/server/_build/default/lib/cowboy/src/cowboy_websocket.erl
new file mode 100644
index 0000000..e7d8f31
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_websocket.erl
@@ -0,0 +1,707 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Cowboy supports versions 7 through 17 of the Websocket drafts.
+%% It also supports RFC6455, the proposed standard for Websocket.
+-module(cowboy_websocket).
+-behaviour(cowboy_sub_protocol).
+
+-export([is_upgrade_request/1]).
+-export([upgrade/4]).
+-export([upgrade/5]).
+-export([takeover/7]).
+-export([loop/3]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type commands() :: [cow_ws:frame()
+ | {active, boolean()}
+ | {deflate, boolean()}
+ | {set_options, map()}
+ | {shutdown_reason, any()}
+].
+-export_type([commands/0]).
+
+-type call_result(State) :: {commands(), State} | {commands(), State, hibernate}.
+
+-type deprecated_call_result(State) :: {ok, State}
+ | {ok, State, hibernate}
+ | {reply, cow_ws:frame() | [cow_ws:frame()], State}
+ | {reply, cow_ws:frame() | [cow_ws:frame()], State, hibernate}
+ | {stop, State}.
+
+-type terminate_reason() :: normal | stop | timeout
+ | remote | {remote, cow_ws:close_code(), binary()}
+ | {error, badencoding | badframe | closed | atom()}
+ | {crash, error | exit | throw, any()}.
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback websocket_init(State)
+ -> call_result(State) | deprecated_call_result(State) when State::any().
+-optional_callbacks([websocket_init/1]).
+
+-callback websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State)
+ -> call_result(State) | deprecated_call_result(State) when State::any().
+-callback websocket_info(any(), State)
+ -> call_result(State) | deprecated_call_result(State) when State::any().
+
+-callback terminate(any(), cowboy_req:req(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+-type opts() :: #{
+ active_n => pos_integer(),
+ compress => boolean(),
+ deflate_opts => cow_ws:deflate_opts(),
+ idle_timeout => timeout(),
+ max_frame_size => non_neg_integer() | infinity,
+ req_filter => fun((cowboy_req:req()) -> map()),
+ validate_utf8 => boolean()
+}.
+-export_type([opts/0]).
+
+-record(state, {
+ parent :: undefined | pid(),
+ ref :: ranch:ref(),
+ socket = undefined :: inet:socket() | {pid(), cowboy_stream:streamid()} | undefined,
+ transport = undefined :: module() | undefined,
+ opts = #{} :: opts(),
+ active = true :: boolean(),
+ handler :: module(),
+ key = undefined :: undefined | binary(),
+ timeout_ref = undefined :: undefined | reference(),
+ messages = undefined :: undefined | {atom(), atom(), atom()}
+ | {atom(), atom(), atom(), atom()},
+ hibernate = false :: boolean(),
+ frag_state = undefined :: cow_ws:frag_state(),
+ frag_buffer = <<>> :: binary(),
+ utf8_state :: cow_ws:utf8_state(),
+ deflate = true :: boolean(),
+ extensions = #{} :: map(),
+ req = #{} :: map(),
+ shutdown_reason = normal :: any()
+}).
+
+%% Because the HTTP/1.1 and HTTP/2 handshakes are so different,
+%% this function is necessary to figure out whether a request
+%% is trying to upgrade to the Websocket protocol.
+
+-spec is_upgrade_request(cowboy_req:req()) -> boolean().
+is_upgrade_request(#{version := 'HTTP/2', method := <<"CONNECT">>, protocol := Protocol}) ->
+ <<"websocket">> =:= cowboy_bstr:to_lower(Protocol);
+is_upgrade_request(Req=#{version := 'HTTP/1.1', method := <<"GET">>}) ->
+ ConnTokens = cowboy_req:parse_header(<<"connection">>, Req, []),
+ case lists:member(<<"upgrade">>, ConnTokens) of
+ false ->
+ false;
+ true ->
+ UpgradeTokens = cowboy_req:parse_header(<<"upgrade">>, Req),
+ lists:member(<<"websocket">>, UpgradeTokens)
+ end;
+is_upgrade_request(_) ->
+ false.
+
+%% Stream process.
+
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerState) ->
+ upgrade(Req, Env, Handler, HandlerState, #{}).
+
+-spec upgrade(Req, Env, module(), any(), opts())
+ -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+%% @todo Immediately crash if a response has already been sent.
+upgrade(Req0=#{version := Version}, Env, Handler, HandlerState, Opts) ->
+ FilteredReq = case maps:get(req_filter, Opts, undefined) of
+ undefined -> maps:with([method, version, scheme, host, port, path, qs, peer], Req0);
+ FilterFun -> FilterFun(Req0)
+ end,
+ Utf8State = case maps:get(validate_utf8, Opts, true) of
+ true -> 0;
+ false -> undefined
+ end,
+ State0 = #state{opts=Opts, handler=Handler, utf8_state=Utf8State, req=FilteredReq},
+ try websocket_upgrade(State0, Req0) of
+ {ok, State, Req} ->
+ websocket_handshake(State, Req, HandlerState, Env);
+ %% The status code 426 is specific to HTTP/1.1 connections.
+ {error, upgrade_required} when Version =:= 'HTTP/1.1' ->
+ {ok, cowboy_req:reply(426, #{
+ <<"connection">> => <<"upgrade">>,
+ <<"upgrade">> => <<"websocket">>
+ }, Req0), Env};
+ %% Use a generic 400 error for HTTP/2.
+ {error, upgrade_required} ->
+ {ok, cowboy_req:reply(400, Req0), Env}
+ catch _:_ ->
+ %% @todo Probably log something here?
+ %% @todo Test that we can have 2 /ws 400 status code in a row on the same connection.
+ %% @todo Does this even work?
+ {ok, cowboy_req:reply(400, Req0), Env}
+ end.
+
+websocket_upgrade(State, Req=#{version := Version}) ->
+ case is_upgrade_request(Req) of
+ false ->
+ {error, upgrade_required};
+ true when Version =:= 'HTTP/1.1' ->
+ Key = cowboy_req:header(<<"sec-websocket-key">>, Req),
+ false = Key =:= undefined,
+ websocket_version(State#state{key=Key}, Req);
+ true ->
+ websocket_version(State, Req)
+ end.
+
+websocket_version(State, Req) ->
+ WsVersion = cowboy_req:parse_header(<<"sec-websocket-version">>, Req),
+ case WsVersion of
+ 7 -> ok;
+ 8 -> ok;
+ 13 -> ok
+ end,
+ websocket_extensions(State, Req#{websocket_version => WsVersion}).
+
+websocket_extensions(State=#state{opts=Opts}, Req) ->
+ %% @todo We want different options for this. For example
+ %% * compress everything auto
+ %% * compress only text auto
+ %% * compress only binary auto
+ %% * compress nothing auto (but still enabled it)
+ %% * disable compression
+ Compress = maps:get(compress, Opts, false),
+ case {Compress, cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req)} of
+ {true, Extensions} when Extensions =/= undefined ->
+ websocket_extensions(State, Req, Extensions, []);
+ _ ->
+ {ok, State, Req}
+ end.
+
+websocket_extensions(State, Req, [], []) ->
+ {ok, State, Req};
+websocket_extensions(State, Req, [], [<<", ">>|RespHeader]) ->
+ {ok, State, cowboy_req:set_resp_header(<<"sec-websocket-extensions">>, lists:reverse(RespHeader), Req)};
+%% For HTTP/2 we ARE on the controlling process and do NOT want to update the owner.
+websocket_extensions(State=#state{opts=Opts, extensions=Extensions},
+ Req=#{pid := Pid, version := Version},
+ [{<<"permessage-deflate">>, Params}|Tail], RespHeader) ->
+ DeflateOpts0 = maps:get(deflate_opts, Opts, #{}),
+ DeflateOpts = case Version of
+ 'HTTP/1.1' -> DeflateOpts0#{owner => Pid};
+ _ -> DeflateOpts0
+ end,
+ try cow_ws:negotiate_permessage_deflate(Params, Extensions, DeflateOpts) of
+ {ok, RespExt, Extensions2} ->
+ websocket_extensions(State#state{extensions=Extensions2},
+ Req, Tail, [<<", ">>, RespExt|RespHeader]);
+ ignore ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ catch exit:{error, incompatible_zlib_version, _} ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ end;
+websocket_extensions(State=#state{opts=Opts, extensions=Extensions},
+ Req=#{pid := Pid, version := Version},
+ [{<<"x-webkit-deflate-frame">>, Params}|Tail], RespHeader) ->
+ DeflateOpts0 = maps:get(deflate_opts, Opts, #{}),
+ DeflateOpts = case Version of
+ 'HTTP/1.1' -> DeflateOpts0#{owner => Pid};
+ _ -> DeflateOpts0
+ end,
+ try cow_ws:negotiate_x_webkit_deflate_frame(Params, Extensions, DeflateOpts) of
+ {ok, RespExt, Extensions2} ->
+ websocket_extensions(State#state{extensions=Extensions2},
+ Req, Tail, [<<", ">>, RespExt|RespHeader]);
+ ignore ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ catch exit:{error, incompatible_zlib_version, _} ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ end;
+websocket_extensions(State, Req, [_|Tail], RespHeader) ->
+ websocket_extensions(State, Req, Tail, RespHeader).
+
+-spec websocket_handshake(#state{}, Req, any(), Env)
+ -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+websocket_handshake(State=#state{key=Key},
+ Req=#{version := 'HTTP/1.1', pid := Pid, streamid := StreamID},
+ HandlerState, Env) ->
+ Challenge = base64:encode(crypto:hash(sha,
+ << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
+ %% @todo We don't want date and server headers.
+ Headers = cowboy_req:response_headers(#{
+ <<"connection">> => <<"Upgrade">>,
+ <<"upgrade">> => <<"websocket">>,
+ <<"sec-websocket-accept">> => Challenge
+ }, Req),
+ Pid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE, {State, HandlerState}}},
+ {ok, Req, Env};
+%% For HTTP/2 we do not let the process die, we instead keep it
+%% for the Websocket stream. This is because in HTTP/2 we only
+%% have a stream, it doesn't take over the whole connection.
+websocket_handshake(State, Req=#{ref := Ref, pid := Pid, streamid := StreamID},
+ HandlerState, _Env) ->
+ %% @todo We don't want date and server headers.
+ Headers = cowboy_req:response_headers(#{}, Req),
+ Pid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE, {State, HandlerState}}},
+ takeover(Pid, Ref, {Pid, StreamID}, undefined, undefined, <<>>,
+ {State, HandlerState}).
+
+%% Connection process.
+
+-record(ps_header, {
+ buffer = <<>> :: binary()
+}).
+
+-record(ps_payload, {
+ type :: cow_ws:frame_type(),
+ len :: non_neg_integer(),
+ mask_key :: cow_ws:mask_key(),
+ rsv :: cow_ws:rsv(),
+ close_code = undefined :: undefined | cow_ws:close_code(),
+ unmasked = <<>> :: binary(),
+ unmasked_len = 0 :: non_neg_integer(),
+ buffer = <<>> :: binary()
+}).
+
+-type parse_state() :: #ps_header{} | #ps_payload{}.
+
+-spec takeover(pid(), ranch:ref(), inet:socket() | {pid(), cowboy_stream:streamid()},
+ module() | undefined, any(), binary(),
+ {#state{}, any()}) -> no_return().
+takeover(Parent, Ref, Socket, Transport, _Opts, Buffer,
+ {State0=#state{handler=Handler}, HandlerState}) ->
+ %% @todo We should have an option to disable this behavior.
+ ranch:remove_connection(Ref),
+ Messages = case Transport of
+ undefined -> undefined;
+ _ -> Transport:messages()
+ end,
+ State = loop_timeout(State0#state{parent=Parent,
+ ref=Ref, socket=Socket, transport=Transport,
+ key=undefined, messages=Messages}),
+ %% We call parse_header/3 immediately because there might be
+ %% some data in the buffer that was sent along with the handshake.
+ %% While it is not allowed by the protocol to send frames immediately,
+ %% we still want to process that data if any.
+ case erlang:function_exported(Handler, websocket_init, 1) of
+ true -> handler_call(State, HandlerState, #ps_header{buffer=Buffer},
+ websocket_init, undefined, fun after_init/3);
+ false -> after_init(State, HandlerState, #ps_header{buffer=Buffer})
+ end.
+
+after_init(State=#state{active=true}, HandlerState, ParseState) ->
+ %% Enable active,N for HTTP/1.1, and auto read_body for HTTP/2.
+ %% We must do this only after calling websocket_init/1 (if any)
+ %% to give the handler a chance to disable active mode immediately.
+ setopts_active(State),
+ maybe_read_body(State),
+ parse_header(State, HandlerState, ParseState);
+after_init(State, HandlerState, ParseState) ->
+ parse_header(State, HandlerState, ParseState).
+
+%% We have two ways of reading the body for Websocket. For HTTP/1.1
+%% we have full control of the socket and can therefore use active,N.
+%% For HTTP/2 we are just a stream, and are instead using read_body
+%% (automatic mode). Technically HTTP/2 will only go passive after
+%% receiving the next data message, while HTTP/1.1 goes passive
+%% immediately but there might still be data to be processed in
+%% the message queue.
+
+setopts_active(#state{transport=undefined}) ->
+ ok;
+setopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ N = maps:get(active_n, Opts, 100),
+ Transport:setopts(Socket, [{active, N}]).
+
+maybe_read_body(#state{socket=Stream={Pid, _}, transport=undefined, active=true}) ->
+ %% @todo Keep Ref around.
+ ReadBodyRef = make_ref(),
+ Pid ! {Stream, {read_body, self(), ReadBodyRef, auto, infinity}},
+ ok;
+maybe_read_body(_) ->
+ ok.
+
+active(State) ->
+ setopts_active(State),
+ maybe_read_body(State),
+ State#state{active=true}.
+
+passive(State=#state{transport=undefined}) ->
+ %% Unfortunately we cannot currently cancel read_body.
+ %% But that's OK, we will just stop reading the body
+ %% after the next message.
+ State#state{active=false};
+passive(State=#state{socket=Socket, transport=Transport, messages=Messages}) ->
+ Transport:setopts(Socket, [{active, false}]),
+ flush_passive(Socket, Messages),
+ State#state{active=false}.
+
+flush_passive(Socket, Messages) ->
+ receive
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ flush_passive(Socket, Messages)
+ after 0 ->
+ ok
+ end.
+
+before_loop(State=#state{hibernate=true}, HandlerState, ParseState) ->
+ proc_lib:hibernate(?MODULE, loop,
+ [State#state{hibernate=false}, HandlerState, ParseState]);
+before_loop(State, HandlerState, ParseState) ->
+ loop(State, HandlerState, ParseState).
+
+-spec loop_timeout(#state{}) -> #state{}.
+loop_timeout(State=#state{opts=Opts, timeout_ref=PrevRef}) ->
+ _ = case PrevRef of
+ undefined -> ignore;
+ PrevRef -> erlang:cancel_timer(PrevRef)
+ end,
+ case maps:get(idle_timeout, Opts, 60000) of
+ infinity ->
+ State#state{timeout_ref=undefined};
+ Timeout ->
+ TRef = erlang:start_timer(Timeout, self(), ?MODULE),
+ State#state{timeout_ref=TRef}
+ end.
+
+-spec loop(#state{}, any(), parse_state()) -> no_return().
+loop(State=#state{parent=Parent, socket=Socket, messages=Messages,
+ timeout_ref=TRef}, HandlerState, ParseState) ->
+ receive
+ %% Socket messages. (HTTP/1.1)
+ {OK, Socket, Data} when OK =:= element(1, Messages) ->
+ State2 = loop_timeout(State),
+ parse(State2, HandlerState, ParseState, Data);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ terminate(State, HandlerState, {error, closed});
+ {Error, Socket, Reason} when Error =:= element(3, Messages) ->
+ terminate(State, HandlerState, {error, Reason});
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ setopts_active(State),
+ loop(State, HandlerState, ParseState);
+ %% Body reading messages. (HTTP/2)
+ {request_body, _Ref, nofin, Data} ->
+ maybe_read_body(State),
+ State2 = loop_timeout(State),
+ parse(State2, HandlerState, ParseState, Data);
+ %% @todo We need to handle this case as if it was an {error, closed}
+ %% but not before we finish processing frames. We probably should have
+ %% a check in before_loop to let us stop looping if a flag is set.
+ {request_body, _Ref, fin, _, Data} ->
+ maybe_read_body(State),
+ State2 = loop_timeout(State),
+ parse(State2, HandlerState, ParseState, Data);
+ %% Timeouts.
+ {timeout, TRef, ?MODULE} ->
+ websocket_close(State, HandlerState, timeout);
+ {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
+ before_loop(State, HandlerState, ParseState);
+ %% System messages.
+ {'EXIT', Parent, Reason} ->
+ %% @todo We should exit gracefully.
+ exit(Reason);
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
+ {State, HandlerState, ParseState});
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, [], ?MODULE),
+ before_loop(State, HandlerState, ParseState);
+ Message ->
+ handler_call(State, HandlerState, ParseState,
+ websocket_info, Message, fun before_loop/3)
+ end.
+
+parse(State, HandlerState, PS=#ps_header{buffer=Buffer}, Data) ->
+ parse_header(State, HandlerState, PS#ps_header{
+ buffer= <<Buffer/binary, Data/binary>>});
+parse(State, HandlerState, PS=#ps_payload{buffer=Buffer}, Data) ->
+ parse_payload(State, HandlerState, PS#ps_payload{buffer= <<>>},
+ <<Buffer/binary, Data/binary>>).
+
+parse_header(State=#state{opts=Opts, frag_state=FragState, extensions=Extensions},
+ HandlerState, ParseState=#ps_header{buffer=Data}) ->
+ MaxFrameSize = maps:get(max_frame_size, Opts, infinity),
+ case cow_ws:parse_header(Data, Extensions, FragState) of
+ %% All frames sent from the client to the server are masked.
+ {_, _, _, _, undefined, _} ->
+ websocket_close(State, HandlerState, {error, badframe});
+ {_, _, _, Len, _, _} when Len > MaxFrameSize ->
+ websocket_close(State, HandlerState, {error, badsize});
+ {Type, FragState2, Rsv, Len, MaskKey, Rest} ->
+ parse_payload(State#state{frag_state=FragState2}, HandlerState,
+ #ps_payload{type=Type, len=Len, mask_key=MaskKey, rsv=Rsv}, Rest);
+ more ->
+ before_loop(State, HandlerState, ParseState);
+ error ->
+ websocket_close(State, HandlerState, {error, badframe})
+ end.
+
+parse_payload(State=#state{frag_state=FragState, utf8_state=Incomplete, extensions=Extensions},
+ HandlerState, ParseState=#ps_payload{
+ type=Type, len=Len, mask_key=MaskKey, rsv=Rsv,
+ unmasked=Unmasked, unmasked_len=UnmaskedLen}, Data) ->
+ case cow_ws:parse_payload(Data, MaskKey, Incomplete, UnmaskedLen,
+ Type, Len, FragState, Extensions, Rsv) of
+ {ok, CloseCode, Payload, Utf8State, Rest} ->
+ dispatch_frame(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{unmasked= <<Unmasked/binary, Payload/binary>>,
+ close_code=CloseCode}, Rest);
+ {ok, Payload, Utf8State, Rest} ->
+ dispatch_frame(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{unmasked= <<Unmasked/binary, Payload/binary>>},
+ Rest);
+ {more, CloseCode, Payload, Utf8State} ->
+ before_loop(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{len=Len - byte_size(Data), close_code=CloseCode,
+ unmasked= <<Unmasked/binary, Payload/binary>>,
+ unmasked_len=UnmaskedLen + byte_size(Data)});
+ {more, Payload, Utf8State} ->
+ before_loop(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{len=Len - byte_size(Data),
+ unmasked= <<Unmasked/binary, Payload/binary>>,
+ unmasked_len=UnmaskedLen + byte_size(Data)});
+ Error = {error, _Reason} ->
+ websocket_close(State, HandlerState, Error)
+ end.
+
+dispatch_frame(State=#state{opts=Opts, frag_state=FragState, frag_buffer=SoFar}, HandlerState,
+ #ps_payload{type=Type0, unmasked=Payload0, close_code=CloseCode0}, RemainingData) ->
+ MaxFrameSize = maps:get(max_frame_size, Opts, infinity),
+ case cow_ws:make_frame(Type0, Payload0, CloseCode0, FragState) of
+ %% @todo Allow receiving fragments.
+ {fragment, _, _, Payload} when byte_size(Payload) + byte_size(SoFar) > MaxFrameSize ->
+ websocket_close(State, HandlerState, {error, badsize});
+ {fragment, nofin, _, Payload} ->
+ parse_header(State#state{frag_buffer= << SoFar/binary, Payload/binary >>},
+ HandlerState, #ps_header{buffer=RemainingData});
+ {fragment, fin, Type, Payload} ->
+ handler_call(State#state{frag_state=undefined, frag_buffer= <<>>}, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, {Type, << SoFar/binary, Payload/binary >>},
+ fun parse_header/3);
+ close ->
+ websocket_close(State, HandlerState, remote);
+ {close, CloseCode, Payload} ->
+ websocket_close(State, HandlerState, {remote, CloseCode, Payload});
+ Frame = ping ->
+ transport_send(State, nofin, frame(pong, State)),
+ handler_call(State, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, Frame, fun parse_header/3);
+ Frame = {ping, Payload} ->
+ transport_send(State, nofin, frame({pong, Payload}, State)),
+ handler_call(State, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, Frame, fun parse_header/3);
+ Frame ->
+ handler_call(State, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, Frame, fun parse_header/3)
+ end.
+
+handler_call(State=#state{handler=Handler}, HandlerState,
+ ParseState, Callback, Message, NextState) ->
+ try case Callback of
+ websocket_init -> Handler:websocket_init(HandlerState);
+ _ -> Handler:Callback(Message, HandlerState)
+ end of
+ {Commands, HandlerState2} when is_list(Commands) ->
+ handler_call_result(State,
+ HandlerState2, ParseState, NextState, Commands);
+ {Commands, HandlerState2, hibernate} when is_list(Commands) ->
+ handler_call_result(State#state{hibernate=true},
+ HandlerState2, ParseState, NextState, Commands);
+ %% The following call results are deprecated.
+ {ok, HandlerState2} ->
+ NextState(State, HandlerState2, ParseState);
+ {ok, HandlerState2, hibernate} ->
+ NextState(State#state{hibernate=true}, HandlerState2, ParseState);
+ {reply, Payload, HandlerState2} ->
+ case websocket_send(Payload, State) of
+ ok ->
+ NextState(State, HandlerState2, ParseState);
+ stop ->
+ terminate(State, HandlerState2, stop);
+ Error = {error, _} ->
+ terminate(State, HandlerState2, Error)
+ end;
+ {reply, Payload, HandlerState2, hibernate} ->
+ case websocket_send(Payload, State) of
+ ok ->
+ NextState(State#state{hibernate=true},
+ HandlerState2, ParseState);
+ stop ->
+ terminate(State, HandlerState2, stop);
+ Error = {error, _} ->
+ terminate(State, HandlerState2, Error)
+ end;
+ {stop, HandlerState2} ->
+ websocket_close(State, HandlerState2, stop)
+ catch Class:Reason:Stacktrace ->
+ websocket_send_close(State, {crash, Class, Reason}),
+ handler_terminate(State, HandlerState, {crash, Class, Reason}),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+-spec handler_call_result(#state{}, any(), parse_state(), fun(), commands()) -> no_return().
+handler_call_result(State0, HandlerState, ParseState, NextState, Commands) ->
+ case commands(Commands, State0, []) of
+ {ok, State} ->
+ NextState(State, HandlerState, ParseState);
+ {stop, State} ->
+ terminate(State, HandlerState, stop);
+ {Error = {error, _}, State} ->
+ terminate(State, HandlerState, Error)
+ end.
+
+commands([], State, []) ->
+ {ok, State};
+commands([], State, Data) ->
+ Result = transport_send(State, nofin, lists:reverse(Data)),
+ {Result, State};
+commands([{active, Active}|Tail], State0=#state{active=Active0}, Data) when is_boolean(Active) ->
+ State = if
+ Active, not Active0 ->
+ active(State0);
+ Active0, not Active ->
+ passive(State0);
+ true ->
+ State0
+ end,
+ commands(Tail, State#state{active=Active}, Data);
+commands([{deflate, Deflate}|Tail], State, Data) when is_boolean(Deflate) ->
+ commands(Tail, State#state{deflate=Deflate}, Data);
+commands([{set_options, SetOpts}|Tail], State0=#state{opts=Opts}, Data) ->
+ State = case SetOpts of
+ #{idle_timeout := IdleTimeout} ->
+ loop_timeout(State0#state{opts=Opts#{idle_timeout => IdleTimeout}});
+ _ ->
+ State0
+ end,
+ commands(Tail, State, Data);
+commands([{shutdown_reason, ShutdownReason}|Tail], State, Data) ->
+ commands(Tail, State#state{shutdown_reason=ShutdownReason}, Data);
+commands([Frame|Tail], State, Data0) ->
+ Data = [frame(Frame, State)|Data0],
+ case is_close_frame(Frame) of
+ true ->
+ _ = transport_send(State, fin, lists:reverse(Data)),
+ {stop, State};
+ false ->
+ commands(Tail, State, Data)
+ end.
+
+transport_send(#state{socket=Stream={Pid, _}, transport=undefined}, IsFin, Data) ->
+ Pid ! {Stream, {data, IsFin, Data}},
+ ok;
+transport_send(#state{socket=Socket, transport=Transport}, _, Data) ->
+ Transport:send(Socket, Data).
+
+-spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}.
+websocket_send(Frames, State) when is_list(Frames) ->
+ websocket_send_many(Frames, State, []);
+websocket_send(Frame, State) ->
+ Data = frame(Frame, State),
+ case is_close_frame(Frame) of
+ true ->
+ _ = transport_send(State, fin, Data),
+ stop;
+ false ->
+ transport_send(State, nofin, Data)
+ end.
+
+websocket_send_many([], State, Acc) ->
+ transport_send(State, nofin, lists:reverse(Acc));
+websocket_send_many([Frame|Tail], State, Acc0) ->
+ Acc = [frame(Frame, State)|Acc0],
+ case is_close_frame(Frame) of
+ true ->
+ _ = transport_send(State, fin, lists:reverse(Acc)),
+ stop;
+ false ->
+ websocket_send_many(Tail, State, Acc)
+ end.
+
+is_close_frame(close) -> true;
+is_close_frame({close, _}) -> true;
+is_close_frame({close, _, _}) -> true;
+is_close_frame(_) -> false.
+
+-spec websocket_close(#state{}, any(), terminate_reason()) -> no_return().
+websocket_close(State, HandlerState, Reason) ->
+ websocket_send_close(State, Reason),
+ terminate(State, HandlerState, Reason).
+
+websocket_send_close(State, Reason) ->
+ _ = case Reason of
+ Normal when Normal =:= stop; Normal =:= timeout ->
+ transport_send(State, fin, frame({close, 1000, <<>>}, State));
+ {error, badframe} ->
+ transport_send(State, fin, frame({close, 1002, <<>>}, State));
+ {error, badencoding} ->
+ transport_send(State, fin, frame({close, 1007, <<>>}, State));
+ {error, badsize} ->
+ transport_send(State, fin, frame({close, 1009, <<>>}, State));
+ {crash, _, _} ->
+ transport_send(State, fin, frame({close, 1011, <<>>}, State));
+ remote ->
+ transport_send(State, fin, frame(close, State));
+ {remote, Code, _} ->
+ transport_send(State, fin, frame({close, Code, <<>>}, State))
+ end,
+ ok.
+
+%% Don't compress frames while deflate is disabled.
+frame(Frame, #state{deflate=false, extensions=Extensions}) ->
+ cow_ws:frame(Frame, Extensions#{deflate => false});
+frame(Frame, #state{extensions=Extensions}) ->
+ cow_ws:frame(Frame, Extensions).
+
+-spec terminate(#state{}, any(), terminate_reason()) -> no_return().
+terminate(State=#state{shutdown_reason=Shutdown}, HandlerState, Reason) ->
+ handler_terminate(State, HandlerState, Reason),
+ case Shutdown of
+ normal -> exit(normal);
+ _ -> exit({shutdown, Shutdown})
+ end.
+
+handler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->
+ cowboy_handler:terminate(Reason, Req, HandlerState, Handler).
+
+%% System callbacks.
+
+-spec system_continue(_, _, {#state{}, any(), parse_state()}) -> no_return().
+system_continue(_, _, {State, HandlerState, ParseState}) ->
+ loop(State, HandlerState, ParseState).
+
+-spec system_terminate(any(), _, _, {#state{}, any(), parse_state()}) -> no_return().
+system_terminate(Reason, _, _, {State, HandlerState, _}) ->
+ %% @todo We should exit gracefully, if possible.
+ terminate(State, HandlerState, Reason).
+
+-spec system_code_change(Misc, _, _, _)
+ -> {ok, Misc} when Misc::{#state{}, any(), parse_state()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.