diff options
Diffstat (limited to 'src/cowboy_http_req.erl')
-rw-r--r-- | src/cowboy_http_req.erl | 105 |
1 files changed, 86 insertions, 19 deletions
diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl index 5b63599..a1a37bd 100644 --- a/src/cowboy_http_req.erl +++ b/src/cowboy_http_req.erl @@ -28,6 +28,7 @@ qs_val/2, qs_val/3, qs_vals/1, raw_qs/1, binding/2, binding/3, bindings/1, header/2, header/3, headers/1, + parse_header/2, parse_header/3, cookie/2, cookie/3, cookies/1 ]). %% Request API. @@ -67,14 +68,14 @@ peer(Req) -> {Req#http_req.peer, Req}. %% @doc Return the tokens for the hostname requested. --spec host(#http_req{}) -> {cowboy_dispatcher:path_tokens(), #http_req{}}. +-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:path_tokens() | undefined, #http_req{}}. + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. host_info(Req) -> {Req#http_req.host_info, Req}. @@ -88,15 +89,19 @@ raw_host(Req) -> port(Req) -> {Req#http_req.port, Req}. -%% @doc Return the tokens for the path requested. --spec path(#http_req{}) -> {cowboy_dispatcher:path_tokens(), #http_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:path_tokens() | undefined, #http_req{}}. + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. path_info(Req) -> {Req#http_req.path_info, Req}. @@ -178,6 +183,54 @@ header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) -> 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(http_header(), #http_req{}) + -> {tokens, [binary()], #http_req{}} + | {undefined, binary(), #http_req{}} + | {error, badarg}. +parse_header('Connection', Req) -> + parse_header('Connection', Req, []); +parse_header(Name, Req) -> + parse_header(Name, Req, undefined). + +%% @doc Semantically parse headers. +%% +%% When the header is known, a named tuple is returned containing +%% {Type, P, Req} with Type being the type of value found in P. +%% For example, the header 'Connection' is a list of tokens, therefore +%% the value returned will be a list of binary values and Type will be +%% 'tokens'. +%% +%% When the header is known but not found, the tuple {Type, Default, Req} +%% is returned instead. +%% +%% When the header is unknown, the value is returned directly as an +%% 'undefined' tagged tuple. +-spec parse_header(http_header(), #http_req{}, any()) + -> {tokens, [binary()], #http_req{}} + | {undefined, binary(), #http_req{}} + | {error, badarg}. +parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default) + when Name =:= 'Connection' -> + case header(Name, Req) of + {undefined, Req2} -> {tokens, Default, Req2}; + {Value, Req2} -> + case cowboy_http:parse_tokens_list(Value) of + {error, badarg} -> + {error, badarg}; + P -> + {tokens, P, Req2#http_req{ + p_headers=[{Name, P}|PHeaders]}} + end + end; +parse_header(Name, Req, Default) -> + {Value, Req2} = header(Name, Req, Default), + {undefined, Value, Req2}. + %% @equiv cookie(Name, Req, undefined) -spec cookie(binary(), #http_req{}) -> {binary() | true | undefined, #http_req{}}. @@ -265,6 +318,7 @@ body_qs(Req) -> reply(Code, Headers, Body, Req=#http_req{socket=Socket, transport=Transport, connection=Connection, method=Method, resp_state=waiting}) -> + RespConn = response_connection(Headers, Connection), Head = response_head(Code, Headers, [ {<<"Connection">>, atom_to_connection(Connection)}, {<<"Content-Length">>, @@ -276,30 +330,23 @@ reply(Code, Headers, Body, Req=#http_req{socket=Socket, 'HEAD' -> Transport:send(Socket, Head); _ -> Transport:send(Socket, [Head, Body]) end, - {ok, Req#http_req{resp_state=done}}. + {ok, Req#http_req{connection=RespConn, resp_state=done}}. %% @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{}) -> {ok, #http_req{}}. chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport, - method='HEAD', resp_state=waiting}) -> + connection=Connection, resp_state=waiting}) -> + RespConn = response_connection(Headers, Connection), Head = response_head(Code, Headers, [ - {<<"Date">>, cowboy_clock:rfc1123()}, - {<<"Server">>, <<"Cowboy">>} - ]), - Transport:send(Socket, Head), - {ok, Req#http_req{resp_state=done}}; -chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport, - resp_state=waiting}) -> - Head = response_head(Code, Headers, [ - {<<"Connection">>, <<"close">>}, + {<<"Connection">>, atom_to_connection(Connection)}, {<<"Transfer-Encoding">>, <<"chunked">>}, {<<"Date">>, cowboy_clock:rfc1123()}, {<<"Server">>, <<"Cowboy">>} ]), Transport:send(Socket, Head), - {ok, Req#http_req{resp_state=chunks}}. + {ok, Req#http_req{connection=RespConn, resp_state=chunks}}. %% @doc Send a chunk of data. %% @@ -321,7 +368,7 @@ chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> -spec compact(#http_req{}) -> #http_req{}. compact(Req) -> Req#http_req{host=undefined, host_info=undefined, path=undefined, - path_info=undefined, qs_vals=undefined, raw_qs=undefined, + path_info=undefined, qs_vals=undefined, bindings=undefined, headers=[]}. %% Internal. @@ -336,13 +383,33 @@ parse_qs(Qs) -> [Name, Value] -> {quoted:from_url(Name), quoted:from_url(Value)} end || Token <- Tokens]. +-spec response_connection(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 -> + 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:parse_tokens_list(ReplyConn), + cowboy_http:connection_to_atom(Tokens). + -spec response_head(http_status(), http_headers(), http_headers()) -> iolist(). response_head(Code, Headers, DefaultHeaders) -> StatusLine = <<"HTTP/1.1 ", (status(Code))/binary, "\r\n">>, Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers], Headers3 = lists:keysort(1, Headers2), Headers4 = lists:ukeymerge(1, Headers3, DefaultHeaders), - Headers5 = [<< Key/binary, ": ", Value/binary, "\r\n" >> + Headers5 = [[Key, <<": ">>, Value, <<"\r\n">>] || {Key, Value} <- Headers4], [StatusLine, Headers5, <<"\r\n">>]. |