aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-02-28 16:18:29 +0100
committerLoïc Hoguin <[email protected]>2018-02-28 16:18:29 +0100
commit19054e40e04e3f3012963c3229033c69d4973388 (patch)
treee5cbd2b9b97a47d6c4b4a8cd2acccfb1973586a7
parente23e12287ab0557444c676c8f6c3c27db3427659 (diff)
downloadcowboy-19054e40e04e3f3012963c3229033c69d4973388.tar.gz
cowboy-19054e40e04e3f3012963c3229033c69d4973388.tar.bz2
cowboy-19054e40e04e3f3012963c3229033c69d4973388.zip
Fix crash in cowboy_http2 when content-length is invalid
-rw-r--r--src/cowboy_http2.erl25
-rw-r--r--test/rfc7540_SUITE.erl20
2 files changed, 33 insertions, 12 deletions
diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl
index 54cd5c8..6399346 100644
--- a/src/cowboy_http2.erl
+++ b/src/cowboy_http2.erl
@@ -927,24 +927,27 @@ headers_to_map([{Name, Value}|Tail], Acc0) ->
end,
headers_to_map(Tail, Acc).
-stream_req_init(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert},
- StreamID, IsFin, Headers, #{method := Method, scheme := Scheme,
- authority := Authority, path := PathWithQs}) ->
- BodyLength = case Headers of
+stream_req_init(State, StreamID, IsFin, Headers, PseudoHeaders) ->
+ case Headers of
_ when IsFin =:= fin ->
- 0;
+ stream_req_init(State, StreamID, IsFin, Headers, PseudoHeaders, 0);
#{<<"content-length">> := <<"0">>} ->
- 0;
+ stream_req_init(State, StreamID, IsFin, Headers, PseudoHeaders, 0);
#{<<"content-length">> := BinLength} ->
try
- cow_http_hd:parse_content_length(BinLength)
+ stream_req_init(State, StreamID, IsFin, Headers, PseudoHeaders,
+ cow_http_hd:parse_content_length(BinLength))
catch _:_ ->
- terminate(State, {stream_error, StreamID, protocol_error,
- 'The content-length header is invalid. (RFC7230 3.3.2)'})
+ stream_malformed(State, StreamID,
+ 'The content-length header is invalid. (RFC7230 3.3.2)')
end;
_ ->
- undefined
- end,
+ stream_req_init(State, StreamID, IsFin, Headers, PseudoHeaders, undefined)
+ end.
+
+stream_req_init(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert},
+ StreamID, IsFin, Headers, #{method := Method, scheme := Scheme,
+ authority := Authority, path := PathWithQs}, BodyLength) ->
try cow_http_hd:parse_host(Authority) of
{Host, Port} ->
try cow_http:parse_fullpath(PathWithQs) of
diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl
index fdd83b3..a97da71 100644
--- a/test/rfc7540_SUITE.erl
+++ b/test/rfc7540_SUITE.erl
@@ -3196,7 +3196,25 @@ reject_many_pseudo_header_path(Config) ->
% that is defined to have no payload, as described in [RFC7230],
% Section 3.3.2, can have a non-zero content-length header field, even
% though no content is included in DATA frames.
-%
+
+reject_duplicate_content_length_header(Config) ->
+ doc("A request with duplicate content-length headers must be rejected "
+ "with a PROTOCOL_ERROR stream error. (RFC7230 3.3.2, RFC7540 8.1.2.6)"),
+ {ok, Socket} = do_handshake(Config),
+ %% Send a HEADERS frame with more than one content-length header.
+ {HeadersBlock, _} = cow_hpack:encode([
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+ {<<":path">>, <<>>},
+ {<<"content-length">>, <<"12">>},
+ {<<"content-length">>, <<"12">>}
+ ]),
+ ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
+ %% Receive a PROTOCOL_ERROR stream error.
+ {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
+ ok.
+
% Intermediaries that process HTTP requests or responses (i.e., any
% intermediary not acting as a tunnel) MUST NOT forward a malformed
% request or response. Malformed requests or responses that are