From a49200eef5681a21b47b9398804021b340486db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 26 Sep 2018 16:05:52 +0200 Subject: Don't crash on HEADERS frames with PRIORITY flag set --- src/gun_http2.erl | 10 ++--- test/rfc7540_SUITE.erl | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 test/rfc7540_SUITE.erl diff --git a/src/gun_http2.erl b/src/gun_http2.erl index edbc7c0..6176fe3 100644 --- a/src/gun_http2.erl +++ b/src/gun_http2.erl @@ -188,11 +188,11 @@ frame({headers, StreamID, IsFin, head_fin, HeaderBlock}, %% @todo HEADERS frame starting a headers block. Enter continuation mode. %frame(State, {headers, StreamID, IsFin, head_nofin, HeaderBlockFragment}) -> % State#http2_state{parse_state={continuation, StreamID, IsFin, HeaderBlockFragment}}; -%% @todo Single HEADERS frame headers block with priority. -%frame(State, {headers, StreamID, IsFin, head_fin, -% _IsExclusive, _DepStreamID, _Weight, HeaderBlock}) -> -% %% @todo Handle priority. -% stream_init(State, StreamID, IsFin, HeaderBlock); +%% Single HEADERS frame headers block with priority. +frame({headers, StreamID, IsFin, head_fin, + _IsExclusive, _DepStreamID, _Weight, HeaderBlock}, State) -> + %% @todo Handle priority. + frame({headers, StreamID, IsFin, head_fin, HeaderBlock}, State); %% @todo HEADERS frame starting a headers block. Enter continuation mode. %frame(State, {headers, StreamID, IsFin, head_nofin, % _IsExclusive, _DepStreamID, _Weight, HeaderBlockFragment}) -> diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl new file mode 100644 index 0000000..10084da --- /dev/null +++ b/test/rfc7540_SUITE.erl @@ -0,0 +1,100 @@ +%% Copyright (c) 2018, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(rfc7540_SUITE). +-compile(export_all). +-compile(nowarn_export_all). + +-import(ct_helper, [doc/1]). + +all() -> + ct_helper:all(?MODULE). + +%% Server helpers. + +do_origin_start(Fun) -> + Self = self(), + Pid = spawn_link(fun() -> do_origin_init_tcp(Self, Fun) end), + Port = do_receive(Pid), + {ok, Pid, Port}. + +do_origin_init_tcp(Parent, Fun) -> + {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), + {ok, {_, Port}} = inet:sockname(ListenSocket), + Parent ! {self(), Port}, + {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), + do_handshake(ClientSocket, gen_tcp), + Fun(Parent, ClientSocket, gen_tcp). + +do_handshake(Socket, Transport) -> + %% Send a valid preface. + ok = Transport:send(Socket, cow_http2:settings(#{})), + %% Receive the fixed sequence from the preface. + Preface = <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>, + {ok, Preface} = Transport:recv(Socket, byte_size(Preface), 5000), + %% Receive the SETTINGS from the preface. + {ok, <>} = Transport:recv(Socket, 3, 1000), + {ok, <<4:8, 0:40, _:Len/binary>>} = Transport:recv(Socket, 6 + Len, 1000), + %% Send the SETTINGS ack. + ok = Transport:send(Socket, cow_http2:settings_ack()), + %% Receive the SETTINGS ack. + {ok, <<0:24, 4:8, 1:8, 0:32>>} = Transport:recv(Socket, 9, 1000), + ok. + +do_receive(Pid) -> + do_receive(Pid, 1000). + +do_receive(Pid, Timeout) -> + receive + {Pid, Msg} -> + Msg + after Timeout -> + error(timeout) + end. + +%% Tests. + +headers_priority_flag(_) -> + doc("HEADERS frames may include a PRIORITY flag indicating " + "that stream dependency information is attached. (RFC7540 6.2)"), + {ok, _, Port} = do_origin_start(fun(_, Socket, Transport) -> + %% Receive a HEADERS frame. + {ok, <<_:24, 1:8, _:8, 1:32>>} = Transport:recv(Socket, 9, 1000), + %% Send a HEADERS frame with PRIORITY back. + {HeadersBlock, _} = cow_hpack:encode([ + {<<":status">>, <<"200">>} + ]), + Len = iolist_size(HeadersBlock) + 5, + ok = Transport:send(Socket, [ + <>, %% Weight. + HeadersBlock + ]), + timer:sleep(1000) + end), + {ok, ConnPid} = gun:open("localhost", Port, #{protocols => [http2]}), + {ok, http2} = gun:await_up(ConnPid), + StreamRef = gun:get(ConnPid, "/"), + {response, fin, 200, _} = gun:await(ConnPid, StreamRef), + gun:close(ConnPid). -- cgit v1.2.3