Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/mint/http1.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,16 @@ defmodule Mint.HTTP1 do
{:ok, request}
end

# A successful CONNECT switches the connection to tunnel mode, so the
# response's HTTP version and Connection headers no longer determine the
# lifetime of the underlying socket. In particular, HTTP/1.0 responses are
# otherwise treated as non-persistent and would close the newly-established
# tunnel before the caller can use it.
defp request_done(%{request: %{method: "CONNECT", status: status}} = conn)
when status in 200..299 do
pop_request(conn)
end

defp request_done(%{request: request} = conn) do
conn = pop_request(conn)

Expand Down
12 changes: 12 additions & 0 deletions test/mint/http1/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ defmodule Mint.HTTP1Test do
assert conn.buffer == "XXX"
end

test "HTTP/1.0 2xx response to CONNECT leaves the tunnel socket open", %{conn: conn} do
{:ok, conn, ref} = HTTP1.request(conn, "CONNECT", "example.com:443", [], nil)

assert {:ok, conn, [_status, _headers, {:done, ^ref}]} =
HTTP1.stream(
conn,
{:tcp, conn.socket, "HTTP/1.0 200 Connection established\r\n\r\n"}
)

assert HTTP1.open?(conn)
end

test "content-length is ignored in 2xx response to CONNECT request", %{conn: conn} do
{:ok, conn, ref} = HTTP1.request(conn, "CONNECT", "example.com:443", [], nil)
response = "HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n"
Expand Down
20 changes: 20 additions & 0 deletions test/mint/tunnel_proxy_connect_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ defmodule Mint.TunnelProxyConnectTest do
assert merge_body(rest, request) == "hello"
end

test "tunnels through a proxy that sends an HTTP/1.0 CONNECT response" do
{origin_port, _origin_ref} = start_tls_origin()

{proxy_port, _proxy_ref} =
start_connect_proxy(
"HTTP/1.0 200 Connection established\r\nProxy-agent: tinyproxy/1.11.1\r\n\r\n"
)

assert {:ok, conn} =
HTTP.connect(:https, "localhost", origin_port,
proxy: {:http, "localhost", proxy_port, []},
transport_opts: [verify: :verify_none]
)

assert {:ok, conn, request} = HTTP.request(conn, "GET", "/", [], nil)
assert {:ok, _conn, responses} = receive_stream(conn)
assert [{:status, ^request, 200}, {:headers, ^request, _headers} | rest] = responses
assert merge_body(rest, request) == "hello"
end

test "a proxy that stalls the CONNECT response yields a tunnel timeout error" do
proxy_port = start_silent_proxy()

Expand Down