aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http_req.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_http_req.erl')
-rw-r--r--src/cowboy_http_req.erl112
1 files changed, 91 insertions, 21 deletions
diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl
index b0a0232..92d96ad 100644
--- a/src/cowboy_http_req.erl
+++ b/src/cowboy_http_req.erl
@@ -34,7 +34,8 @@
]). %% Request API.
-export([
- body/1, body/2, body_qs/1
+ body/1, body/2, body_qs/1,
+ multipart_data/1, multipart_skip/1
]). %% Request Body API.
-export([
@@ -55,12 +56,12 @@
%% Request API.
%% @doc Return the HTTP method of the request.
--spec method(#http_req{}) -> {http_method(), #http_req{}}.
+-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{}) -> {http_version(), #http_req{}}.
+-spec version(#http_req{}) -> {cowboy_http:version(), #http_req{}}.
version(Req) ->
{Req#http_req.version, Req}.
@@ -208,7 +209,7 @@ header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) ->
end.
%% @doc Return the full list of headers.
--spec headers(#http_req{}) -> {http_headers(), #http_req{}}.
+-spec headers(#http_req{}) -> {cowboy_http:headers(), #http_req{}}.
headers(Req) ->
{Req#http_req.headers, Req}.
@@ -217,7 +218,7 @@ headers(Req) ->
%% 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(http_header(), #http_req{})
+-spec parse_header(cowboy_http:header(), #http_req{})
-> {any(), #http_req{}} | {error, badarg}.
parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
case lists:keyfind(Name, 1, PHeaders) of
@@ -226,14 +227,14 @@ parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
end.
%% @doc Default values for semantic header parsing.
--spec parse_header_default(http_header()) -> any().
+-spec parse_header_default(cowboy_http:header()) -> any().
parse_header_default('Connection') -> [];
parse_header_default(_Name) -> undefined.
%% @doc Semantically parse headers.
%%
%% When the header is unknown, the value is returned directly without parsing.
--spec parse_header(http_header(), #http_req{}, any())
+-spec parse_header(cowboy_http:header(), #http_req{}, any())
-> {any(), #http_req{}} | {error, badarg}.
parse_header(Name, Req, Default) when Name =:= 'Accept' ->
parse_header(Name, Req, Default,
@@ -363,6 +364,7 @@ meta(Name, Req, Default) ->
%% @doc Return the full body sent with the request, or <em>{error, badarg}</em>
%% if no <em>Content-Length</em> is available.
%% @todo We probably want to allow a max length.
+%% @todo Add multipart support to this function.
-spec body(#http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}.
body(Req) ->
{Length, Req2} = cowboy_http_req:parse_header('Content-Length', Req),
@@ -400,6 +402,72 @@ body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) ->
{ok, Body, Req2} = body(Req),
{parse_qs(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>{data, 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()}
+ | {data, 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=#http_req{buffer=Buffer}} =
+ parse_header('Content-Length', Req2),
+ multipart_data(Req3, Length, cowboy_multipart:parser(Boundary), Buffer);
+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, Parser, Buffer) when byte_size(Buffer) >= Length ->
+ << Data:Length/binary, Rest/binary >> = Buffer,
+ multipart_data(Req#http_req{buffer=Rest}, 0, Parser(Data));
+multipart_data(Req, Length, Parser, Buffer) ->
+ NewLength = Length - byte_size(Buffer),
+ multipart_data(Req#http_req{buffer= <<>>}, NewLength, Parser(Buffer)).
+
+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) ->
+ {ok, _Data} = Transport:recv(Socket, Length, 5000),
+ {eof, Req#http_req{body_state=done}};
+multipart_data(Req=#http_req{socket=Socket, transport=Transport},
+ Length, {more, Parser}) when Length > 0 ->
+ case Transport:recv(Socket, 0, 5000) of
+ {ok, << Data:Length/binary, Buffer/binary >>} ->
+ multipart_data(Req#http_req{buffer=Buffer}, 0, Parser(Data));
+ {ok, Data} ->
+ multipart_data(Req, 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.
@@ -410,7 +478,7 @@ set_resp_cookie(Name, Value, Options, Req) ->
set_resp_header(HeaderName, HeaderValue, Req).
%% @doc Add a header to the response.
--spec set_resp_header(http_header(), iodata(), #http_req{})
+-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),
@@ -447,7 +515,7 @@ set_resp_body_fun(StreamLen, StreamFun, Req) ->
%% @doc Return whether the given header has been set for the response.
--spec has_resp_header(http_header(), #http_req{}) -> boolean().
+-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).
@@ -460,17 +528,18 @@ has_resp_body(#http_req{resp_body=RespBody}) ->
iolist_size(RespBody) > 0.
%% @equiv reply(Status, [], [], Req)
--spec reply(http_status(), #http_req{}) -> {ok, #http_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(http_status(), http_headers(), #http_req{}) -> {ok, #http_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(http_status(), http_headers(), iodata(), #http_req{})
+-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, connection=Connection, pid=ReqPid,
@@ -493,13 +562,13 @@ reply(Status, Headers, Body, Req=#http_req{socket=Socket,
resp_headers=[], resp_body= <<>>}}.
%% @equiv chunked_reply(Status, [], Req)
--spec chunked_reply(http_status(), #http_req{}) -> {ok, #http_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_http_req:chunk/2
--spec chunked_reply(http_status(), http_headers(), #http_req{})
+-spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{})
-> {ok, #http_req{}}.
chunked_reply(Status, Headers, Req=#http_req{socket=Socket,
transport=Transport, connection=Connection, pid=ReqPid,
@@ -528,7 +597,7 @@ chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
%% @doc Send an upgrade reply.
%% @private
--spec upgrade_reply(http_status(), http_headers(), #http_req{})
+-spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{})
-> {ok, #http_req{}}.
upgrade_reply(Status, Headers, Req=#http_req{socket=Socket, transport=Transport,
pid=ReqPid, resp_state=waiting, resp_headers=RespHeaders}) ->
@@ -578,7 +647,7 @@ parse_qs(Qs, URLDecode) ->
[Name, Value] -> {URLDecode(Name), URLDecode(Value)}
end || Token <- Tokens].
--spec response_connection(http_headers(), keepalive | close)
+-spec response_connection(cowboy_http:headers(), keepalive | close)
-> keepalive | close.
response_connection([], Connection) ->
Connection;
@@ -599,8 +668,8 @@ response_connection_parse(ReplyConn) ->
Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2),
cowboy_http:connection_to_atom(Tokens).
--spec response_head(http_status(), http_headers(), http_headers(),
- http_headers()) -> iolist().
+-spec response_head(cowboy_http:status(), cowboy_http:headers(),
+ cowboy_http:headers(), cowboy_http:headers()) -> iolist().
response_head(Status, Headers, RespHeaders, DefaultHeaders) ->
StatusLine = <<"HTTP/1.1 ", (status(Status))/binary, "\r\n">>,
Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers],
@@ -611,7 +680,8 @@ response_head(Status, Headers, RespHeaders, DefaultHeaders) ->
|| {Key, Value} <- Headers3],
[StatusLine, Headers4, <<"\r\n">>].
--spec merge_headers(http_headers(), http_headers()) -> http_headers().
+-spec merge_headers(cowboy_http:headers(), cowboy_http:headers())
+ -> cowboy_http:headers().
merge_headers(Headers, []) ->
Headers;
merge_headers(Headers, [{Name, Value}|Tail]) ->
@@ -628,7 +698,7 @@ atom_to_connection(keepalive) ->
atom_to_connection(close) ->
<<"close">>.
--spec status(http_status()) -> binary().
+-spec status(cowboy_http:status()) -> binary().
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
status(102) -> <<"102 Processing">>;
@@ -684,7 +754,7 @@ status(507) -> <<"507 Insufficient Storage">>;
status(510) -> <<"510 Not Extended">>;
status(B) when is_binary(B) -> B.
--spec header_to_binary(http_header()) -> binary().
+-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">>;