Skip to content

Commit 13aabc4

Browse files
Prevent connection reuse when writing a chunked or fixed length body fails.
1 parent 7a607a4 commit 13aabc4

3 files changed

Lines changed: 27 additions & 0 deletions

File tree

‎lib/protocol/http1/connection.rb‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,9 @@ def write_fixed_length_body(body, length, head)
710710
end
711711
end
712712
rescue => error
713+
# The body may have been partially written, so the connection can't be reused, even if the caller swallows the error:
714+
@persistent = false
715+
713716
raise
714717
ensure
715718
# Close the body after the response is fully flushed, so that completion callbacks run after the client has received the response:
@@ -760,6 +763,9 @@ def write_chunked_body(body, head, trailer = nil)
760763
flush_stream
761764
end
762765
rescue => error
766+
# The body may have been partially written, so the connection can't be reused, even if the caller swallows the error:
767+
@persistent = false
768+
763769
raise
764770
ensure
765771
# Close the body after the complete chunked response (including terminal chunk) is flushed, so that completion callbacks don't block the client from seeing the response as complete:

‎releases.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- Validate trailer fields in `write_chunked_body` against the header policy, so framing fields such as `content-length` and `transfer-encoding` are rejected with `Protocol::HTTP::InvalidTrailerError` rather than written to the wire, regardless of the trailer collection type.
6+
- Mark the connection as non-persistent when `write_chunked_body` or `write_fixed_length_body` fails, so a connection with a partially written body is never reused, even if the caller swallows the error.
67

78
## v0.41.0
89

‎test/protocol/http1/connection.rb‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,20 @@
523523
# The valid field preceding the invalid one must not have been written either:
524524
expect(written).to be == "transfer-encoding: chunked\r\n\r\n5\r\nHello\r\n5\r\nWorld\r\n"
525525
end
526+
527+
it "prevents the connection from being reused" do
528+
# As a server which has finished reading the request:
529+
server.receive_end_stream!
530+
expect(server).to be(:half_closed_remote?)
531+
532+
expect do
533+
server.write_chunked_body(body, false, {"content-length" => "5"})
534+
end.to raise_exception(Protocol::HTTP::InvalidTrailerError)
535+
536+
# The body was partially written, so even if the error is swallowed, the connection must not return to the idle state:
537+
expect(server.persistent).to be == false
538+
expect(server).to be(:closed?)
539+
end
526540
end
527541

528542
with "HEAD request" do
@@ -564,6 +578,9 @@
564578
expect do
565579
server.write_fixed_length_body(body, 100, false)
566580
end.to raise_exception(Protocol::HTTP1::ContentLengthError)
581+
582+
# The body was partially written, so the connection can't be reused:
583+
expect(server.persistent).to be == false
567584
end
568585
end
569586

@@ -572,6 +589,9 @@
572589
expect do
573590
server.write_fixed_length_body(body, 1, false)
574591
end.to raise_exception(Protocol::HTTP1::ContentLengthError)
592+
593+
# The body was partially written, so the connection can't be reused:
594+
expect(server.persistent).to be == false
575595
end
576596
end
577597

0 commit comments

Comments
 (0)