diff options
Diffstat (limited to 'src/cowboy_http_req.erl')
-rw-r--r-- | src/cowboy_http_req.erl | 112 |
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">>; |