aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2020-02-27 12:19:07 +0100
committerLoïc Hoguin <[email protected]>2020-02-27 15:18:02 +0100
commitc236ed09726a4283a597f662307c9ac07e4f3b7a (patch)
tree2d2cde30817e9b56b41ba3a8e601e991f08f3110 /src
parent069040a93bb88477dcae197fa14280a10cce72d8 (diff)
downloadgun-c236ed09726a4283a597f662307c9ac07e4f3b7a.tar.gz
gun-c236ed09726a4283a597f662307c9ac07e4f3b7a.tar.bz2
gun-c236ed09726a4283a597f662307c9ac07e4f3b7a.zip
Detect invalid HTTP/2 preface errors
And make sure all HTTP/2 connection_error(s) result in a gun_down message containing the error. In the preface case we do not send a gun_error message (because there's no stream open yet) and gun_down was always saying normal. Also make sure the human readable reason is included in the gun_error message, if any.
Diffstat (limited to 'src')
-rw-r--r--src/gun_http2.erl37
1 files changed, 32 insertions, 5 deletions
diff --git a/src/gun_http2.erl b/src/gun_http2.erl
index 99776c6..990d08c 100644
--- a/src/gun_http2.erl
+++ b/src/gun_http2.erl
@@ -75,7 +75,7 @@
%% is established and continues normally. An exception is when a HEADERS
%% frame is sent followed by CONTINUATION frames: no other frame can be
%% sent in between.
- parse_state = undefined :: normal
+ parse_state = undefined :: preface | normal
| {continuation, cowboy_stream:streamid(), cowboy_stream:fin(), binary()},
%% HPACK decoding and encoding state.
@@ -107,7 +107,7 @@ init(Owner, Socket, Transport, Opts) ->
Handlers = maps:get(content_handlers, Opts, [gun_data_h]),
State = #http2_state{owner=Owner, socket=Socket,
transport=Transport, opts=Opts, content_handlers=Handlers,
- parse_state=normal}, %% @todo Have a special parse state for preface.
+ parse_state=preface},
#http2_state{local_settings=Settings} = State,
%% Send the HTTP/2 preface.
Transport:send(Socket, [
@@ -119,8 +119,32 @@ init(Owner, Socket, Transport, Opts) ->
handle(Data, State=#http2_state{buffer=Buffer}) ->
parse(<< Buffer/binary, Data/binary >>, State#http2_state{buffer= <<>>}).
+parse(Data0, State0=#http2_state{buffer=Buffer, parse_state=preface}) ->
+ Data = << Buffer/binary, Data0/binary >>,
+ case cow_http2:parse(Data) of
+ {ok, Frame, Rest} when element(1, Frame) =:= settings ->
+ case frame(Frame, State0#http2_state{parse_state=normal}) of
+ close -> close;
+ Error = {error, _} -> Error;
+ State -> parse(Rest, State)
+ end;
+ more ->
+ case Data of
+ %% Maybe we have a proper SETTINGS frame.
+ <<_:24,4:8,_/bits>> ->
+ {state, State0#http2_state{buffer=Data}};
+ %% Not a SETTINGS frame, this is an invalid preface.
+ _ ->
+ terminate(State0, {connection_error, protocol_error,
+ 'Invalid connection preface received. (RFC7540 3.5)'})
+ end;
+ %% Any error in the preface is converted to this specific error
+ %% to make debugging the problem easier (it's the server's fault).
+ _ ->
+ terminate(State0, {connection_error, protocol_error,
+ 'Invalid connection preface received. (RFC7540 3.5)'})
+ end;
parse(Data0, State0=#http2_state{buffer=Buffer, parse_state=PS}) ->
- %% @todo Parse states: Preface. Continuation.
Data = << Buffer/binary, Data0/binary >>,
case cow_http2:parse(Data) of
{ok, Frame, Rest} when PS =:= normal ->
@@ -524,7 +548,7 @@ terminate(#http2_state{socket=Socket, transport=Transport, streams=Streams}, Rea
_ = [ReplyTo ! {gun_error, self(), Reason} || #stream{reply_to=ReplyTo} <- Streams],
%% @todo LastGoodStreamID
Transport:send(Socket, cow_http2:goaway(0, terminate_reason(Reason), <<>>)),
- close.
+ terminate_ret(Reason).
terminate(State=#http2_state{socket=Socket, transport=Transport}, StreamID, Reason) ->
case get_stream_by_id(StreamID, State) of
@@ -532,7 +556,7 @@ terminate(State=#http2_state{socket=Socket, transport=Transport}, StreamID, Reas
ReplyTo ! {gun_error, self(), Reason},
%% @todo LastGoodStreamID
Transport:send(Socket, cow_http2:goaway(0, terminate_reason(Reason), <<>>)),
- close;
+ terminate_ret(Reason);
_ ->
terminate(State, Reason)
end.
@@ -540,6 +564,9 @@ terminate(State=#http2_state{socket=Socket, transport=Transport}, StreamID, Reas
terminate_reason({connection_error, Reason, _}) -> Reason;
terminate_reason({stop, _, _}) -> no_error.
+terminate_ret(Reason={connection_error, _, _}) -> {error, Reason};
+terminate_ret(_) -> close.
+
%% Stream functions.
stream_decode_init(State=#http2_state{decode_state=DecodeState0}, StreamID, IsFin, HeaderBlock) ->