aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http2.erl
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-04-29 11:29:26 +0200
committerLoïc Hoguin <[email protected]>2018-04-29 17:39:48 +0200
commit84b4128d068cddc37bbb45e755044183f3e9cd97 (patch)
tree641891c28f3409f92dafad2a7ea114c290ca423a /src/cowboy_http2.erl
parent2db5ffbf842e0aeb0ae11484b8c247bbfdcf5e9e (diff)
downloadcowboy-84b4128d068cddc37bbb45e755044183f3e9cd97.tar.gz
cowboy-84b4128d068cddc37bbb45e755044183f3e9cd97.tar.bz2
cowboy-84b4128d068cddc37bbb45e755044183f3e9cd97.zip
Receive and ignore HTTP/2 request trailers if any
This is a first step toward properly supporting request trailers.
Diffstat (limited to 'src/cowboy_http2.erl')
-rw-r--r--src/cowboy_http2.erl69
1 files changed, 59 insertions, 10 deletions
diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl
index 9bd1bda..bb075ed 100644
--- a/src/cowboy_http2.erl
+++ b/src/cowboy_http2.erl
@@ -420,14 +420,28 @@ frame(State0=#state{remote_window=ConnWindow, streams=Streams, lingering_streams
frame(State, {headers, StreamID, _, _, _}) when StreamID rem 2 =:= 0 ->
terminate(State, {connection_error, protocol_error,
'HEADERS frame received with even-numbered streamid. (RFC7540 5.1.1)'});
-%% HEADERS frame received on (half-)closed stream.
-%%
-%% We always close the connection here to avoid having to decode
-%% the headers to not waste resources on non-compliant clients.
-frame(State=#state{client_streamid=LastStreamID}, {headers, StreamID, _, _, _})
+%% Either a HEADERS frame received on (half-)closed stream,
+%% or a HEADERS frame containing the trailers.
+frame(State=#state{client_streamid=LastStreamID, streams=Streams},
+ {headers, StreamID, IsFin, IsHeadFin, HeaderBlockOrFragment})
when StreamID =< LastStreamID ->
- terminate(State, {connection_error, stream_closed,
- 'HEADERS frame received on a stream in closed or half-closed state. (RFC7540 5.1)'});
+ case lists:keyfind(StreamID, #stream.id, Streams) of
+ Stream = #stream{remote=nofin} when IsFin =:= fin ->
+ case IsHeadFin of
+ head_fin ->
+ stream_decode_trailers(State, Stream, HeaderBlockOrFragment);
+ head_nofin ->
+ State#state{parse_state={continuation, StreamID, IsFin, HeaderBlockOrFragment}}
+ end;
+ %% We always close the connection here to avoid having to decode
+ %% the headers to not waste resources on non-compliant clients.
+ #stream{remote=nofin} when IsFin =:= nofin ->
+ terminate(State, {connection_error, protocol_error,
+ 'Trailing HEADERS frame received without the END_STREAM flag set. (RFC7540 8.1, RFC7540 8.1.2.6)'});
+ _ ->
+ terminate(State, {connection_error, stream_closed,
+ 'HEADERS frame received on a stream in closed or half-closed state. (RFC7540 5.1)'})
+ end;
%% Single HEADERS frame headers block.
frame(State, {headers, StreamID, IsFin, head_fin, HeaderBlock}) ->
%% @todo We probably need to validate StreamID here and in 4 next clauses.
@@ -544,10 +558,17 @@ frame(State, {continuation, _, _, _}) ->
terminate(State, {connection_error, protocol_error,
'CONTINUATION frames MUST be preceded by a HEADERS frame. (RFC7540 6.10)'}).
-continuation_frame(State=#state{parse_state={continuation, StreamID, IsFin, HeaderBlockFragment0}},
+continuation_frame(State=#state{client_streamid=LastStreamID, streams=Streams,
+ parse_state={continuation, StreamID, IsFin, HeaderBlockFragment0}},
{continuation, StreamID, head_fin, HeaderBlockFragment1}) ->
- stream_decode_init(State#state{parse_state=normal}, StreamID, IsFin,
- << HeaderBlockFragment0/binary, HeaderBlockFragment1/binary >>);
+ HeaderBlock = << HeaderBlockFragment0/binary, HeaderBlockFragment1/binary >>,
+ case StreamID > LastStreamID of
+ true -> %% New stream.
+ stream_decode_init(State#state{parse_state=normal}, StreamID, IsFin, HeaderBlock);
+ false -> %% Trailers.
+ Stream = lists:keyfind(StreamID, #stream.id, Streams),
+ stream_decode_trailers(State, Stream, HeaderBlock)
+ end;
continuation_frame(State=#state{parse_state={continuation, StreamID, IsFin, HeaderBlockFragment0}},
{continuation, StreamID, head_nofin, HeaderBlockFragment1}) ->
State#state{parse_state={continuation, StreamID, IsFin,
@@ -1181,6 +1202,34 @@ stream_handler_init(State=#state{opts=Opts,
'Unhandled exception in cowboy_stream:init/3.'})
end.
+stream_decode_trailers(State=#state{decode_state=DecodeState0}, Stream, HeaderBlock) ->
+ try cow_hpack:decode(HeaderBlock, DecodeState0) of
+ {Headers, DecodeState} ->
+ stream_reject_pseudo_headers_in_trailers(State#state{decode_state=DecodeState},
+ Stream#stream{remote=fin}, Headers)
+ catch _:_ ->
+ terminate(State, {connection_error, compression_error,
+ 'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'})
+ end.
+
+stream_reject_pseudo_headers_in_trailers(State, Stream=#stream{id=StreamID}, Headers) ->
+ case has_pseudo_header(Headers) of
+ false ->
+ %% @todo There's probably a number of regular headers forbidden too.
+ %% @todo Propagate trailers.
+ after_commands(State, Stream);
+ true ->
+ stream_reset(State, StreamID, {stream_error, protocol_error,
+ 'Trailer header blocks must not contain pseudo-headers. (RFC7540 8.1.2.1)'})
+ end.
+
+has_pseudo_header([]) ->
+ false;
+has_pseudo_header([{<<":", _/bits>>, _}|_]) ->
+ true;
+has_pseudo_header([_|Tail]) ->
+ has_pseudo_header(Tail).
+
%% @todo Don't send an RST_STREAM if one was already sent.
stream_reset(State=#state{socket=Socket, transport=Transport}, StreamID, StreamError) ->
Reason = case StreamError of