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
26 changes: 25 additions & 1 deletion lib/mint/http1.ex
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,27 @@ defmodule Mint.HTTP1 do
{:ok, conn, responses}
end

# Informational (1xx) responses have no body and must not finalize the
# request; the final response follows on the same request ref. Reset the
# request's response-side fields and continue parsing without popping it.
defp decode_body(:informational, conn, data, _request_ref, responses) do
request = %{
conn.request
| state: :status,
version: nil,
status: nil,
headers_buffer: [],
data_buffer: [],
content_length: nil,
connection: [],
transfer_encoding: [],
body: nil
}

conn = %{conn | request: request, buffer: ""}
decode(:status, conn, data, responses)
end

defp decode_body(:single, conn, data, request_ref, responses) do
{conn, responses} = add_body(conn, data, responses)
conn = request_done(conn)
Expand Down Expand Up @@ -981,7 +1002,10 @@ defmodule Mint.HTTP1 do
status == 101 ->
{:ok, :single}

method == "HEAD" or status in 100..199 or status in [204, 304] ->
status in 100..199 ->
{:ok, :informational}

method == "HEAD" or status in [204, 304] ->
{:ok, :none}

# method == "CONNECT" and status in 200..299 -> nil
Expand Down
78 changes: 78 additions & 0 deletions test/mint/http1/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,84 @@ defmodule Mint.HTTP1Test do
assert conn.buffer == "XXX"
end

test "100 Continue informational response followed by final response", %{conn: conn} do
{:ok, conn, ref} = HTTP1.request(conn, "POST", "/", [{"expect", "100-continue"}], "hello")

response =
"HTTP/1.1 100 Continue\r\n\r\n" <>
"HTTP/1.1 201 Created\r\ncontent-length: 2\r\n\r\nok"

assert {:ok, conn,
[
{:status, ^ref, 100},
{:headers, ^ref, []},
{:status, ^ref, 201},
{:headers, ^ref, [{"content-length", "2"}]},
{:data, ^ref, "ok"},
{:done, ^ref}
]} = HTTP1.stream(conn, {:tcp, conn.socket, response})

assert HTTP1.open?(conn)
end

test "103 Early Hints informational response followed by final response", %{conn: conn} do
{:ok, conn, ref} = HTTP1.request(conn, "GET", "/", [], nil)

response =
"HTTP/1.1 103 Early Hints\r\nlink: </style.css>; rel=preload\r\n\r\n" <>
"HTTP/1.1 200 OK\r\ncontent-length: 2\r\n\r\nok"

assert {:ok, _conn,
[
{:status, ^ref, 103},
{:headers, ^ref, [{"link", "</style.css>; rel=preload"}]},
{:status, ^ref, 200},
{:headers, ^ref, [{"content-length", "2"}]},
{:data, ^ref, "ok"},
{:done, ^ref}
]} = HTTP1.stream(conn, {:tcp, conn.socket, response})
end

test "multiple informational responses before final response", %{conn: conn} do
{:ok, conn, ref} = HTTP1.request(conn, "POST", "/", [{"expect", "100-continue"}], "x")

response =
"HTTP/1.1 100 Continue\r\n\r\n" <>
"HTTP/1.1 103 Early Hints\r\nlink: </a>; rel=preload\r\n\r\n" <>
"HTTP/1.1 200 OK\r\ncontent-length: 2\r\n\r\nok"

assert {:ok, _conn,
[
{:status, ^ref, 100},
{:headers, ^ref, []},
{:status, ^ref, 103},
{:headers, ^ref, [{"link", "</a>; rel=preload"}]},
{:status, ^ref, 200},
{:headers, ^ref, [{"content-length", "2"}]},
{:data, ^ref, "ok"},
{:done, ^ref}
]} = HTTP1.stream(conn, {:tcp, conn.socket, response})
end

test "informational response split across multiple TCP messages", %{conn: conn} do
{:ok, conn, ref} = HTTP1.request(conn, "POST", "/", [{"expect", "100-continue"}], "x")

assert {:ok, conn, [{:status, ^ref, 100}, {:headers, ^ref, []}]} =
HTTP1.stream(conn, {:tcp, conn.socket, "HTTP/1.1 100 Continue\r\n\r\n"})

assert {:ok, _conn,
[
{:status, ^ref, 201},
{:headers, ^ref, [{"content-length", "2"}]},
{:data, ^ref, "ok"},
{:done, ^ref}
]} =
HTTP1.stream(
conn,
{:tcp, conn.socket, "HTTP/1.1 201 Created\r\ncontent-length: 2\r\n\r\nok"}
)
end

test "body following a 101 switching-protocols", %{conn: conn} do
{:ok, conn, ref} = HTTP1.request(conn, "GET", "/socket/websocket", [], nil)

Expand Down
Loading