From 7ff9e963b89c50f29a33e3e72bbea16d0f26affa Mon Sep 17 00:00:00 2001 From: Fredrik Enestad Date: Tue, 22 Jan 2019 17:50:40 +0100 Subject: Fallback to host header if authority is missing --- src/cowboy_http2.erl | 17 ++++++++++++++--- test/rfc7540_SUITE.erl | 25 ++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl index 5070bd4..d6f80e6 100644 --- a/src/cowboy_http2.erl +++ b/src/cowboy_http2.erl @@ -315,9 +315,20 @@ headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders=#{method := <<"TRACE">>}, _) -> early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501, 'The TRACE method is currently not implemented. (RFC7231 4.3.8)'); -headers_frame(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert, proxy_header=ProxyHeader}, - StreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme, - authority := Authority, path := PathWithQs}, BodyLen) -> +headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) -> + headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority); +headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen) -> + case lists:keyfind(<<"host">>, 1, Headers) of + {_, Authority} -> + headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority); + _ -> + reset_stream(State, StreamID, {stream_error, protocol_error, + 'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'}) + end. + +headers_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert, proxy_header=ProxyHeader}, + StreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs}, + BodyLen, Authority) -> try cow_http_hd:parse_host(Authority) of {Host, Port0} -> Port = ensure_port(Scheme, Port0), diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index be30b71..47c213d 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -3746,7 +3746,7 @@ reject_many_pseudo_header_scheme(Config) -> ok. reject_missing_pseudo_header_authority(Config) -> - doc("A request without an authority component must be rejected " + doc("A request without an authority or host component must be rejected " "with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"), {ok, Socket} = do_handshake(Config), %% Send a HEADERS frame without an :authority pseudo-header. @@ -3760,6 +3760,29 @@ reject_missing_pseudo_header_authority(Config) -> {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000), ok. +accept_host_header_on_missing_pseudo_header_authority(Config) -> + doc("A request without an authority but with a host header must be accepted. " + "(RFC7540 8.1.2.3, RFC7540 8.1.3)"), + {ok, Socket} = do_handshake(Config), + %% Send a HEADERS frame with host header and without an :authority pseudo-header. + {HeadersBlock, _} = cow_hpack:encode([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":path">>, <<"/">>}, + {<<"host">>, <<"localhost">>} + ]), + ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)), + %% Receive a 200 response. + {ok, << Len:24, 1:8, _:8, _:32 >>} = gen_tcp:recv(Socket, 9, 6000), + {ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len, 6000), + {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock), + {_, <<"200">>} = lists:keyfind(<<":status">>, 1, RespHeaders), + ok. + +%% When both :authority and host headers are received, the current behavior +%% is to favor :authority and ignore the host header. The specification does +%% not describe the correct behavior to follow in that case. + reject_many_pseudo_header_authority(Config) -> doc("A request containing more than one authority component must be rejected " "with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"), -- cgit v1.2.3