From 34021666cbf626e979ea136664b1f8beca893d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Fri, 21 Sep 2012 09:18:56 +0200 Subject: Don't use decode_packet/3 for parsing the headers Header names are now binaries. Since header names are case insensitive they are all converted to lowercase. For example: <<"content-length">>. The max_line_length option was removed. Three new options have been added instead: * max_request_line_length (defaults to 4096) * max_header_name_length (defaults to 64) * max_header_value_length (defaults to 4096) --- src/cowboy_clock.erl | 4 +- src/cowboy_http.erl | 16 +--- src/cowboy_protocol.erl | 136 ++++++++++++-------------- src/cowboy_req.erl | 184 ++++++++++++------------------------ src/cowboy_rest.erl | 18 ++-- src/cowboy_websocket.erl | 15 +-- test/http_SUITE.erl | 8 +- test/http_handler_init_shutdown.erl | 2 +- test/http_handler_set_resp.erl | 4 +- 9 files changed, 146 insertions(+), 241 deletions(-) diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl index 747bffe..5e2bf44 100644 --- a/src/cowboy_clock.erl +++ b/src/cowboy_clock.erl @@ -62,14 +62,14 @@ stop() -> %% @doc Return the current date and time formatted according to RFC-1123. %% -%% This format is used in the 'Date' header sent with HTTP responses. +%% This format is used in the date header sent with HTTP responses. -spec rfc1123() -> binary(). rfc1123() -> ets:lookup_element(?TABLE, rfc1123, 2). %% @doc Return the current date and time formatted according to RFC-2109. %% -%% This format is used in the 'Set-Cookie' header sent with +%% This format is used in the set-cookie header sent with %% HTTP responses. -spec rfc2109(calendar:datetime()) -> binary(). rfc2109(LocalTime) -> diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 83a67fe..9c38fae 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -56,25 +56,11 @@ | {scheme, Scheme::binary(), binary()} | {abs_path, binary()} | binary(). -type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}. --type header() :: 'Cache-Control' | 'Connection' | 'Date' | 'Pragma' - | 'Transfer-Encoding' | 'Upgrade' | 'Via' | 'Accept' | 'Accept-Charset' - | 'Accept-Encoding' | 'Accept-Language' | 'Authorization' | 'From' | 'Host' - | 'If-Modified-Since' | 'If-Match' | 'If-None-Match' | 'If-Range' - | 'If-Unmodified-Since' | 'Max-Forwards' | 'Proxy-Authorization' | 'Range' - | 'Referer' | 'User-Agent' | 'Age' | 'Location' | 'Proxy-Authenticate' - | 'Public' | 'Retry-After' | 'Server' | 'Vary' | 'Warning' - | 'Www-Authenticate' | 'Allow' | 'Content-Base' | 'Content-Encoding' - | 'Content-Language' | 'Content-Length' | 'Content-Location' - | 'Content-Md5' | 'Content-Range' | 'Content-Type' | 'Etag' - | 'Expires' | 'Last-Modified' | 'Accept-Ranges' | 'Set-Cookie' - | 'Set-Cookie2' | 'X-Forwarded-For' | 'Cookie' | 'Keep-Alive' - | 'Proxy-Connection' | binary(). --type headers() :: [{header(), iodata()}]. +-type headers() :: [{binary(), iodata()}]. -type status() :: non_neg_integer() | binary(). -export_type([uri/0]). -export_type([version/0]). --export_type([header/0]). -export_type([headers/0]). -export_type([status/0]). diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index 4caa00b..a64a983 100644 --- a/src/cowboy_protocol.erl +++ b/src/cowboy_protocol.erl @@ -42,10 +42,6 @@ -export([parse_request/1]). -export([handler_loop/3]). --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). --endif. - -type onrequest_fun() :: fun((Req) -> Req). -type onresponse_fun() :: fun((cowboy_http:status(), cowboy_http:headers(), Req) -> Req). @@ -66,7 +62,9 @@ max_empty_lines :: integer(), req_keepalive = 1 :: integer(), max_keepalive :: integer(), - max_line_length :: integer(), + max_request_line_length :: integer(), + max_header_name_length :: integer(), + max_header_value_length :: integer(), timeout :: timeout(), buffer = <<>> :: binary(), host_tokens = undefined :: undefined | cowboy_dispatcher:tokens(), @@ -100,16 +98,21 @@ init(ListenerPid, Socket, Transport, Opts) -> Dispatch = get_value(dispatch, Opts, []), MaxEmptyLines = get_value(max_empty_lines, Opts, 5), MaxKeepalive = get_value(max_keepalive, Opts, infinity), - MaxLineLength = get_value(max_line_length, Opts, 4096), + MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096), + MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64), + MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096), OnRequest = get_value(onrequest, Opts, undefined), OnResponse = get_value(onresponse, Opts, undefined), Timeout = get_value(timeout, Opts, 5000), URLDecDefault = {fun cowboy_http:urldecode/2, crash}, URLDec = get_value(urldecode, Opts, URLDecDefault), ok = ranch:accept_ack(ListenerPid), - wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport, - dispatch=Dispatch, max_empty_lines=MaxEmptyLines, - max_keepalive=MaxKeepalive, max_line_length=MaxLineLength, + wait_request(#state{listener=ListenerPid, socket=Socket, + transport=Transport, dispatch=Dispatch, + max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive, + max_request_line_length=MaxRequestLineLength, + max_header_name_length=MaxHeaderNameLength, + max_header_value_length=MaxHeaderValueLength, timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse, urldecode=URLDec}). @@ -126,7 +129,7 @@ wait_request(State=#state{socket=Socket, transport=Transport, -spec parse_request(#state{}) -> ok. %% We limit the length of the Request-line to MaxLength to avoid endlessly %% reading from the socket and eventually crashing. -parse_request(State=#state{buffer=Buffer, max_line_length=MaxLength, +parse_request(State=#state{buffer=Buffer, max_request_line_length=MaxLength, req_empty_lines=ReqEmpty, max_empty_lines=MaxEmpty}) -> case binary:split(Buffer, <<"\r\n">>) of [_] when byte_size(Buffer) > MaxLength -> @@ -171,29 +174,50 @@ request(State=#state{socket=Socket, transport=Transport, Path, Qs, OnResponse, URLDec)). -spec parse_header(#state{}, cowboy_req:req()) -> ok. -parse_header(State=#state{buffer=Buffer, max_line_length=MaxLength}, Req) -> - case erlang:decode_packet(httph_bin, Buffer, []) of - {ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest}); - {more, _Length} when byte_size(Buffer) > MaxLength -> +parse_header(State=#state{buffer= << "\r\n", Rest/binary >>}, Req) -> + header_end(State#state{buffer=Rest}, Req); +parse_header(State=#state{buffer=Buffer, + max_header_name_length=MaxLength}, Req) -> + case binary:split(Buffer, <<":">>) of + [_] when byte_size(Buffer) > MaxLength -> + error_terminate(413, State); + [_] -> + wait_header(State, Req, fun parse_header/2); + [Name, Rest] -> + Name2 = cowboy_bstr:to_lower(Name), + Rest2 = cowboy_http:whitespace(Rest, fun(D) -> D end), + parse_header_value(State#state{buffer=Rest2}, Req, Name2, <<>>) + end. + +parse_header_value(State=#state{buffer=Buffer, + max_header_value_length=MaxLength}, Req, Name, SoFar) -> + case binary:split(Buffer, <<"\r\n">>) of + [_] when byte_size(Buffer) + byte_size(SoFar) > MaxLength -> error_terminate(413, State); - {more, _Length} -> wait_header(Req, State); - {error, _Reason} -> error_terminate(400, State) + [_] -> + wait_header(State, Req, + fun(S, R) -> parse_header_value(S, R, Name, SoFar) end); + [Value, << C, Rest/binary >>] when C =:= $\s; C =:= $\t -> + parse_header_value(State#state{buffer=Rest}, Req, Name, + << SoFar/binary, Value/binary >>); + [Value, Rest] -> + header(State#state{buffer=Rest}, Req, Name, + << SoFar/binary, Value/binary >>) end. --spec wait_header(cowboy_req:req(), #state{}) -> ok. -wait_header(Req, State=#state{socket=Socket, - transport=Transport, timeout=T, buffer=Buffer}) -> +-spec wait_header(#state{}, cowboy_req:req(), fun()) -> ok. +wait_header(State=#state{socket=Socket, transport=Transport, + timeout=T, buffer=Buffer}, Req, Fun) -> case Transport:recv(Socket, 0, T) of - {ok, Data} -> parse_header(State#state{ + {ok, Data} -> Fun(State#state{ buffer= << Buffer/binary, Data/binary >>}, Req); {error, timeout} -> error_terminate(408, State); {error, closed} -> terminate(State) end. --spec header({http_header, integer(), cowboy_http:header(), any(), binary()} - | http_eoh, cowboy_req:req(), #state{}) -> ok. -header({http_header, _I, 'Host', _R, RawHost}, Req, - State=#state{host_tokens=undefined, transport=Transport}) -> +-spec header(#state{}, cowboy_req:req(), binary(), binary()) -> ok. +header(State=#state{host_tokens=undefined, transport=Transport}, + Req, <<"host">>, RawHost) -> RawHost2 = cowboy_bstr:to_lower(RawHost), case catch cowboy_dispatcher:split_host(RawHost2) of {HostTokens, Host, undefined} -> @@ -207,18 +231,18 @@ header({http_header, _I, 'Host', _R, RawHost}, Req, error_terminate(400, State) end; %% Ignore Host headers if we already have it. -header({http_header, _I, 'Host', _R, _V}, Req, State) -> +header(State, Req, <<"host">>, _) -> parse_header(State, Req); -header({http_header, _I, 'Connection', _R, Connection}, Req, - State=#state{req_keepalive=Keepalive, max_keepalive=MaxKeepalive}) +header(State=#state{req_keepalive=Keepalive, max_keepalive=MaxKeepalive}, + Req, <<"connection">>, Connection) when Keepalive < MaxKeepalive -> parse_header(State, cowboy_req:set_connection(Connection, Req)); -header({http_header, _I, Field, _R, Value}, Req, State) -> - Field2 = format_header(Field), - parse_header(State, cowboy_req:add_header(Field2, Value, Req)); +header(State, Req, Name, Value) -> + parse_header(State, cowboy_req:add_header(Name, Value, Req)). + %% The Host header is required in HTTP/1.1 and optional in HTTP/1.0. -header(http_eoh, Req, State=#state{host_tokens=undefined, - buffer=Buffer, transport=Transport}) -> +header_end(State=#state{host_tokens=undefined, + buffer=Buffer, transport=Transport}, Req) -> case cowboy_req:version(Req) of {{1, 1}, _} -> error_terminate(400, State); @@ -229,10 +253,8 @@ header(http_eoh, Req, State=#state{host_tokens=undefined, cowboy_req:set_host(<<>>, Port, <<>>, Req2)), State#state{buffer= <<>>, host_tokens=[]}) end; -header(http_eoh, Req, State=#state{buffer=Buffer}) -> - onrequest(cowboy_req:set_buffer(Buffer, Req), State#state{buffer= <<>>}); -header(_Any, _Req, State) -> - error_terminate(400, State). +header_end(State=#state{buffer=Buffer}, Req) -> + onrequest(cowboy_req:set_buffer(Buffer, Req), State#state{buffer= <<>>}). %% Call the global onrequest callback. The callback can send a reply, %% in which case we consider the request handled and move on to the next @@ -451,45 +473,3 @@ version_to_connection(_, _) -> -spec default_port(atom()) -> 80 | 443. default_port(ssl) -> 443; default_port(_) -> 80. - -%% @todo While 32 should be enough for everybody, we should probably make -%% this configurable or something. --spec format_header(atom()) -> atom(); (binary()) -> binary(). -format_header(Field) when is_atom(Field) -> - Field; -format_header(Field) when byte_size(Field) =< 20; byte_size(Field) > 32 -> - Field; -format_header(Field) -> - format_header(Field, true, <<>>). - --spec format_header(binary(), boolean(), binary()) -> binary(). -format_header(<<>>, _Any, Acc) -> - Acc; -%% Replicate a bug in OTP for compatibility reasons when there's a - right -%% after another. Proper use should always be 'true' instead of 'not Bool'. -format_header(<< $-, Rest/bits >>, Bool, Acc) -> - format_header(Rest, not Bool, << Acc/binary, $- >>); -format_header(<< C, Rest/bits >>, true, Acc) -> - format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_upper(C)) >>); -format_header(<< C, Rest/bits >>, false, Acc) -> - format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_lower(C)) >>). - -%% Tests. - --ifdef(TEST). - -format_header_test_() -> - %% {Header, Result} - Tests = [ - {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>}, - {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>}, - {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>}, - {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>}, - %% These last tests ensures we're formatting headers exactly like OTP. - %% Even though it's dumb, it's better for compatibility reasons. - {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--version">>}, - {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>} - ], - [{H, fun() -> R = format_header(H) end} || {H, R} <- Tests]. - --endif. diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 8d2cd98..8f2ef15 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -203,8 +203,8 @@ peer(Req) -> %% @doc Returns the peer address calculated from headers. -spec peer_addr(Req) -> {inet:ip_address(), Req} when Req::req(). peer_addr(Req = #http_req{}) -> - {RealIp, Req1} = header(<<"X-Real-Ip">>, Req), - {ForwardedForRaw, Req2} = header(<<"X-Forwarded-For">>, Req1), + {RealIp, Req1} = header(<<"x-real-ip">>, Req), + {ForwardedForRaw, Req2} = header(<<"x-forwarded-for">>, Req1), {{PeerIp, _PeerPort}, Req3} = peer(Req2), ForwardedFor = case ForwardedForRaw of undefined -> @@ -339,15 +339,15 @@ bindings(Req) -> {Req#http_req.bindings, Req}. %% @equiv header(Name, Req, undefined) --spec header(atom() | binary(), Req) +-spec header(binary(), Req) -> {binary() | undefined, Req} when Req::req(). -header(Name, Req) when is_atom(Name) orelse is_binary(Name) -> +header(Name, Req) -> header(Name, Req, undefined). %% @doc Return the header value for the given key, or a default if missing. --spec header(atom() | binary(), Req, Default) +-spec header(binary(), Req, Default) -> {binary() | Default, Req} when Req::req(), Default::any(). -header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) -> +header(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.headers) of {Name, Value} -> {Value, Req}; false -> {Default, Req} @@ -363,7 +363,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(cowboy_http:header(), Req) +-spec parse_header(binary(), Req) -> {ok, any(), Req} | {undefined, binary(), Req} | {error, badarg} when Req::req(). parse_header(Name, Req=#http_req{p_headers=PHeaders}) -> @@ -373,76 +373,77 @@ parse_header(Name, Req=#http_req{p_headers=PHeaders}) -> 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">>]; +-spec parse_header_default(binary()) -> 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(), Req, any()) +-spec parse_header(binary(), Req, any()) -> {ok, any(), Req} | {undefined, binary(), Req} | {error, badarg} when Req::req(). -parse_header(Name, Req, Default) when Name =:= 'Accept' -> +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) 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) 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) 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) 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) 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) 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) 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' -> + 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' -> + 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) 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) when Name =:= <<"upgrade">> -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) @@ -475,7 +476,7 @@ cookie(Name, Req) when is_binary(Name) -> -spec cookie(binary(), Req, Default) -> {binary() | true | Default, Req} when Req::req(), Default::any(). cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> - case header('Cookie', Req) of + case header(<<"cookie">>, Req) of {undefined, Req2} -> {Default, Req2#http_req{cookies=[]}}; {RawCookie, Req2} -> @@ -491,7 +492,7 @@ cookie(Name, Req, Default) -> %% @doc Return the full list of cookie values. -spec cookies(Req) -> {list({binary(), binary() | true}), Req} when Req::req(). cookies(Req=#http_req{cookies=undefined}) -> - case header('Cookie', Req) of + case header(<<"cookie">>, Req) of {undefined, Req2} -> {[], Req2#http_req{cookies=[]}}; {RawCookie, Req2} -> @@ -532,8 +533,8 @@ set_meta(Name, Value, Req=#http_req{meta=Meta}) -> %% @doc Return whether the request message has a body. -spec has_body(Req) -> {boolean(), Req} when Req::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 = 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. @@ -542,11 +543,11 @@ has_body(Req) -> %% and the body hasn't been read at the time of the call. -spec body_length(Req) -> {undefined | non_neg_integer(), Req} when Req::req(). body_length(Req) -> - case lists:keymember('Transfer-Encoding', 1, Req#http_req.headers) of + case lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers) of true -> {undefined, Req}; false -> - {ok, Length, Req2} = parse_header('Content-Length', Req, 0), + {ok, Length, Req2} = parse_header(<<"content-length">>, Req, 0), {Length, Req2} end. @@ -586,7 +587,7 @@ init_stream(TransferDecode, TransferState, ContentDecode, Req) -> | {done, Req} | {error, atom()} when Req::req(). stream_body(Req=#http_req{body_state=waiting, version=Version, transport=Transport, socket=Socket}) -> - case parse_header(<<"Expect">>, Req) of + case parse_header(<<"expect">>, Req) of {ok, [<<"100-continue">>], Req1} -> HTTPVer = cowboy_http:version_to_binary(Version), Transport:send(Socket, @@ -594,7 +595,7 @@ stream_body(Req=#http_req{body_state=waiting, {ok, undefined, Req1} -> ok end, - case parse_header('Transfer-Encoding', Req1) of + case parse_header(<<"transfer-encoding">>, Req1) of {ok, [<<"chunked">>], Req2} -> stream_body(Req2#http_req{body_state= {stream, fun cowboy_http:te_chunked/2, {0, 0}, @@ -657,13 +658,13 @@ transfer_decode(Data, Req=#http_req{ -> Req when Req::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))}), + 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), + 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}. @@ -743,9 +744,9 @@ body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) -> | {end_of_part | eof, Req} when Req::req(). multipart_data(Req=#http_req{body_state=waiting}) -> {ok, {<<"multipart">>, _SubType, Params}, Req2} = - parse_header('Content-Type', Req), + parse_header(<<"content-type">>, Req), {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), - {ok, Length, Req3} = parse_header('Content-Length', Req2), + {ok, Length, Req3} = parse_header(<<"content-length">>, Req2), multipart_data(Req3, Length, {more, cowboy_multipart:parser(Boundary)}); multipart_data(Req=#http_req{multipart={Length, Cont}}) -> multipart_data(Req, Length, Cont()); @@ -796,11 +797,10 @@ set_resp_cookie(Name, Value, Options, Req) -> set_resp_header(HeaderName, HeaderValue, Req). %% @doc Add a header to the response. --spec set_resp_header(cowboy_http:header(), iodata(), Req) +-spec set_resp_header(binary(), iodata(), Req) -> Req when Req::req(). set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> - NameBin = header_to_binary(Name), - Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}. + Req#http_req{resp_headers=[{Name, Value}|RespHeaders]}. %% @doc Add a body to the response. %% @@ -831,10 +831,9 @@ set_resp_body_fun(StreamLen, StreamFun, Req) -> 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(), req()) -> boolean(). +-spec has_resp_header(binary(), req()) -> boolean(). has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> - NameBin = header_to_binary(Name), - lists:keymember(NameBin, 1, RespHeaders). + lists:keymember(Name, 1, RespHeaders). %% @doc Return whether a body has been set for the response. -spec has_resp_body(req()) -> boolean(). @@ -844,7 +843,7 @@ has_resp_body(#http_req{resp_body=RespBody}) -> iolist_size(RespBody) > 0. %% Remove a header previously set for the response. --spec delete_resp_header(cowboy_http:header(), Req) +-spec delete_resp_header(binary(), Req) -> Req when Req::req(). delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) -> RespHeaders2 = lists:keydelete(Name, 1, RespHeaders), @@ -870,13 +869,13 @@ reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport, 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)}]; + {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">>} + {<<"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. @@ -904,13 +903,13 @@ chunked_reply(Status, Headers, Req=#http_req{ RespConn = response_connection(Headers, Connection), HTTP11Headers = case Version of {1, 1} -> [ - {<<"Connection">>, atom_to_connection(Connection)}, - {<<"Transfer-Encoding">>, <<"chunked">>}]; + {<<"connection">>, atom_to_connection(Connection)}, + {<<"transfer-encoding">>, <<"chunked">>}]; _ -> [] end, {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"Date">>, cowboy_clock:rfc1123()}, - {<<"Server">>, <<"Cowboy">>} + {<<"date">>, cowboy_clock:rfc1123()}, + {<<"server">>, <<"Cowboy">>} |HTTP11Headers], Req), {ok, Req2#http_req{connection=RespConn, resp_state=chunks, resp_headers=[], resp_body= <<>>}}. @@ -934,7 +933,7 @@ chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> upgrade_reply(Status, Headers, Req=#http_req{ resp_state=waiting, resp_headers=RespHeaders}) -> {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"Connection">>, <<"Upgrade">>} + {<<"connection">>, <<"Upgrade">>} ], Req), {ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. @@ -965,18 +964,18 @@ ensure_response(#http_req{socket=Socket, transport=Transport, -spec set_host(binary(), inet:port_number(), binary(), Req) -> Req when Req::req(). set_host(Host, Port, RawHost, Req=#http_req{headers=Headers}) -> - Req#http_req{host=Host, port=Port, headers=[{'Host', RawHost}|Headers]}. + Req#http_req{host=Host, port=Port, headers=[{<<"host">>, RawHost}|Headers]}. %% @private -spec set_connection(binary(), Req) -> Req when Req::req(). set_connection(RawConnection, Req=#http_req{headers=Headers}) -> - Req2 = Req#http_req{headers=[{'Connection', RawConnection}|Headers]}, - {ok, ConnTokens, Req3} = parse_header('Connection', Req2), + Req2 = Req#http_req{headers=[{<<"connection">>, RawConnection}|Headers]}, + {ok, ConnTokens, Req3} = parse_header(<<"connection">>, Req2), ConnAtom = cowboy_http:connection_to_atom(ConnTokens), Req3#http_req{connection=ConnAtom}. %% @private --spec add_header(cowboy_http:header(), binary(), Req) +-spec add_header(binary(), binary(), Req) -> Req when Req::req(). add_header(Name, Value, Req=#http_req{headers=Headers}) -> Req#http_req{headers=[{Name, Value}|Headers]}. @@ -1084,14 +1083,8 @@ 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 + <<"connection">> -> response_connection_parse(Value); + _ -> response_connection(Tail, Connection) end. -spec response_connection_parse(binary()) -> keepalive | close. @@ -1102,7 +1095,7 @@ response_connection_parse(ReplyConn) -> -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], + Headers2 = [{Key, Value} || {Key, Value} <- Headers], merge_headers( merge_headers(Headers2, RespHeaders), DefaultHeaders). @@ -1185,61 +1178,6 @@ 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. - %% Tests. -ifdef(TEST). diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index f084a6c..ad8d62f 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -210,7 +210,7 @@ content_types_provided(Req, State) -> CTP2 = [normalize_content_types(P) || P <- CTP], State2 = State#state{ handler_state=HandlerState, content_types_p=CTP2}, - {ok, Accept, Req3} = cowboy_req:parse_header('Accept', Req2), + {ok, Accept, Req3} = cowboy_req:parse_header(<<"accept">>, Req2), case Accept of undefined -> {PMT, _Fun} = HeadCTP = hd(CTP2), @@ -305,7 +305,7 @@ languages_provided(Req, State) -> {LP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, languages_p=LP}, {ok, AcceptLanguage, Req3} = - cowboy_req:parse_header('Accept-Language', Req2), + cowboy_req:parse_header(<<"accept-language">>, Req2), case AcceptLanguage of undefined -> set_language(Req3, State2#state{language_a=hd(LP)}); @@ -367,7 +367,7 @@ charsets_provided(Req, State) -> {CP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, charsets_p=CP}, {ok, AcceptCharset, Req3} = - cowboy_req:parse_header('Accept-Charset', Req2), + cowboy_req:parse_header(<<"accept-charset">>, Req2), case AcceptCharset of undefined -> set_content_type(Req3, State2#state{ @@ -479,7 +479,7 @@ resource_exists(Req, State) -> fun if_match_exists/2, fun if_match_musnt_exist/2). if_match_exists(Req, State) -> - case cowboy_req:parse_header('If-Match', Req) of + case cowboy_req:parse_header(<<"if-match">>, Req) of {ok, undefined, Req2} -> if_unmodified_since_exists(Req2, State); {ok, '*', Req2} -> @@ -497,13 +497,13 @@ if_match(Req, State, EtagsList) -> end. if_match_musnt_exist(Req, State) -> - case cowboy_req:header('If-Match', Req) of + case cowboy_req:header(<<"if-match">>, Req) of {undefined, Req2} -> is_put_to_missing_resource(Req2, State); {_Any, Req2} -> precondition_failed(Req2, State) end. if_unmodified_since_exists(Req, State) -> - case cowboy_req:parse_header('If-Unmodified-Since', Req) of + case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of {ok, undefined, Req2} -> if_none_match_exists(Req2, State); {ok, IfUnmodifiedSince, Req2} -> @@ -521,7 +521,7 @@ if_unmodified_since(Req, State, IfUnmodifiedSince) -> end. if_none_match_exists(Req, State) -> - case cowboy_req:parse_header('If-None-Match', Req) of + case cowboy_req:parse_header(<<"if-none-match">>, Req) of {ok, undefined, Req2} -> if_modified_since_exists(Req2, State); {ok, '*', Req2} -> @@ -549,7 +549,7 @@ precondition_is_head_get(Req, State) -> precondition_failed(Req, State). if_modified_since_exists(Req, State) -> - case cowboy_req:parse_header('If-Modified-Since', Req) of + case cowboy_req:parse_header(<<"if-modified-since">>, Req) of {ok, undefined, Req2} -> method(Req2, State); {ok, IfModifiedSince, Req2} -> @@ -715,7 +715,7 @@ put_resource(Req, State, OnTrue) -> CTA2 = [normalize_content_types(P) || P <- CTA], State2 = State#state{handler_state=HandlerState}, {ok, ContentType, Req3} - = cowboy_req:parse_header('Content-Type', Req2), + = cowboy_req:parse_header(<<"content-type">>, Req2), choose_content_type(Req3, State2, OnTrue, ContentType, CTA2) end. diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index a6f2be3..dcd3008 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -72,11 +72,12 @@ upgrade(ListenerPid, Handler, Opts, Req) -> -> {ok, #state{}, Req} when Req::cowboy_req:req(). websocket_upgrade(State, Req) -> {ok, ConnTokens, Req2} - = cowboy_req:parse_header('Connection', Req), + = cowboy_req:parse_header(<<"connection">>, Req), true = lists:member(<<"upgrade">>, ConnTokens), %% @todo Should probably send a 426 if the Upgrade header is missing. - {ok, [<<"websocket">>], Req3} = cowboy_req:parse_header('Upgrade', Req2), - {Version, Req4} = cowboy_req:header(<<"Sec-Websocket-Version">>, Req3), + {ok, [<<"websocket">>], Req3} + = cowboy_req:parse_header(<<"upgrade">>, Req2), + {Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3), websocket_upgrade(Version, State, Req4). %% @todo Handle the Sec-Websocket-Protocol header. @@ -90,9 +91,9 @@ websocket_upgrade(State, Req) -> %% a reply before sending it. Therefore we calculate the challenge %% key only in websocket_handshake/3. websocket_upgrade(undefined, State, Req) -> - {Origin, Req2} = cowboy_req:header(<<"Origin">>, Req), - {Key1, Req3} = cowboy_req:header(<<"Sec-Websocket-Key1">>, Req2), - {Key2, Req4} = cowboy_req:header(<<"Sec-Websocket-Key2">>, Req3), + {Origin, Req2} = cowboy_req:header(<<"origin">>, Req), + {Key1, Req3} = cowboy_req:header(<<"sec-websocket-key1">>, Req2), + {Key2, Req4} = cowboy_req:header(<<"sec-websocket-key2">>, Req3), false = lists:member(undefined, [Origin, Key1, Key2]), EOP = binary:compile_pattern(<< 255 >>), {ok, State#state{version=0, origin=Origin, challenge={Key1, Key2}, @@ -101,7 +102,7 @@ websocket_upgrade(undefined, State, Req) -> websocket_upgrade(Version, State, Req) when Version =:= <<"7">>; Version =:= <<"8">>; Version =:= <<"13">> -> - {Key, Req2} = cowboy_req:header(<<"Sec-Websocket-Key">>, Req), + {Key, Req2} = cowboy_req:header(<<"sec-websocket-key">>, Req), false = Key =:= undefined, Challenge = hybi_challenge(Key), IntVersion = list_to_integer(binary_to_list(Version)), diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index af1e9cd..a7e49f9 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -218,11 +218,11 @@ init_dispatch(Config) -> {[<<"init_shutdown">>], http_handler_init_shutdown, []}, {[<<"long_polling">>], http_handler_long_polling, []}, {[<<"headers">>, <<"dupe">>], http_handler, - [{headers, [{<<"Connection">>, <<"close">>}]}]}, + [{headers, [{<<"connection">>, <<"close">>}]}]}, {[<<"set_resp">>, <<"header">>], http_handler_set_resp, - [{headers, [{<<"Vary">>, <<"Accept">>}]}]}, + [{headers, [{<<"vary">>, <<"Accept">>}]}]}, {[<<"set_resp">>, <<"overwrite">>], http_handler_set_resp, - [{headers, [{<<"Server">>, <<"DesireDrive/1.0">>}]}]}, + [{headers, [{<<"server">>, <<"DesireDrive/1.0">>}]}]}, {[<<"set_resp">>, <<"body">>], http_handler_set_resp, [{body, <<"A flameless dance does not equal a cycle">>}]}, {[<<"stream_body">>, <<"set_resp">>], http_handler_stream_body, @@ -599,7 +599,7 @@ onrequest_reply(Config) -> onrequest_hook(Req) -> case cowboy_req:qs_val(<<"reply">>, Req) of {undefined, Req2} -> - cowboy_req:set_resp_header('Server', <<"Serenity">>, Req2); + cowboy_req:set_resp_header(<<"server">>, <<"Serenity">>, Req2); {_, Req2} -> {ok, Req3} = cowboy_req:reply( 200, [], <<"replied!">>, Req2), diff --git a/test/http_handler_init_shutdown.erl b/test/http_handler_init_shutdown.erl index c26fa97..edea1a0 100644 --- a/test/http_handler_init_shutdown.erl +++ b/test/http_handler_init_shutdown.erl @@ -6,7 +6,7 @@ init({_Transport, http}, Req, _Opts) -> {ok, Req2} = cowboy_req:reply(<<"666 Init Shutdown Testing">>, - [{'Connection', <<"close">>}], Req), + [{<<"connection">>, <<"close">>}], Req), {shutdown, Req2, undefined}. handle(Req, State) -> diff --git a/test/http_handler_set_resp.erl b/test/http_handler_set_resp.erl index 806bca8..70ddf79 100644 --- a/test/http_handler_set_resp.erl +++ b/test/http_handler_set_resp.erl @@ -11,12 +11,12 @@ init({_Transport, http}, Req, Opts) -> cowboy_req:set_resp_header(Name, Value, R) end, Req, Headers), Req3 = cowboy_req:set_resp_body(Body, Req2), - Req4 = cowboy_req:set_resp_header(<<"X-Cowboy-Test">>, <<"ok">>, Req3), + Req4 = cowboy_req:set_resp_header(<<"x-cowboy-test">>, <<"ok">>, Req3), Req5 = cowboy_req:set_resp_cookie(<<"cake">>, <<"lie">>, [], Req4), {ok, Req5, undefined}. handle(Req, State) -> - case cowboy_req:has_resp_header(<<"X-Cowboy-Test">>, Req) of + case cowboy_req:has_resp_header(<<"x-cowboy-test">>, Req) of false -> {ok, Req, State}; true -> case cowboy_req:has_resp_body(Req) of -- cgit v1.2.3