aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-09-26 16:05:52 +0200
committerLoïc Hoguin <[email protected]>2018-09-26 16:05:52 +0200
commita49200eef5681a21b47b9398804021b340486db8 (patch)
tree53d40bf48a276f1ca0a6a2de513bd65aa2f58611
parent679e3bf1c1dcfeefd405d4edaa060b8350c9a4cf (diff)
downloadgun-a49200eef5681a21b47b9398804021b340486db8.tar.gz
gun-a49200eef5681a21b47b9398804021b340486db8.tar.bz2
gun-a49200eef5681a21b47b9398804021b340486db8.zip
Don't crash on HEADERS frames with PRIORITY flag set
-rw-r--r--src/gun_http2.erl10
-rw-r--r--test/rfc7540_SUITE.erl100
2 files changed, 105 insertions, 5 deletions
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 <[email protected]>
+%%
+%% 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, <<Len:24>>} = 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, [
+ <<Len:24, 1:8,
+ 0:2, %% Undefined.
+ 1:1, %% PRIORITY.
+ 0:1, %% Undefined.
+ 0:1, %% PADDED.
+ 1:1, %% END_HEADERS.
+ 0:1, %% Undefined.
+ 1:1, %% END_STREAM.
+ 0:1, 1:31,
+ 1:1, %% Exclusive?
+ 3:31, %% Stream dependency.
+ 42:8 >>, %% 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).