%% Copyright (c) 2018-2023, 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(rfc7231_SUITE). -compile(export_all). -compile(nowarn_export_all). -import(ct_helper, [doc/1]). -import(gun_test, [init_origin/1]). -import(gun_test, [init_origin/2]). -import(gun_test, [receive_from/1]). -import(gun_test, [receive_from/2]). all() -> ct_helper:all(?MODULE). %% Proxy helpers. do_proxy_start(Transport) -> do_proxy_start(Transport, 200, []). do_proxy_start(Transport, Status) -> do_proxy_start(Transport, Status, []). do_proxy_start(Transport, Status, ConnectRespHeaders) -> do_proxy_start(Transport, Status, ConnectRespHeaders, 0). do_proxy_start(Transport, Status, ConnectRespHeaders, Delay) -> do_proxy_start(Transport, Status, ConnectRespHeaders, Delay, <<"HTTP/1.1">>). do_proxy_start(Transport0, Status, ConnectRespHeaders, Delay, ConnectRespVersion) -> Transport = case Transport0 of tcp -> gun_tcp; tls -> gun_tls end, Self = self(), Pid = spawn_link(fun() -> do_proxy_init(Self, Transport, Status, ConnectRespHeaders, Delay, ConnectRespVersion) end), Port = receive_from(Pid), {ok, Pid, Port}. do_proxy_init(Parent, Transport, Status, ConnectRespHeaders, Delay, ConnectRespVersion) -> {ok, ListenSocket} = case Transport of gun_tcp -> gen_tcp:listen(0, [binary, {active, false}]); gun_tls -> Opts = ct_helper:get_certs_from_ets(), ssl:listen(0, [binary, {active, false}, {verify, verify_none}, {fail_if_no_peer_cert, false}|Opts]) end, {ok, {_, Port}} = Transport:sockname(ListenSocket), Parent ! {self(), Port}, {ok, ClientSocket} = case Transport of gun_tcp -> gen_tcp:accept(ListenSocket, infinity); gun_tls -> {ok, ClientSocket0} = ssl:transport_accept(ListenSocket, infinity), {ok, ClientSocket1} = ssl:handshake(ClientSocket0, infinity), {ok, ClientSocket1} end, {ok, Data} = case Transport of gun_tcp -> gen_tcp:recv(ClientSocket, 0, infinity); gun_tls -> ssl:recv(ClientSocket, 0, infinity) end, {Method= <<"CONNECT">>, Authority, Version, Rest} = cow_http:parse_request_line(Data), {Headers, <<>>} = cow_http:parse_headers(Rest), timer:sleep(Delay), Parent ! {self(), {request, Method, Authority, Version, Headers}}, {OriginHost, OriginPort} = cow_http_hd:parse_host(Authority), ok = Transport:send(ClientSocket, [ ConnectRespVersion, <<" ">>, integer_to_binary(Status), <<" Reason phrase\r\n">>, cow_http:headers(ConnectRespHeaders), <<"\r\n">> ]), if Status >= 200, Status < 300 -> {ok, OriginSocket} = gen_tcp:connect( binary_to_list(OriginHost), OriginPort, [binary, {active, false}]), Transport:setopts(ClientSocket, [{active, true}]), inet:setopts(OriginSocket, [{active, true}]), do_proxy_loop(Transport, ClientSocket, OriginSocket); true -> timer:sleep(infinity) end. do_proxy_loop(Transport, ClientSocket, OriginSocket) -> {OK, _, _} = Transport:messages(), receive {OK, ClientSocket, Data} -> case gen_tcp:send(OriginSocket, Data) of ok -> do_proxy_loop(Transport, ClientSocket, OriginSocket); {error, _} -> ok end; {tcp, OriginSocket, Data} -> case Transport:send(ClientSocket, Data) of ok -> do_proxy_loop(Transport, ClientSocket, OriginSocket); {error, _} -> ok end; %% Wait forever when a connection gets closed. We will exit with the test process. {tcp_closed, _} -> timer:sleep(infinity); {ssl_closed, _} -> timer:sleep(infinity); Msg -> error(Msg) end. %% Tests. connect_http(_) -> doc("CONNECT can be used to establish a TCP connection " "to an HTTP/1.1 server via an HTTP proxy. (RFC7231 4.3.6)"), do_connect_http(<<"http">>, tcp, tcp). connect_https(_) -> doc("CONNECT can be used to establish a TLS connection " "to an HTTP/1.1 server via an HTTP proxy. (RFC7231 4.3.6)"), do_connect_http(<<"https">>, tls, tcp). connect_http_over_https_proxy(_) -> doc("CONNECT can be used to establish a TCP connection " "to an HTTP/1.1 server via an HTTPS proxy. (RFC7231 4.3.6)"), do_connect_http(<<"http">>, tcp, tls). connect_https_over_https_proxy(_) -> doc("CONNECT can be used to establish a TLS connection " "to an HTTP/1.1 server via an HTTPS proxy. (RFC7231 4.3.6)"), do_connect_http(<<"https">>, tls, tls). do_connect_http(OriginScheme, OriginTransport, ProxyTransport) -> {ok, OriginPid, OriginPort} = init_origin(OriginTransport, http), {ok, ProxyPid, ProxyPort} = do_proxy_start(ProxyTransport), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort, #{ transport => ProxyTransport, tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] }), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort, transport => OriginTransport, tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef), %% @todo Do we still need these handshake_completed messages? handshake_completed = receive_from(OriginPid), {up, http} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ transport := OriginTransport, protocol := http, origin_scheme := OriginScheme, origin_host := "localhost", origin_port := OriginPort, intermediaries := [#{ type := connect, host := "localhost", port := ProxyPort, transport := ProxyTransport, protocol := http }]} = gun:info(ConnPid), gun:close(ConnPid). connect_h2c(_) -> doc("CONNECT can be used to establish a TCP connection " "to an HTTP/2 server via an HTTP proxy. (RFC7231 4.3.6)"), do_connect_h2(<<"http">>, tcp, tcp). connect_h2(_) -> doc("CONNECT can be used to establish a TLS connection " "to an HTTP/2 server via an HTTP proxy. (RFC7231 4.3.6)"), do_connect_h2(<<"https">>, tls, tcp). connect_h2c_over_https_proxy(_) -> doc("CONNECT can be used to establish a TCP connection " "to an HTTP/2 server via an HTTPS proxy. (RFC7231 4.3.6)"), do_connect_h2(<<"http">>, tcp, tls). connect_h2_over_https_proxy(_) -> doc("CONNECT can be used to establish a TLS connection " "to an HTTP/2 server via an HTTPS proxy. (RFC7231 4.3.6)"), do_connect_h2(<<"https">>, tls, tls). do_connect_h2(OriginScheme, OriginTransport, ProxyTransport) -> {ok, OriginPid, OriginPort} = init_origin(OriginTransport, http2), {ok, ProxyPid, ProxyPort} = do_proxy_start(ProxyTransport), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort, #{ transport => ProxyTransport, tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] }), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort, transport => OriginTransport, tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}], protocols => [http2] }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), {up, http2} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), <<_:24, 1:8, _/bits>> = receive_from(OriginPid), #{ transport := OriginTransport, protocol := http2, origin_scheme := OriginScheme, origin_host := "localhost", origin_port := OriginPort, intermediaries := [#{ type := connect, host := "localhost", port := ProxyPort, transport := ProxyTransport, protocol := http }]} = gun:info(ConnPid), gun:close(ConnPid). connect_tcp_through_multiple_tcp_proxies(_) -> doc("CONNECT can be used to establish a TCP connection " "to an HTTP/1.1 server via a tunnel going through " "two separate HTTP proxies. (RFC7231 4.3.6)"), do_connect_through_multiple_proxies(<<"http">>, tcp, tcp). connect_tls_through_multiple_tls_proxies(_) -> doc("CONNECT can be used to establish a TLS connection " "to an HTTP/1.1 server via a tunnel going through " "two separate HTTPS proxies. (RFC7231 4.3.6)"), do_connect_through_multiple_proxies(<<"https">>, tls, tls). do_connect_through_multiple_proxies(OriginScheme, OriginTransport, ProxiesTransport) -> {ok, OriginPid, OriginPort} = init_origin(OriginTransport), {ok, Proxy1Pid, Proxy1Port} = do_proxy_start(ProxiesTransport), {ok, Proxy2Pid, Proxy2Port} = do_proxy_start(ProxiesTransport), {ok, ConnPid} = gun:open("localhost", Proxy1Port, #{ transport => ProxiesTransport, tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] }), {ok, http} = gun:await_up(ConnPid), Authority1 = iolist_to_binary(["localhost:", integer_to_binary(Proxy2Port)]), StreamRef1 = gun:connect(ConnPid, #{ host => "localhost", port => Proxy2Port, transport => ProxiesTransport, tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] }), {request, <<"CONNECT">>, Authority1, 'HTTP/1.1', _} = receive_from(Proxy1Pid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef1), {up, http} = gun:await(ConnPid, StreamRef1), Authority2 = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), StreamRef2 = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort, transport => OriginTransport, tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] }, [], #{tunnel => StreamRef1}), {request, <<"CONNECT">>, Authority2, 'HTTP/1.1', _} = receive_from(Proxy2Pid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef2), handshake_completed = receive_from(OriginPid), {up, http} = gun:await(ConnPid, StreamRef2), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef2}), Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority2/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ transport := OriginTransport, protocol := http, origin_scheme := OriginScheme, origin_host := "localhost", origin_port := OriginPort, intermediaries := [#{ type := connect, host := "localhost", port := Proxy1Port, transport := ProxiesTransport, protocol := http }, #{ type := connect, host := "localhost", port := Proxy2Port, transport := ProxiesTransport, protocol := http }]} = gun:info(ConnPid), gun:close(ConnPid). connect_delay(_) -> doc("The CONNECT response may not be immediate."), {ok, OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp, 201, [], 2000), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort, #{http_opts => #{keepalive => 1000}}), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid, 3000), {response, fin, 201, _} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), {up, http} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ transport := tcp, protocol := http, origin_scheme := <<"http">>, origin_host := "localhost", origin_port := OriginPort, intermediaries := [#{ type := connect, host := "localhost", port := ProxyPort, transport := tcp, protocol := http }]} = gun:info(ConnPid), gun:close(ConnPid). connect_response_201(_) -> doc("2xx responses to CONNECT requests indicate " "the tunnel was set up successfully. (RFC7231 4.3.6)"), {ok, OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp, 201), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 201, _} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), {up, http} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ transport := tcp, protocol := http, origin_scheme := <<"http">>, origin_host := "localhost", origin_port := OriginPort, intermediaries := [#{ type := connect, host := "localhost", port := ProxyPort, transport := tcp, protocol := http }]} = gun:info(ConnPid), gun:close(ConnPid). connect_response_302(_) -> doc("3xx responses to CONNECT requests indicate " "the tunnel was not set up. (RFC7231 4.3.6)"), do_connect_failure(302). connect_response_403(_) -> doc("4xx responses to CONNECT requests indicate " "the tunnel was not set up. (RFC7231 4.3.6)"), do_connect_failure(403). connect_response_500(_) -> doc("5xx responses to CONNECT requests indicate " "the tunnel was not set up. (RFC7231 4.3.6)"), do_connect_failure(500). do_connect_failure(Status) -> OriginPort = 33333, %% Doesn't matter because we won't try to connect. Headers = [{<<"content-length">>, <<"0">>}], {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp, Status, Headers), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, Status, Headers} = gun:await(ConnPid, StreamRef), %% We cannot do a request because the StreamRef is not a tunnel. FailedStreamRef = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), {error, {stream_error, {badstate, _}}} = gun:await(ConnPid, FailedStreamRef), #{ transport := tcp, protocol := http, origin_scheme := <<"http">>, origin_host := "localhost", origin_port := ProxyPort, intermediaries := [] } = gun:info(ConnPid), gun:close(ConnPid). connect_response_http10(_) -> doc("CONNECT can be used to establish a TCP connection " "to a server via an HTTP/1.0 proxy. (RFC7230 2.6, RFC7231 4.3.6)"), {ok, OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp, 201, [], 0, <<"HTTP/1.0">>), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 201, _} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), {up, http} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ transport := tcp, protocol := http, origin_scheme := <<"http">>, origin_host := "localhost", origin_port := OriginPort, intermediaries := [#{ type := connect, host := "localhost", port := ProxyPort, transport := tcp, protocol := http }]} = gun:info(ConnPid), gun:close(ConnPid). connect_authority_form(_) -> doc("CONNECT requests must use the authority-form. (RFC7231 4.3.6)"), {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), _StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {<<"localhost">>, OriginPort} = cow_http_hd:parse_host(Authority), gun:close(ConnPid). connect_proxy_authorization(_) -> doc("CONNECT requests may include a proxy-authorization header. (RFC7231 4.3.6)"), {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), _StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort, username => "essen", password => "myrealpasswordis" }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = receive_from(ProxyPid), {_, ProxyAuthorization} = lists:keyfind(<<"proxy-authorization">>, 1, Headers), {basic, <<"essen">>, <<"myrealpasswordis">>} = cow_http_hd:parse_proxy_authorization(ProxyAuthorization), gun:close(ConnPid). connect_request_no_transfer_encoding(_) -> doc("The payload for CONNECT requests has no defined semantics. " "The transfer-encoding header should not be sent. (RFC7231 4.3.6)"), {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), _StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = receive_from(ProxyPid), false = lists:keyfind(<<"transfer-encoding">>, 1, Headers), gun:close(ConnPid). connect_request_no_content_length(_) -> doc("The payload for CONNECT requests has no defined semantics. " "The content-length header should not be sent. (RFC7231 4.3.6)"), {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), _StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = receive_from(ProxyPid), false = lists:keyfind(<<"content-length">>, 1, Headers), gun:close(ConnPid). connect_response_ignore_transfer_encoding(_) -> doc("Clients must ignore transfer-encoding headers in responses " "to CONNECT requests. (RFC7231 4.3.6)"), {ok, OriginPid, OriginPort} = init_origin(tcp), Headers = [{<<"transfer-encoding">>, <<"chunked">>}], {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp, 200, Headers), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, Headers} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), {up, http} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], gun:close(ConnPid). connect_response_ignore_content_length(_) -> doc("Clients must ignore content-length headers in responses " "to CONNECT requests. (RFC7231 4.3.6)"), {ok, OriginPid, OriginPort} = init_origin(tcp), Headers = [{<<"content-length">>, <<"1000">>}], {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp, 200, Headers), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, Headers} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), {up, http} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied", [], #{tunnel => StreamRef}), Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], gun:close(ConnPid).