aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_req.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_req.erl')
-rw-r--r--src/cowboy_req.erl1038
1 files changed, 1038 insertions, 0 deletions
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
new file mode 100644
index 0000000..ad4a9a0
--- /dev/null
+++ b/src/cowboy_req.erl
@@ -0,0 +1,1038 @@
+%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011, Anthony Ramine <[email protected]>
+%%
+%% 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.
+
+%% @doc HTTP request manipulation API.
+%%
+%% Almost all functions in this module return a new <em>Req</em> variable.
+%% It should always be used instead of the one used in your function call
+%% because it keeps the state of the request. It also allows Cowboy to do
+%% some lazy evaluation and cache results where possible.
+-module(cowboy_req).
+
+%% Request API.
+-export([method/1]).
+-export([version/1]).
+-export([peer/1]).
+-export([peer_addr/1]).
+-export([host/1]).
+-export([host_info/1]).
+-export([raw_host/1]).
+-export([port/1]).
+-export([path/1]).
+-export([path_info/1]).
+-export([raw_path/1]).
+-export([qs_val/2]).
+-export([qs_val/3]).
+-export([qs_vals/1]).
+-export([raw_qs/1]).
+-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([cookie/2]).
+-export([cookie/3]).
+-export([cookies/1]).
+-export([meta/2]).
+-export([meta/3]).
+
+%% Request body API.
+-export([has_body/1]).
+-export([body_length/1]).
+-export([init_stream/4]).
+-export([stream_body/1]).
+-export([skip_body/1]).
+-export([body/1]).
+-export([body/2]).
+-export([body_qs/1]).
+-export([multipart_data/1]).
+-export([multipart_skip/1]).
+
+%% Response API.
+-export([set_resp_cookie/4]).
+-export([set_resp_header/3]).
+-export([set_resp_body/2]).
+-export([set_resp_body_fun/3]).
+-export([has_resp_header/2]).
+-export([has_resp_body/1]).
+-export([reply/2]).
+-export([reply/3]).
+-export([reply/4]).
+-export([chunked_reply/2]).
+-export([chunked_reply/3]).
+-export([chunk/2]).
+-export([upgrade_reply/3]).
+
+%% Misc API.
+-export([compact/1]).
+-export([transport/1]).
+
+-include("http.hrl").
+
+%% Request API.
+
+%% @doc Return the HTTP method of the request.
+-spec method(#http_req{}) -> {cowboy_http:method(), #http_req{}}.
+method(Req) ->
+ {Req#http_req.method, Req}.
+
+%% @doc Return the HTTP version used for the request.
+-spec version(#http_req{}) -> {cowboy_http:version(), #http_req{}}.
+version(Req) ->
+ {Req#http_req.version, Req}.
+
+%% @doc Return the peer address and port number of the remote host.
+-spec peer(#http_req{})
+ -> {{inet:ip_address(), inet:port_number()}, #http_req{}}.
+peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) ->
+ {ok, Peer} = Transport:peername(Socket),
+ {Peer, Req#http_req{peer=Peer}};
+peer(Req) ->
+ {Req#http_req.peer, Req}.
+
+%% @doc Returns the peer address calculated from headers.
+-spec peer_addr(#http_req{}) -> {inet:ip_address(), #http_req{}}.
+peer_addr(Req = #http_req{}) ->
+ {RealIp, Req1} = header(<<"X-Real-Ip">>, Req),
+ {ForwardedForRaw, Req2} = header(<<"X-Forwarded-For">>, Req1),
+ {{PeerIp, _PeerPort}, Req3} = peer(Req2),
+ ForwardedFor = case ForwardedForRaw of
+ undefined ->
+ undefined;
+ ForwardedForRaw ->
+ case re:run(ForwardedForRaw, "^(?<first_ip>[^\\,]+)",
+ [{capture, [first_ip], binary}]) of
+ {match, [FirstIp]} -> FirstIp;
+ _Any -> undefined
+ end
+ end,
+ {ok, PeerAddr} = if
+ is_binary(RealIp) -> inet_parse:address(binary_to_list(RealIp));
+ is_binary(ForwardedFor) -> inet_parse:address(binary_to_list(ForwardedFor));
+ true -> {ok, PeerIp}
+ end,
+ {PeerAddr, Req3}.
+
+%% @doc Return the tokens for the hostname requested.
+-spec host(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}.
+host(Req) ->
+ {Req#http_req.host, Req}.
+
+%% @doc Return the extra host information obtained from partially matching
+%% the hostname using <em>'...'</em>.
+-spec host_info(#http_req{})
+ -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}.
+host_info(Req) ->
+ {Req#http_req.host_info, Req}.
+
+%% @doc Return the raw host directly taken from the request.
+-spec raw_host(#http_req{}) -> {binary(), #http_req{}}.
+raw_host(Req) ->
+ {Req#http_req.raw_host, Req}.
+
+%% @doc Return the port used for this request.
+-spec port(#http_req{}) -> {inet:port_number(), #http_req{}}.
+port(Req) ->
+ {Req#http_req.port, Req}.
+
+%% @doc Return the path segments for the path requested.
+%%
+%% 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 in the path requested.
+-spec path(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}.
+path(Req) ->
+ {Req#http_req.path, Req}.
+
+%% @doc Return the extra path information obtained from partially matching
+%% the patch using <em>'...'</em>.
+-spec path_info(#http_req{})
+ -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}.
+path_info(Req) ->
+ {Req#http_req.path_info, Req}.
+
+%% @doc Return the raw path directly taken from the request.
+-spec raw_path(#http_req{}) -> {binary(), #http_req{}}.
+raw_path(Req) ->
+ {Req#http_req.raw_path, Req}.
+
+%% @equiv qs_val(Name, Req, undefined)
+-spec qs_val(binary(), #http_req{})
+ -> {binary() | true | undefined, #http_req{}}.
+qs_val(Name, Req) when is_binary(Name) ->
+ qs_val(Name, Req, undefined).
+
+%% @doc Return the query string value for the given key, or a default if
+%% missing.
+-spec qs_val(binary(), #http_req{}, Default)
+ -> {binary() | true | Default, #http_req{}} when Default::any().
+qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined,
+ urldecode={URLDecFun, URLDecArg}}, Default) when is_binary(Name) ->
+ QsVals = cowboy_http:x_www_form_urlencoded(
+ RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end),
+ qs_val(Name, Req#http_req{qs_vals=QsVals}, Default);
+qs_val(Name, Req, Default) ->
+ case lists:keyfind(Name, 1, Req#http_req.qs_vals) of
+ {Name, Value} -> {Value, Req};
+ false -> {Default, Req}
+ end.
+
+%% @doc Return the full list of query string values.
+-spec qs_vals(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}.
+qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined,
+ urldecode={URLDecFun, URLDecArg}}) ->
+ QsVals = cowboy_http:x_www_form_urlencoded(
+ RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end),
+ qs_vals(Req#http_req{qs_vals=QsVals});
+qs_vals(Req=#http_req{qs_vals=QsVals}) ->
+ {QsVals, Req}.
+
+%% @doc Return the raw query string directly taken from the request.
+-spec raw_qs(#http_req{}) -> {binary(), #http_req{}}.
+raw_qs(Req) ->
+ {Req#http_req.raw_qs, Req}.
+
+%% @equiv binding(Name, Req, undefined)
+-spec binding(atom(), #http_req{}) -> {binary() | undefined, #http_req{}}.
+binding(Name, Req) when is_atom(Name) ->
+ binding(Name, Req, undefined).
+
+%% @doc Return the binding value for the given key obtained when matching
+%% the host and path against the dispatch list, or a default if missing.
+-spec binding(atom(), #http_req{}, Default)
+ -> {binary() | Default, #http_req{}} when Default::any().
+binding(Name, Req, Default) when is_atom(Name) ->
+ case lists:keyfind(Name, 1, Req#http_req.bindings) of
+ {Name, Value} -> {Value, Req};
+ false -> {Default, Req}
+ end.
+
+%% @doc Return the full list of binding values.
+-spec bindings(#http_req{}) -> {list({atom(), binary()}), #http_req{}}.
+bindings(Req) ->
+ {Req#http_req.bindings, Req}.
+
+%% @equiv header(Name, Req, undefined)
+-spec header(atom() | binary(), #http_req{})
+ -> {binary() | undefined, #http_req{}}.
+header(Name, Req) when is_atom(Name) orelse is_binary(Name) ->
+ header(Name, Req, undefined).
+
+%% @doc Return the header value for the given key, or a default if missing.
+-spec header(atom() | binary(), #http_req{}, Default)
+ -> {binary() | Default, #http_req{}} when Default::any().
+header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) ->
+ case lists:keyfind(Name, 1, Req#http_req.headers) of
+ {Name, Value} -> {Value, Req};
+ false -> {Default, Req}
+ end.
+
+%% @doc Return the full list of headers.
+-spec headers(#http_req{}) -> {cowboy_http:headers(), #http_req{}}.
+headers(Req) ->
+ {Req#http_req.headers, Req}.
+
+%% @doc Semantically parse headers.
+%%
+%% When the value isn't found, a proper default value for the type
+%% returned is used as a return value.
+%% @see parse_header/3
+-spec parse_header(cowboy_http:header(), #http_req{})
+ -> {any(), #http_req{}} | {undefined, binary(), #http_req{}}
+ | {error, badarg}.
+parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
+ case lists:keyfind(Name, 1, PHeaders) of
+ false -> parse_header(Name, Req, parse_header_default(Name));
+ {Name, Value} -> {Value, Req}
+ end.
+
+%% @doc Default values for semantic header parsing.
+-spec parse_header_default(cowboy_http:header()) -> any().
+parse_header_default('Connection') -> [];
+parse_header_default('Transfer-Encoding') -> [<<"identity">>];
+parse_header_default(_Name) -> undefined.
+
+%% @doc Semantically parse headers.
+%%
+%% When the header is unknown, the value is returned directly without parsing.
+-spec parse_header(cowboy_http:header(), #http_req{}, any())
+ -> {any(), #http_req{}} | {undefined, binary(), #http_req{}}
+ | {error, badarg}.
+parse_header(Name, Req, Default) when Name =:= 'Accept' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:list(Value, fun cowboy_http:media_range/2)
+ end);
+parse_header(Name, Req, Default) when Name =:= 'Accept-Charset' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2)
+ end);
+parse_header(Name, Req, Default) when Name =:= 'Accept-Encoding' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:list(Value, fun cowboy_http:conneg/2)
+ end);
+parse_header(Name, Req, Default) when Name =:= 'Accept-Language' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
+ end);
+parse_header(Name, Req, Default) when Name =:= 'Connection' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
+ end);
+parse_header(Name, Req, Default) when Name =:= 'Content-Length' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:digits(Value)
+ end);
+parse_header(Name, Req, Default) when Name =:= 'Content-Type' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:content_type(Value)
+ end);
+parse_header(Name, Req, Default) when Name =:= <<"Expect">> ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2)
+ end);
+parse_header(Name, Req, Default)
+ when Name =:= 'If-Match'; Name =:= 'If-None-Match' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:entity_tag_match(Value)
+ end);
+parse_header(Name, Req, Default)
+ when Name =:= 'If-Modified-Since'; Name =:= 'If-Unmodified-Since' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:http_date(Value)
+ end);
+%% @todo Extension parameters.
+parse_header(Name, Req, Default) when Name =:= 'Transfer-Encoding' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
+ end);
+parse_header(Name, Req, Default) when Name =:= 'Upgrade' ->
+ parse_header(Name, Req, Default,
+ fun (Value) ->
+ cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
+ end);
+parse_header(Name, Req, Default) ->
+ {Value, Req2} = header(Name, Req, Default),
+ {undefined, Value, Req2}.
+
+parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) ->
+ case lists:keyfind(Name, 1, PHeaders) of
+ {Name, P} ->
+ {P, Req};
+ false ->
+ parse_header_no_cache(Name, Req, Default, Fun)
+ end.
+
+parse_header_no_cache(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) ->
+ case header(Name, Req) of
+ {undefined, Req2} ->
+ {Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}};
+ {Value, Req2} ->
+ case Fun(Value) of
+ {error, badarg} ->
+ {error, badarg};
+ P ->
+ {P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}}
+ end
+ end.
+
+%% @equiv cookie(Name, Req, undefined)
+-spec cookie(binary(), #http_req{})
+ -> {binary() | true | undefined, #http_req{}}.
+cookie(Name, Req) when is_binary(Name) ->
+ cookie(Name, Req, undefined).
+
+%% @doc Return the cookie value for the given key, or a default if
+%% missing.
+-spec cookie(binary(), #http_req{}, Default)
+ -> {binary() | true | Default, #http_req{}} when Default::any().
+cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) ->
+ case header('Cookie', Req) of
+ {undefined, Req2} ->
+ {Default, Req2#http_req{cookies=[]}};
+ {RawCookie, Req2} ->
+ Cookies = cowboy_cookies:parse_cookie(RawCookie),
+ cookie(Name, Req2#http_req{cookies=Cookies}, Default)
+ end;
+cookie(Name, Req, Default) ->
+ case lists:keyfind(Name, 1, Req#http_req.cookies) of
+ {Name, Value} -> {Value, Req};
+ false -> {Default, Req}
+ end.
+
+%% @doc Return the full list of cookie values.
+-spec cookies(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}.
+cookies(Req=#http_req{cookies=undefined}) ->
+ case header('Cookie', Req) of
+ {undefined, Req2} ->
+ {[], Req2#http_req{cookies=[]}};
+ {RawCookie, Req2} ->
+ Cookies = cowboy_cookies:parse_cookie(RawCookie),
+ cookies(Req2#http_req{cookies=Cookies})
+ end;
+cookies(Req=#http_req{cookies=Cookies}) ->
+ {Cookies, Req}.
+
+%% @equiv meta(Name, Req, undefined)
+-spec meta(atom(), #http_req{}) -> {any() | undefined, #http_req{}}.
+meta(Name, Req) ->
+ meta(Name, Req, undefined).
+
+%% @doc Return metadata information about the request.
+%%
+%% Metadata information varies from one protocol to another. Websockets
+%% would define the protocol version here, while REST would use it to
+%% indicate which media type, language and charset were retained.
+-spec meta(atom(), #http_req{}, any()) -> {any(), #http_req{}}.
+meta(Name, Req, Default) ->
+ case lists:keyfind(Name, 1, Req#http_req.meta) of
+ {Name, Value} -> {Value, Req};
+ false -> {Default, Req}
+ end.
+
+%% Request Body API.
+
+%% @doc Return whether the request message has a body.
+-spec has_body(#http_req{}) -> {boolean(), #http_req{}}.
+has_body(Req) ->
+ Has = lists:keymember('Content-Length', 1, Req#http_req.headers) orelse
+ lists:keymember('Transfer-Encoding', 1, Req#http_req.headers),
+ {Has, Req}.
+
+%% @doc Return the request message body length, if known.
+%%
+%% The length may not be known if Transfer-Encoding is not identity,
+%% and the body hasn't been read at the time of the call.
+-spec body_length(#http_req{}) -> {undefined | non_neg_integer(), #http_req{}}.
+body_length(Req) ->
+ case lists:keymember('Transfer-Encoding', 1, Req#http_req.headers) of
+ true -> {undefined, Req};
+ false -> parse_header('Content-Length', Req, 0)
+ end.
+
+%% @doc Initialize body streaming and set custom decoding functions.
+%%
+%% Calling this function is optional. It should only be used if you
+%% need to override the default behavior of Cowboy. Otherwise you
+%% should call stream_body/1 directly.
+%%
+%% Two decodings happen. First a decoding function is applied to the
+%% transferred data, and then another is applied to the actual content.
+%%
+%% Transfer encoding is generally used for chunked bodies. The decoding
+%% function uses a state to keep track of how much it has read, which is
+%% also initialized through this function.
+%%
+%% Content encoding is generally used for compression.
+%%
+%% Standard encodings can be found in cowboy_http.
+-spec init_stream(fun(), any(), fun(), #http_req{}) -> {ok, #http_req{}}.
+init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
+ {ok, Req#http_req{body_state=
+ {stream, TransferDecode, TransferState, ContentDecode}}}.
+
+%% @doc Stream the request's body.
+%%
+%% This is the most low level function to read the request body.
+%%
+%% In most cases, if they weren't defined before using stream_body/4,
+%% this function will guess which transfer and content encodings were
+%% used for building the request body, and configure the decoding
+%% functions that will be used when streaming.
+%%
+%% It then starts streaming the body, returning {ok, Data, Req}
+%% for each streamed part, and {done, Req} when it's finished streaming.
+-spec stream_body(#http_req{}) -> {ok, binary(), #http_req{}}
+ | {done, #http_req{}} | {error, atom()}.
+stream_body(Req=#http_req{body_state=waiting,
+ version=Version, transport=Transport, socket=Socket}) ->
+ case parse_header(<<"Expect">>, Req) of
+ {[<<"100-continue">>], Req1} ->
+ HTTPVer = cowboy_http:version_to_binary(Version),
+ Transport:send(Socket,
+ << HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>);
+ {undefined, Req1} ->
+ ok;
+ {undefined, _, Req1} ->
+ ok
+ end,
+ case parse_header('Transfer-Encoding', Req1) of
+ {[<<"chunked">>], Req2} ->
+ stream_body(Req2#http_req{body_state=
+ {stream, fun cowboy_http:te_chunked/2, {0, 0},
+ fun cowboy_http:ce_identity/1}});
+ {[<<"identity">>], Req2} ->
+ {Length, Req3} = body_length(Req2),
+ case Length of
+ 0 ->
+ {done, Req3#http_req{body_state=done}};
+ Length ->
+ stream_body(Req3#http_req{body_state=
+ {stream, fun cowboy_http:te_identity/2, {0, Length},
+ fun cowboy_http:ce_identity/1}})
+ end
+ end;
+stream_body(Req=#http_req{buffer=Buffer, body_state={stream, _, _, _}})
+ when Buffer =/= <<>> ->
+ transfer_decode(Buffer, Req#http_req{buffer= <<>>});
+stream_body(Req=#http_req{body_state={stream, _, _, _}}) ->
+ stream_body_recv(Req);
+stream_body(Req=#http_req{body_state=done}) ->
+ {done, Req};
+stream_body(Req=#http_req{body_state={multipart, _N, _Fun},
+ transport=Transport, socket=Socket}) ->
+ case Transport:recv(Socket, 0, 5000) of
+ {ok, Data} -> {ok, Data, Req};
+ {error, Reason} -> {error, Reason}
+ end.
+
+-spec stream_body_recv(#http_req{})
+ -> {ok, binary(), #http_req{}} | {error, atom()}.
+stream_body_recv(Req=#http_req{
+ transport=Transport, socket=Socket, buffer=Buffer}) ->
+ %% @todo Allow configuring the timeout.
+ case Transport:recv(Socket, 0, 5000) of
+ {ok, Data} -> transfer_decode(<< Buffer/binary, Data/binary >>, Req);
+ {error, Reason} -> {error, Reason}
+ end.
+
+-spec transfer_decode(binary(), #http_req{})
+ -> {ok, binary(), #http_req{}} | {error, atom()}.
+transfer_decode(Data, Req=#http_req{
+ body_state={stream, TransferDecode, TransferState, ContentDecode}}) ->
+ case TransferDecode(Data, TransferState) of
+ {ok, Data2, TransferState2} ->
+ content_decode(ContentDecode, Data2, Req#http_req{body_state=
+ {stream, TransferDecode, TransferState2, ContentDecode}});
+ {ok, Data2, Rest, TransferState2} ->
+ content_decode(ContentDecode, Data2, Req#http_req{
+ buffer=Rest, body_state=
+ {stream, TransferDecode, TransferState2, ContentDecode}});
+ %% @todo {header(s) for chunked
+ more ->
+ stream_body_recv(Req#http_req{buffer=Data});
+ {done, Length, Rest} ->
+ Req2 = transfer_decode_done(Length, Rest, Req),
+ {done, Req2};
+ {done, Data2, Length, Rest} ->
+ Req2 = transfer_decode_done(Length, Rest, Req),
+ content_decode(ContentDecode, Data2, Req2);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+-spec transfer_decode_done(non_neg_integer(), binary(), #http_req{})
+ -> #http_req{}.
+transfer_decode_done(Length, Rest, Req=#http_req{
+ headers=Headers, p_headers=PHeaders}) ->
+ Headers2 = lists:keystore('Content-Length', 1, Headers,
+ {'Content-Length', list_to_binary(integer_to_list(Length))}),
+ %% At this point we just assume TEs were all decoded.
+ Headers3 = lists:keydelete('Transfer-Encoding', 1, Headers2),
+ PHeaders2 = lists:keystore('Content-Length', 1, PHeaders,
+ {'Content-Length', Length}),
+ PHeaders3 = lists:keydelete('Transfer-Encoding', 1, PHeaders2),
+ Req#http_req{buffer=Rest, body_state=done,
+ headers=Headers3, p_headers=PHeaders3}.
+
+%% @todo Probably needs a Rest.
+-spec content_decode(fun(), binary(), #http_req{})
+ -> {ok, binary(), #http_req{}} | {error, atom()}.
+content_decode(ContentDecode, Data, Req) ->
+ case ContentDecode(Data) of
+ {ok, Data2} -> {ok, Data2, Req};
+ {error, Reason} -> {error, Reason}
+ end.
+
+%% @doc Return the full body sent with the request.
+-spec body(#http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}.
+body(Req) ->
+ read_body(infinity, Req, <<>>).
+
+%% @doc Return the full body sent with the request as long as the body
+%% length doesn't go over MaxLength.
+%%
+%% This is most useful to quickly be able to get the full body while
+%% avoiding filling your memory with huge request bodies when you're
+%% not expecting it.
+-spec body(non_neg_integer() | infinity, #http_req{})
+ -> {ok, binary(), #http_req{}} | {error, atom()}.
+body(MaxLength, Req) ->
+ read_body(MaxLength, Req, <<>>).
+
+-spec read_body(non_neg_integer() | infinity, #http_req{}, binary())
+ -> {ok, binary(), #http_req{}} | {error, atom()}.
+read_body(MaxLength, Req, Acc) when MaxLength > byte_size(Acc) ->
+ case stream_body(Req) of
+ {ok, Data, Req2} ->
+ read_body(MaxLength, Req2, << Acc/binary, Data/binary >>);
+ {done, Req2} ->
+ {ok, Acc, Req2};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+-spec skip_body(#http_req{}) -> {ok, #http_req{}} | {error, atom()}.
+skip_body(Req) ->
+ case stream_body(Req) of
+ {ok, _, Req2} -> skip_body(Req2);
+ {done, Req2} -> {ok, Req2};
+ {error, Reason} -> {error, Reason}
+ end.
+
+%% @doc Return the full body sent with the reqest, parsed as an
+%% application/x-www-form-urlencoded string. Essentially a POST query string.
+%% @todo We need an option to limit the size of the body for QS too.
+-spec body_qs(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}.
+body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) ->
+ {ok, Body, Req2} = body(Req),
+ {cowboy_http:x_www_form_urlencoded(
+ Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2}.
+
+%% Multipart Request API.
+
+%% @doc Return data from the multipart parser.
+%%
+%% Use this function for multipart streaming. For each part in the request,
+%% this function returns <em>{headers, Headers}</em> followed by a sequence of
+%% <em>{body, Data}</em> tuples and finally <em>end_of_part</em>. When there
+%% is no part to parse anymore, <em>eof</em> is returned.
+%%
+%% If the request Content-Type is not a multipart one, <em>{error, badarg}</em>
+%% is returned.
+-spec multipart_data(#http_req{})
+ -> {{headers, cowboy_http:headers()}
+ | {body, binary()} | end_of_part | eof,
+ #http_req{}}.
+multipart_data(Req=#http_req{body_state=waiting}) ->
+ {{<<"multipart">>, _SubType, Params}, Req2} =
+ parse_header('Content-Type', Req),
+ {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params),
+ {Length, Req3} = parse_header('Content-Length', Req2),
+ multipart_data(Req3, Length, {more, cowboy_multipart:parser(Boundary)});
+multipart_data(Req=#http_req{body_state={multipart, Length, Cont}}) ->
+ multipart_data(Req, Length, Cont());
+multipart_data(Req=#http_req{body_state=done}) ->
+ {eof, Req}.
+
+multipart_data(Req, Length, {headers, Headers, Cont}) ->
+ {{headers, Headers}, Req#http_req{body_state={multipart, Length, Cont}}};
+multipart_data(Req, Length, {body, Data, Cont}) ->
+ {{body, Data}, Req#http_req{body_state={multipart, Length, Cont}}};
+multipart_data(Req, Length, {end_of_part, Cont}) ->
+ {end_of_part, Req#http_req{body_state={multipart, Length, Cont}}};
+multipart_data(Req, 0, eof) ->
+ {eof, Req#http_req{body_state=done}};
+multipart_data(Req=#http_req{socket=Socket, transport=Transport},
+ Length, eof) ->
+ %% We just want to skip so no need to stream data here.
+ {ok, _Data} = Transport:recv(Socket, Length, 5000),
+ {eof, Req#http_req{body_state=done}};
+multipart_data(Req, Length, {more, Parser}) when Length > 0 ->
+ case stream_body(Req) of
+ {ok, << Data:Length/binary, Buffer/binary >>, Req2} ->
+ multipart_data(Req2#http_req{buffer=Buffer}, 0, Parser(Data));
+ {ok, Data, Req2} ->
+ multipart_data(Req2, Length - byte_size(Data), Parser(Data))
+ end.
+
+%% @doc Skip a part returned by the multipart parser.
+%%
+%% This function repeatedly calls <em>multipart_data/1</em> until
+%% <em>end_of_part</em> or <em>eof</em> is parsed.
+multipart_skip(Req) ->
+ case multipart_data(Req) of
+ {end_of_part, Req2} -> {ok, Req2};
+ {eof, Req2} -> {ok, Req2};
+ {_Other, Req2} -> multipart_skip(Req2)
+ end.
+
+%% Response API.
+
+%% @doc Add a cookie header to the response.
+-spec set_resp_cookie(binary(), binary(), [cowboy_cookies:cookie_option()],
+ #http_req{}) -> {ok, #http_req{}}.
+set_resp_cookie(Name, Value, Options, Req) ->
+ {HeaderName, HeaderValue} = cowboy_cookies:cookie(Name, Value, Options),
+ set_resp_header(HeaderName, HeaderValue, Req).
+
+%% @doc Add a header to the response.
+-spec set_resp_header(cowboy_http:header(), iodata(), #http_req{})
+ -> {ok, #http_req{}}.
+set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) ->
+ NameBin = header_to_binary(Name),
+ {ok, Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}}.
+
+%% @doc Add a body to the response.
+%%
+%% The body set here is ignored if the response is later sent using
+%% anything other than reply/2 or reply/3. The response body is expected
+%% to be a binary or an iolist.
+-spec set_resp_body(iodata(), #http_req{}) -> {ok, #http_req{}}.
+set_resp_body(Body, Req) ->
+ {ok, Req#http_req{resp_body=Body}}.
+
+
+%% @doc Add a body function to the response.
+%%
+%% The response body may also be set to a content-length - stream-function pair.
+%% If the response body is of this type normal response headers will be sent.
+%% After the response headers has been sent the body function is applied.
+%% The body function is expected to write the response body directly to the
+%% socket using the transport module.
+%%
+%% If the body function crashes while writing the response body or writes fewer
+%% bytes than declared the behaviour is undefined. The body set here is ignored
+%% if the response is later sent using anything other than `reply/2' or
+%% `reply/3'.
+%%
+%% @see cowboy_req:transport/1.
+-spec set_resp_body_fun(non_neg_integer(), fun(() -> {sent, non_neg_integer()}),
+ #http_req{}) -> {ok, #http_req{}}.
+set_resp_body_fun(StreamLen, StreamFun, Req) ->
+ {ok, Req#http_req{resp_body={StreamLen, StreamFun}}}.
+
+%% @doc Return whether the given header has been set for the response.
+-spec has_resp_header(cowboy_http:header(), #http_req{}) -> boolean().
+has_resp_header(Name, #http_req{resp_headers=RespHeaders}) ->
+ NameBin = header_to_binary(Name),
+ lists:keymember(NameBin, 1, RespHeaders).
+
+%% @doc Return whether a body has been set for the response.
+-spec has_resp_body(#http_req{}) -> boolean().
+has_resp_body(#http_req{resp_body={Length, _}}) ->
+ Length > 0;
+has_resp_body(#http_req{resp_body=RespBody}) ->
+ iolist_size(RespBody) > 0.
+
+%% @equiv reply(Status, [], [], Req)
+-spec reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}.
+reply(Status, Req=#http_req{resp_body=Body}) ->
+ reply(Status, [], Body, Req).
+
+%% @equiv reply(Status, Headers, [], Req)
+-spec reply(cowboy_http:status(), cowboy_http:headers(), #http_req{})
+ -> {ok, #http_req{}}.
+reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
+ reply(Status, Headers, Body, Req).
+
+%% @doc Send a reply to the client.
+-spec reply(cowboy_http:status(), cowboy_http:headers(), iodata(), #http_req{})
+ -> {ok, #http_req{}}.
+reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport,
+ version=Version, connection=Connection,
+ method=Method, resp_state=waiting, resp_headers=RespHeaders}) ->
+ RespConn = response_connection(Headers, Connection),
+ ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end,
+ HTTP11Headers = case Version of
+ {1, 1} -> [{<<"Connection">>, atom_to_connection(Connection)}];
+ _ -> []
+ end,
+ {ReplyType, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"Content-Length">>, integer_to_list(ContentLen)},
+ {<<"Date">>, cowboy_clock:rfc1123()},
+ {<<"Server">>, <<"Cowboy">>}
+ |HTTP11Headers], Req),
+ if Method =:= 'HEAD' -> ok;
+ ReplyType =:= hook -> ok; %% Hook replied for us, stop there.
+ true ->
+ case Body of
+ {_, StreamFun} -> StreamFun();
+ _ -> Transport:send(Socket, Body)
+ end
+ end,
+ {ok, Req2#http_req{connection=RespConn, resp_state=done,
+ resp_headers=[], resp_body= <<>>}}.
+
+%% @equiv chunked_reply(Status, [], Req)
+-spec chunked_reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}.
+chunked_reply(Status, Req) ->
+ chunked_reply(Status, [], Req).
+
+%% @doc Initiate the sending of a chunked reply to the client.
+%% @see cowboy_req:chunk/2
+-spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{})
+ -> {ok, #http_req{}}.
+chunked_reply(Status, Headers, Req=#http_req{
+ version=Version, connection=Connection,
+ resp_state=waiting, resp_headers=RespHeaders}) ->
+ RespConn = response_connection(Headers, Connection),
+ HTTP11Headers = case Version of
+ {1, 1} -> [
+ {<<"Connection">>, atom_to_connection(Connection)},
+ {<<"Transfer-Encoding">>, <<"chunked">>}];
+ _ -> []
+ end,
+ {_, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"Date">>, cowboy_clock:rfc1123()},
+ {<<"Server">>, <<"Cowboy">>}
+ |HTTP11Headers], Req),
+ {ok, Req2#http_req{connection=RespConn, resp_state=chunks,
+ resp_headers=[], resp_body= <<>>}}.
+
+%% @doc Send a chunk of data.
+%%
+%% A chunked reply must have been initiated before calling this function.
+-spec chunk(iodata(), #http_req{}) -> ok | {error, atom()}.
+chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) ->
+ ok;
+chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) ->
+ Transport:send(Socket, Data);
+chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
+ Transport:send(Socket, [integer_to_list(iolist_size(Data), 16),
+ <<"\r\n">>, Data, <<"\r\n">>]).
+
+%% @doc Send an upgrade reply.
+%% @private
+-spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{})
+ -> {ok, #http_req{}}.
+upgrade_reply(Status, Headers, Req=#http_req{
+ resp_state=waiting, resp_headers=RespHeaders}) ->
+ {_, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"Connection">>, <<"Upgrade">>}
+ ], Req),
+ {ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
+
+%% Misc API.
+
+%% @doc Compact the request data by removing all non-system information.
+%%
+%% This essentially removes the host, path, query string, bindings and headers.
+%% Use it when you really need to save up memory, for example when having
+%% many concurrent long-running connections.
+-spec compact(#http_req{}) -> #http_req{}.
+compact(Req) ->
+ Req#http_req{host=undefined, host_info=undefined, path=undefined,
+ path_info=undefined, qs_vals=undefined,
+ bindings=undefined, headers=[],
+ p_headers=[], cookies=[]}.
+
+%% @doc Return the transport module and socket associated with a request.
+%%
+%% This exposes the same socket interface used internally by the HTTP protocol
+%% implementation to developers that needs low level access to the socket.
+%%
+%% It is preferred to use this in conjuction with the stream function support
+%% in `set_resp_body_fun/3' if this is used to write a response body directly
+%% to the socket. This ensures that the response headers are set correctly.
+-spec transport(#http_req{}) -> {ok, module(), inet:socket()}.
+transport(#http_req{transport=Transport, socket=Socket}) ->
+ {ok, Transport, Socket}.
+
+%% Internal.
+
+-spec response(cowboy_http:status(), cowboy_http:headers(),
+ cowboy_http:headers(), cowboy_http:headers(), #http_req{})
+ -> {normal | hook, #http_req{}}.
+response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{
+ socket=Socket, transport=Transport, version=Version,
+ pid=ReqPid, onresponse=OnResponse}) ->
+ FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders),
+ Req2 = case OnResponse of
+ undefined -> Req;
+ OnResponse -> OnResponse(Status, FullHeaders,
+ %% Don't call 'onresponse' from the hook itself.
+ Req#http_req{resp_headers=[], resp_body= <<>>,
+ onresponse=undefined})
+ end,
+ ReplyType = case Req2#http_req.resp_state of
+ waiting ->
+ HTTPVer = cowboy_http:version_to_binary(Version),
+ StatusLine = << HTTPVer/binary, " ",
+ (status(Status))/binary, "\r\n" >>,
+ HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
+ || {Key, Value} <- FullHeaders],
+ Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]),
+ ReqPid ! {?MODULE, resp_sent},
+ normal;
+ _ ->
+ hook
+ end,
+ {ReplyType, Req2}.
+
+-spec response_connection(cowboy_http:headers(), keepalive | close)
+ -> keepalive | close.
+response_connection([], Connection) ->
+ Connection;
+response_connection([{Name, Value}|Tail], Connection) ->
+ case Name of
+ 'Connection' -> response_connection_parse(Value);
+ Name when is_atom(Name) -> response_connection(Tail, Connection);
+ Name ->
+ Name2 = cowboy_bstr:to_lower(Name),
+ case Name2 of
+ <<"connection">> -> response_connection_parse(Value);
+ _Any -> response_connection(Tail, Connection)
+ end
+ end.
+
+-spec response_connection_parse(binary()) -> keepalive | close.
+response_connection_parse(ReplyConn) ->
+ Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2),
+ cowboy_http:connection_to_atom(Tokens).
+
+-spec response_merge_headers(cowboy_http:headers(), cowboy_http:headers(),
+ cowboy_http:headers()) -> cowboy_http:headers().
+response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
+ Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers],
+ merge_headers(
+ merge_headers(Headers2, RespHeaders),
+ DefaultHeaders).
+
+-spec merge_headers(cowboy_http:headers(), cowboy_http:headers())
+ -> cowboy_http:headers().
+merge_headers(Headers, []) ->
+ Headers;
+merge_headers(Headers, [{Name, Value}|Tail]) ->
+ Headers2 = case lists:keymember(Name, 1, Headers) of
+ true -> Headers;
+ false -> Headers ++ [{Name, Value}]
+ end,
+ merge_headers(Headers2, Tail).
+
+-spec atom_to_connection(keepalive) -> <<_:80>>;
+ (close) -> <<_:40>>.
+atom_to_connection(keepalive) ->
+ <<"keep-alive">>;
+atom_to_connection(close) ->
+ <<"close">>.
+
+-spec status(cowboy_http:status()) -> binary().
+status(100) -> <<"100 Continue">>;
+status(101) -> <<"101 Switching Protocols">>;
+status(102) -> <<"102 Processing">>;
+status(200) -> <<"200 OK">>;
+status(201) -> <<"201 Created">>;
+status(202) -> <<"202 Accepted">>;
+status(203) -> <<"203 Non-Authoritative Information">>;
+status(204) -> <<"204 No Content">>;
+status(205) -> <<"205 Reset Content">>;
+status(206) -> <<"206 Partial Content">>;
+status(207) -> <<"207 Multi-Status">>;
+status(226) -> <<"226 IM Used">>;
+status(300) -> <<"300 Multiple Choices">>;
+status(301) -> <<"301 Moved Permanently">>;
+status(302) -> <<"302 Found">>;
+status(303) -> <<"303 See Other">>;
+status(304) -> <<"304 Not Modified">>;
+status(305) -> <<"305 Use Proxy">>;
+status(306) -> <<"306 Switch Proxy">>;
+status(307) -> <<"307 Temporary Redirect">>;
+status(400) -> <<"400 Bad Request">>;
+status(401) -> <<"401 Unauthorized">>;
+status(402) -> <<"402 Payment Required">>;
+status(403) -> <<"403 Forbidden">>;
+status(404) -> <<"404 Not Found">>;
+status(405) -> <<"405 Method Not Allowed">>;
+status(406) -> <<"406 Not Acceptable">>;
+status(407) -> <<"407 Proxy Authentication Required">>;
+status(408) -> <<"408 Request Timeout">>;
+status(409) -> <<"409 Conflict">>;
+status(410) -> <<"410 Gone">>;
+status(411) -> <<"411 Length Required">>;
+status(412) -> <<"412 Precondition Failed">>;
+status(413) -> <<"413 Request Entity Too Large">>;
+status(414) -> <<"414 Request-URI Too Long">>;
+status(415) -> <<"415 Unsupported Media Type">>;
+status(416) -> <<"416 Requested Range Not Satisfiable">>;
+status(417) -> <<"417 Expectation Failed">>;
+status(418) -> <<"418 I'm a teapot">>;
+status(422) -> <<"422 Unprocessable Entity">>;
+status(423) -> <<"423 Locked">>;
+status(424) -> <<"424 Failed Dependency">>;
+status(425) -> <<"425 Unordered Collection">>;
+status(426) -> <<"426 Upgrade Required">>;
+status(428) -> <<"428 Precondition Required">>;
+status(429) -> <<"429 Too Many Requests">>;
+status(431) -> <<"431 Request Header Fields Too Large">>;
+status(500) -> <<"500 Internal Server Error">>;
+status(501) -> <<"501 Not Implemented">>;
+status(502) -> <<"502 Bad Gateway">>;
+status(503) -> <<"503 Service Unavailable">>;
+status(504) -> <<"504 Gateway Timeout">>;
+status(505) -> <<"505 HTTP Version Not Supported">>;
+status(506) -> <<"506 Variant Also Negotiates">>;
+status(507) -> <<"507 Insufficient Storage">>;
+status(510) -> <<"510 Not Extended">>;
+status(511) -> <<"511 Network Authentication Required">>;
+status(B) when is_binary(B) -> B.
+
+-spec header_to_binary(cowboy_http:header()) -> binary().
+header_to_binary('Cache-Control') -> <<"Cache-Control">>;
+header_to_binary('Connection') -> <<"Connection">>;
+header_to_binary('Date') -> <<"Date">>;
+header_to_binary('Pragma') -> <<"Pragma">>;
+header_to_binary('Transfer-Encoding') -> <<"Transfer-Encoding">>;
+header_to_binary('Upgrade') -> <<"Upgrade">>;
+header_to_binary('Via') -> <<"Via">>;
+header_to_binary('Accept') -> <<"Accept">>;
+header_to_binary('Accept-Charset') -> <<"Accept-Charset">>;
+header_to_binary('Accept-Encoding') -> <<"Accept-Encoding">>;
+header_to_binary('Accept-Language') -> <<"Accept-Language">>;
+header_to_binary('Authorization') -> <<"Authorization">>;
+header_to_binary('From') -> <<"From">>;
+header_to_binary('Host') -> <<"Host">>;
+header_to_binary('If-Modified-Since') -> <<"If-Modified-Since">>;
+header_to_binary('If-Match') -> <<"If-Match">>;
+header_to_binary('If-None-Match') -> <<"If-None-Match">>;
+header_to_binary('If-Range') -> <<"If-Range">>;
+header_to_binary('If-Unmodified-Since') -> <<"If-Unmodified-Since">>;
+header_to_binary('Max-Forwards') -> <<"Max-Forwards">>;
+header_to_binary('Proxy-Authorization') -> <<"Proxy-Authorization">>;
+header_to_binary('Range') -> <<"Range">>;
+header_to_binary('Referer') -> <<"Referer">>;
+header_to_binary('User-Agent') -> <<"User-Agent">>;
+header_to_binary('Age') -> <<"Age">>;
+header_to_binary('Location') -> <<"Location">>;
+header_to_binary('Proxy-Authenticate') -> <<"Proxy-Authenticate">>;
+header_to_binary('Public') -> <<"Public">>;
+header_to_binary('Retry-After') -> <<"Retry-After">>;
+header_to_binary('Server') -> <<"Server">>;
+header_to_binary('Vary') -> <<"Vary">>;
+header_to_binary('Warning') -> <<"Warning">>;
+header_to_binary('Www-Authenticate') -> <<"Www-Authenticate">>;
+header_to_binary('Allow') -> <<"Allow">>;
+header_to_binary('Content-Base') -> <<"Content-Base">>;
+header_to_binary('Content-Encoding') -> <<"Content-Encoding">>;
+header_to_binary('Content-Language') -> <<"Content-Language">>;
+header_to_binary('Content-Length') -> <<"Content-Length">>;
+header_to_binary('Content-Location') -> <<"Content-Location">>;
+header_to_binary('Content-Md5') -> <<"Content-Md5">>;
+header_to_binary('Content-Range') -> <<"Content-Range">>;
+header_to_binary('Content-Type') -> <<"Content-Type">>;
+header_to_binary('Etag') -> <<"Etag">>;
+header_to_binary('Expires') -> <<"Expires">>;
+header_to_binary('Last-Modified') -> <<"Last-Modified">>;
+header_to_binary('Accept-Ranges') -> <<"Accept-Ranges">>;
+header_to_binary('Set-Cookie') -> <<"Set-Cookie">>;
+header_to_binary('Set-Cookie2') -> <<"Set-Cookie2">>;
+header_to_binary('X-Forwarded-For') -> <<"X-Forwarded-For">>;
+header_to_binary('Cookie') -> <<"Cookie">>;
+header_to_binary('Keep-Alive') -> <<"Keep-Alive">>;
+header_to_binary('Proxy-Connection') -> <<"Proxy-Connection">>;
+header_to_binary(B) when is_binary(B) -> B.