aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http2.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_http2.erl')
-rw-r--r--src/cowboy_http2.erl57
1 files changed, 44 insertions, 13 deletions
diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl
index caa1f07..7f5d738 100644
--- a/src/cowboy_http2.erl
+++ b/src/cowboy_http2.erl
@@ -165,9 +165,10 @@ init(Parent, Ref, Socket, Transport, Opts) ->
{inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
binary() | undefined, binary()) -> ok.
init(Parent, Ref, Socket, Transport, Opts, Peer, Sock, Cert, Buffer) ->
- State = #state{parent=Parent, ref=Ref, socket=Socket,
+ State0 = #state{parent=Parent, ref=Ref, socket=Socket,
transport=Transport, opts=Opts, peer=Peer, sock=Sock, cert=Cert,
parse_state={preface, sequence, preface_timeout(Opts)}},
+ State = settings_init(State0, Opts),
preface(State),
case Buffer of
<<>> -> before_loop(State, Buffer);
@@ -188,16 +189,21 @@ init(Parent, Ref, Socket, Transport, Opts, Peer, Sock, Cert, Buffer, _Settings,
State1 = stream_handler_init(State0, 1, fin, upgrade, Req),
%% We assume that the upgrade will be applied. A stream handler
%% must not prevent the normal operations of the server.
- State = info(State1, 1, {switch_protocol, #{
+ State2 = info(State1, 1, {switch_protocol, #{
<<"connection">> => <<"Upgrade">>,
<<"upgrade">> => <<"h2c">>
}, ?MODULE, undefined}), %% @todo undefined or #{}?
+ State = settings_init(State2, Opts),
preface(State),
case Buffer of
<<>> -> before_loop(State, Buffer);
_ -> parse(State, Buffer)
end.
+settings_init(State=#state{next_settings=Settings}, Opts) ->
+ EnableConnectProtocol = maps:get(enable_connect_protocol, Opts, false),
+ State#state{next_settings=Settings#{enable_connect_protocol => EnableConnectProtocol}}.
+
preface(#state{socket=Socket, transport=Transport, next_settings=Settings}) ->
%% We send next_settings and use defaults until we get a ack.
Transport:send(Socket, cow_http2:settings(Settings)).
@@ -413,9 +419,9 @@ frame(State0=#state{socket=Socket, transport=Transport, remote_settings=Settings
State
end;
%% Ack for a previously sent SETTINGS frame.
-frame(State=#state{next_settings=_NextSettings}, settings_ack) ->
- %% @todo Apply SETTINGS that require synchronization.
- State;
+frame(State=#state{local_settings=Local0, next_settings=Next}, settings_ack) ->
+ Local = maps:merge(Local0, Next),
+ State#state{local_settings=Local, next_settings=#{}};
%% Unexpected PUSH_PROMISE frame.
frame(State, {push_promise, _, _, _, _}) ->
terminate(State, {connection_error, protocol_error,
@@ -637,9 +643,11 @@ commands(State=#state{socket=Socket, transport=Transport},
Stream=#stream{local=upgrade}, [{switch_protocol, Headers, ?MODULE, _}|Tail]) ->
Transport:send(Socket, cow_http:response(101, 'HTTP/1.1', maps:to_list(Headers))),
commands(State, Stream#stream{local=idle}, Tail);
-%% HTTP/2 has no support for the Upgrade mechanism.
-commands(State, Stream, [{switch_protocol, _Headers, _Mod, _ModState}|Tail]) ->
- %% @todo This is an error. Not sure what to do here yet.
+%% Use a different protocol within the stream (CONNECT :protocol).
+%% @todo Make sure we error out when the feature is disabled.
+commands(State0, #stream{id=StreamID}, [{switch_protocol, Headers, _Mod, _ModState}|Tail]) ->
+ State = #state{streams=Streams} = info(State0, StreamID, {headers, 200, Headers}),
+ Stream = lists:keyfind(StreamID, #stream.id, Streams),
commands(State, Stream, Tail);
commands(State, Stream=#stream{id=StreamID}, [stop|_Tail]) ->
%% @todo Do we want to run the commands after a stop?
@@ -840,8 +848,22 @@ stream_decode_init(State=#state{decode_state=DecodeState0}, StreamID, IsFin, Hea
'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'})
end.
-stream_pseudo_headers_init(State, StreamID, IsFin, Headers0) ->
+stream_pseudo_headers_init(State=#state{local_settings=LocalSettings},
+ StreamID, IsFin, Headers0) ->
+ IsExtendedConnectEnabled = maps:get(enable_connect_protocol, LocalSettings, false),
case pseudo_headers(Headers0, #{}) of
+ {ok, PseudoHeaders=#{method := <<"CONNECT">>, scheme := _,
+ authority := _, path := _, protocol := _}, Headers}
+ when IsExtendedConnectEnabled ->
+ stream_regular_headers_init(State, StreamID, IsFin, Headers, PseudoHeaders);
+ {ok, #{method := <<"CONNECT">>, scheme := _,
+ authority := _, path := _}, _}
+ when IsExtendedConnectEnabled ->
+ stream_malformed(State, StreamID,
+ 'The :protocol pseudo-header MUST be sent with an extended CONNECT. (draft_h2_websockets 4)');
+ {ok, #{protocol := _}, _} ->
+ stream_malformed(State, StreamID,
+ 'The :protocol pseudo-header is only defined for the extended CONNECT. (draft_h2_websockets 4)');
%% @todo Add clause for CONNECT requests (no scheme/path).
{ok, PseudoHeaders=#{method := <<"CONNECT">>}, _} ->
stream_early_error(State, StreamID, IsFin, 501, PseudoHeaders,
@@ -869,13 +891,15 @@ pseudo_headers([{<<":scheme">>, Scheme}|Tail], PseudoHeaders) ->
pseudo_headers([{<<":authority">>, _}|_], #{authority := _}) ->
{error, 'Multiple :authority pseudo-headers were found. (RFC7540 8.1.2.3)'};
pseudo_headers([{<<":authority">>, Authority}|Tail], PseudoHeaders) ->
- %% @todo Probably parse the authority here.
pseudo_headers(Tail, PseudoHeaders#{authority => Authority});
pseudo_headers([{<<":path">>, _}|_], #{path := _}) ->
{error, 'Multiple :path pseudo-headers were found. (RFC7540 8.1.2.3)'};
pseudo_headers([{<<":path">>, Path}|Tail], PseudoHeaders) ->
- %% @todo Probably parse the path here.
pseudo_headers(Tail, PseudoHeaders#{path => Path});
+pseudo_headers([{<<":protocol">>, _}|_], #{protocol := _}) ->
+ {error, 'Multiple :protocol pseudo-headers were found. (RFC7540 8.1.2.3)'};
+pseudo_headers([{<<":protocol">>, Protocol}|Tail], PseudoHeaders) ->
+ pseudo_headers(Tail, PseudoHeaders#{protocol => Protocol});
pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
{error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
pseudo_headers(Headers, PseudoHeaders) ->
@@ -946,7 +970,7 @@ stream_req_init(State, StreamID, IsFin, Headers, PseudoHeaders) ->
end.
stream_req_init(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert},
- StreamID, IsFin, Headers, #{method := Method, scheme := Scheme,
+ StreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme,
authority := Authority, path := PathWithQs}, BodyLength) ->
try cow_http_hd:parse_host(Authority) of
{Host, Port} ->
@@ -955,7 +979,7 @@ stream_req_init(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert},
stream_malformed(State, StreamID,
'The path component must not be empty. (RFC7540 8.1.2.3)');
{Path, Qs} ->
- Req = #{
+ Req0 = #{
ref => Ref,
pid => self(),
streamid => StreamID,
@@ -973,6 +997,13 @@ stream_req_init(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert},
has_body => IsFin =:= nofin,
body_length => BodyLength
},
+ %% We add the protocol information for extended CONNECTs.
+ Req = case PseudoHeaders of
+ #{protocol := Protocol} ->
+ Req0#{protocol => Protocol};
+ _ ->
+ Req0
+ end,
stream_handler_init(State, StreamID, IsFin, idle, Req)
catch _:_ ->
stream_malformed(State, StreamID,