From c0efc574f0d2426ab6c48d53f828ccc183ff088f Mon Sep 17 00:00:00 2001 From: Alexander Clouter Date: Sun, 15 Jul 2018 15:40:19 +0100 Subject: [PATCH] raw headers support --- doc/src/manual/cowboy_http.asciidoc | 4 ++++ src/cowboy_http.erl | 36 ++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/doc/src/manual/cowboy_http.asciidoc b/doc/src/manual/cowboy_http.asciidoc index 21a6d654c..21f53c22e 100644 --- a/doc/src/manual/cowboy_http.asciidoc +++ b/doc/src/manual/cowboy_http.asciidoc @@ -26,6 +26,7 @@ opts() :: #{ max_header_name_length => non_neg_integer(), max_header_value_length => non_neg_integer(), max_headers => non_neg_integer(), + headers_raw => boolean(), max_keepalive => non_neg_integer(), max_method_length => non_neg_integer(), max_request_line_length => non_neg_integer(), @@ -77,6 +78,9 @@ max_header_value_length (4096):: max_headers (100):: Maximum number of headers allowed per request. +headers_raw (false):: + Populate headers_raw in the request. + max_keepalive (100):: Maximum number of requests allowed per connection. diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index e1af0e9a0..8a0a567ce 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -36,6 +36,7 @@ max_header_name_length => non_neg_integer(), max_header_value_length => non_neg_integer(), max_headers => non_neg_integer(), + headers_raw => boolean(), max_keepalive => non_neg_integer(), max_method_length => non_neg_integer(), max_request_line_length => non_neg_integer(), @@ -57,6 +58,7 @@ qs = undefined :: binary(), version = undefined :: cowboy:http_version(), headers = undefined :: map() | undefined, %% @todo better type than map() + headers_raw = undefined :: binary(), name = undefined :: binary() | undefined }). @@ -491,8 +493,9 @@ parse_version(_, State, _, _, _, _) -> 'Unsupported HTTP version. (RFC7230 2.6)'}). before_parse_headers(Rest, State, M, A, P, Q, V) -> + HR = case maps:get(headers_raw, State#state.opts, false) of true -> <<>>; _ -> undefined end, parse_header(Rest, State#state{in_state=#ps_header{ - method=M, authority=A, path=P, qs=Q, version=V}}, #{}). + method=M, authority=A, path=P, qs=Q, version=V, headers_raw=HR}}, #{}). %% Headers. @@ -533,6 +536,9 @@ match_colon(<< _, Rest/bits >>, N) -> match_colon(_, _) -> nomatch. +parse_hd_name(<< $:, Rest/bits >>, State=#state{in_state=PS=#ps_header{headers_raw=HR0}}, H, SoFar) when is_binary(HR0) -> + HR = <>, + parse_hd_before_value(Rest, State#state{in_state=PS#ps_header{headers_raw=HR}}, H, SoFar); parse_hd_name(<< $:, Rest/bits >>, State, H, SoFar) -> parse_hd_before_value(Rest, State, H, SoFar); parse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, <<>>) when ?IS_WS(C) -> @@ -543,12 +549,16 @@ parse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, _) when ?IS_WS(C) - error_terminate(400, State#state{in_state=PS#ps_header{headers=H}}, {connection_error, protocol_error, 'Whitespace is not allowed between the header name and the colon. (RFC7230 3.2.4)'}); +parse_hd_name(<< C, Rest/bits >>, State=#state{in_state=PS=#ps_header{headers_raw=HR0}}, H, SoFar) when is_binary(HR0) -> + HR = <>, + ?LOWER(parse_hd_name, Rest, State#state{in_state=PS#ps_header{headers_raw=HR}}, H, SoFar); parse_hd_name(<< C, Rest/bits >>, State, H, SoFar) -> ?LOWER(parse_hd_name, Rest, State, H, SoFar). -parse_hd_before_value(<< $\s, Rest/bits >>, S, H, N) -> - parse_hd_before_value(Rest, S, H, N); -parse_hd_before_value(<< $\t, Rest/bits >>, S, H, N) -> +parse_hd_before_value(<< C, Rest/bits >>, S=#state{in_state=PS=#ps_header{headers_raw=HR0}}, H, N) when ?IS_WS(C), is_binary(HR0) -> + HR = <>, + parse_hd_before_value(Rest, S#state{in_state=PS#ps_header{headers_raw=HR}}, H, N); +parse_hd_before_value(<< C, Rest/bits >>, S, H, N) when ?IS_WS(C) -> parse_hd_before_value(Rest, S, H, N); parse_hd_before_value(Buffer, State=#state{opts=Opts, in_state=PS}, H, N) -> MaxLength = maps:get(max_header_value_length, Opts, 4096), @@ -563,7 +573,7 @@ parse_hd_before_value(Buffer, State=#state{opts=Opts, in_state=PS}, H, N) -> parse_hd_value(Buffer, State, H, N, <<>>) end. -parse_hd_value(<< $\r, $\n, Rest/bits >>, S, Headers0, Name, SoFar) -> +parse_hd_value(<< $\r, $\n, Rest/bits >>, S=#state{in_state=PS=#ps_header{headers_raw=HR0}}, Headers0, Name, SoFar) -> Value = clean_value_ws_end(SoFar, byte_size(SoFar) - 1), Headers = case maps:get(Name, Headers0, undefined) of undefined -> Headers0#{Name => Value}; @@ -571,7 +581,16 @@ parse_hd_value(<< $\r, $\n, Rest/bits >>, S, Headers0, Name, SoFar) -> Value0 when Name =:= <<"cookie">> -> Headers0#{Name => << Value0/binary, "; ", Value/binary >>}; Value0 -> Headers0#{Name => << Value0/binary, ", ", Value/binary >>} end, - parse_header(Rest, S, Headers); + if + is_binary(HR0) -> + HR = <>, + parse_header(Rest, S#state{in_state=PS#ps_header{headers_raw=HR}}, Headers); + true -> + parse_header(Rest, S, Headers) + end; +parse_hd_value(<< C, Rest/bits >>, S=#state{in_state=PS=#ps_header{headers_raw=HR0}}, H, N, SoFar) when is_binary(HR0) -> + HR = <>, + parse_hd_value(Rest, S#state{in_state=PS#ps_header{headers_raw=HR}}, H, N, << SoFar/binary, C >>); parse_hd_value(<< C, Rest/bits >>, S, H, N, SoFar) -> parse_hd_value(Rest, S, H, N, << SoFar/binary, C >>). @@ -657,7 +676,7 @@ default_port(_) -> 80. request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert, in_streamid=StreamID, in_state= - PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}}, + PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version, headers_raw=HeadersRaw}}, Headers0, Host, Port) -> Scheme = case Transport:secure() of true -> <<"https">>; @@ -709,6 +728,7 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock %% We are transparently taking care of transfer-encodings so %% the user code has no need to know about it. headers => maps:remove(<<"transfer-encoding">>, Headers), + headers_raw => HeadersRaw, has_body => HasBody, body_length => BodyLength }, @@ -728,7 +748,7 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock {true, HTTP2Settings} -> %% We save the headers in case the upgrade will fail %% and we need to pass them to cowboy_stream:early_error. - http2_upgrade(State0#state{in_state=PS#ps_header{headers=Headers}}, + http2_upgrade(State0#state{in_state=PS#ps_header{headers=Headers,headers_raw=undefined}}, Buffer, HTTP2Settings, Req) end.