From 2eaf28d3876c1ebcbf31d843e77f809e0c25aefb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 25 Mar 2020 11:22:59 +0100 Subject: Fix host/:authority header when connecting to an IPv6 address --- src/gun_http.erl | 3 ++- test/gun_test.erl | 3 ++- test/rfc7230_SUITE.erl | 13 +++++++++++++ test/rfc7540_SUITE.erl | 21 +++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/gun_http.erl b/src/gun_http.erl index 401e23a..dd48c42 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -605,7 +605,8 @@ send_request(State=#http_state{socket=Socket, transport=Transport, version=Versi host_header(Transport, Host0, Port) -> Host = case Host0 of {local, _SocketPath} -> <<>>; - Tuple when is_tuple(Tuple) -> inet:ntoa(Tuple); + Tuple when tuple_size(Tuple) =:= 8 -> [$[, inet:ntoa(Tuple), $]]; %% IPv6. + Tuple when tuple_size(Tuple) =:= 4 -> inet:ntoa(Tuple); %% IPv4. Atom when is_atom(Atom) -> atom_to_list(Atom); _ -> Host0 end, diff --git a/test/gun_test.erl b/test/gun_test.erl index bb162f4..c6cdd8c 100644 --- a/test/gun_test.erl +++ b/test/gun_test.erl @@ -45,7 +45,8 @@ init_origin(Transport, Protocol, Fun) -> {ok, Pid, Port}. init_origin(Parent, tcp, Protocol, Fun) -> - {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), + %% We setup the socket for both IPv4 and IPv6. + {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}, inet6]), {ok, {_, Port}} = inet:sockname(ListenSocket), Parent ! {self(), Port}, {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), diff --git a/test/rfc7230_SUITE.erl b/test/rfc7230_SUITE.erl index da7b426..d6afb2c 100644 --- a/test/rfc7230_SUITE.erl +++ b/test/rfc7230_SUITE.erl @@ -34,6 +34,19 @@ host_default_port_https(_) -> doc("The default port for https should not be sent in the host header. (RFC7230 2.7.2)"), do_host_port(tls, 443, <<>>). +host_ipv6(_) -> + doc("When connecting to a server using an IPv6 address the host " + "header must wrap the address with brackets. (RFC7230 5.4, RFC3986 3.2.2)"), + {ok, OriginPid, OriginPort} = init_origin(tcp, http), + {ok, ConnPid} = gun:open({0,0,0,0,0,0,0,1}, OriginPort, #{transport => tcp}), + {ok, http} = gun:await_up(ConnPid), + _ = gun:get(ConnPid, "/"), + handshake_completed = receive_from(OriginPid), + Data = receive_from(OriginPid), + Lines = binary:split(Data, <<"\r\n">>, [global]), + [<<"host: [::1]", _/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], + gun:close(ConnPid). + host_other_port_http(_) -> doc("Non-default ports for http must be sent in the host header. (RFC7230 2.7.1)"), do_host_port(tcp, 443, <<":443">>). diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index 44794ef..56447a3 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -35,6 +35,27 @@ authority_default_port_https(_) -> "the :authority pseudo-header. (RFC7540 3, RFC7230 2.7.2)"), do_authority_port(tls, 443, <<>>). +authority_ipv6(_) -> + doc("When connecting to a server using an IPv6 address the :authority " + "pseudo-header must wrap the address with brackets. (RFC7540 8.1.2.3, RFC3986 3.2.2)"), + {ok, OriginPid, OriginPort} = init_origin(tcp, http2, fun(Parent, Socket, Transport) -> + %% Receive the HEADERS frame and send the headers decoded. + {ok, <>} = Transport:recv(Socket, 9, 1000), + {ok, ReqHeadersBlock} = Transport:recv(Socket, Len, 1000), + {ReqHeaders, _} = cow_hpack:decode(ReqHeadersBlock), + Parent ! {self(), ReqHeaders} + end), + {ok, ConnPid} = gun:open({0,0,0,0,0,0,0,1}, OriginPort, #{ + transport => tcp, + protocols => [http2] + }), + {ok, http2} = gun:await_up(ConnPid), + handshake_completed = receive_from(OriginPid), + _ = gun:get(ConnPid, "/"), + ReqHeaders = receive_from(OriginPid), + {_, <<"[::1]", _/bits>>} = lists:keyfind(<<":authority">>, 1, ReqHeaders), + gun:close(ConnPid). + authority_other_port_http(_) -> doc("Non-default ports for http must be sent in " "the :authority pseudo-header. (RFC7540 3, RFC7230 2.7.1)"), -- cgit v1.2.3