(https://github.com/ruby/net-http/pull/348)
The current implementation has a problem with the following case:
```ruby
print("HTTP/1.1 200 OK\r\n")
print("Content-Length: 5\r\n")
print(" \t\r\n")
print("X-After: value\r\n")
print("\r\n")
print("hello")
```
The `" \t\r\n"` line should be processed as obs-fold in RFC 9112:
https://www.rfc-editor.org/rfc/rfc9112.html#section-5.2
> obs-fold = OWS CRLF RWS
> ; obsolete line folding
>
> A user agent that receives an obs-fold in a response message that
> is not within a "message/http" container MUST replace each received
> obs-fold with one or more SP octets prior to interpreting the field
> value.
But it's processed as the header part end:
Header:
```text
HTTP/1.1 200 OK
Content-Length: 5
```
Body:
```text
X-After: value
hello
```
It must be processed as the following (whitespace only obs-fold is
ignored):
Header:
```text
HTTP/1.1 200 OK
Content-Length: 5
X-After: value
```
Body:
```text
hello
```
https://github.com/ruby/net-http/commit/6e2bc82919
Once chunked is not the final transfer coding, read_body_0 fell through
to Content-Length and framed the body by it, which leaves the client and
the server disagreeing about where the response ends.
See RFC 9112 Section 6.3:
https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3
If a message is received with both a Transfer-Encoding and a
Content-Length header field, the Transfer-Encoding overrides the
Content-Length.
https://github.com/ruby/net-http/commit/9fb5b93b52
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Net::HTTPHeader#chunked? matched the token anywhere in
Transfer-Encoding, so a response carrying `chunked, gzip` was
chunk-framed and the bytes after the terminating chunk were left
unread on the connection.
See RFC 9112 Section 6.3:
https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3
If a Transfer-Encoding header field is present in a response and
the chunked transfer coding is not the final encoding, the message
body length is determined by reading the connection until it is
closed by the server.
https://github.com/ruby/net-http/commit/e70762aaf9
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(https://github.com/ruby/net-http/pull/324)
`Content-Length` value must have only digits.
See also RFC 9110 Section 8.6:
https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6
Content-Length = 1*DIGIT
The current implementation accepts garbage before/after digits such as
`abc5` and `5abc`.
This rejects these invalid values.
But this accepts same values in one `Content-Length`
Content-Length: 5, 5
and in multiple `Content-Length`es:
Content-Length: 5
Content-Length: 5
This is for the following part in the RFC:
https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-13
> Likewise, a sender MUST NOT forward a message with a Content-Length
> header field value that does not match the ABNF above, with one
> exception: a recipient of a Content-Length header field value
> consisting of the same decimal value repeated as a comma-separated
> list (e.g, "Content-Length: 42, 42") MAY either reject the message
> as invalid or replace that invalid field value with a single
> instance of the decimal value, since this likely indicates that a
> duplicate was generated or combined by an upstream message
> processor.
https://github.com/ruby/net-http/commit/23e859e92e
A peer that keeps sending 1xx responses keeps the client in the read
loop of transport_request indefinitely, since the per-response header
limit resets for each response. Cap the count at 100, the same value
CPython's http.client uses.
https://github.com/ruby/net-http/commit/39cf5f648a
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The response header cap added by #219 applies only once a complete line
has arrived, so a server that never sends a line terminator can still
grow the read buffer without bound, as reported in #315. Read the
status line, header lines, chunk-size lines and chunk trailers through
net-protocol's readuntil(limit:) so they fail with
Net::HTTPBadResponse at MAX_RESPONSE_HEADER_LENGTH, and require the
net-protocol that provides it.
The cumulative header budget stays separate from the per-line limit so
that exhausting the total still reports 'response header too large'
rather than blaming a line that is not long.
https://github.com/ruby/net-http/issues/315https://github.com/ruby/net-http/commit/132c8cc1b1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes https://github.com/ruby/net-http/issues/202
As of June 2026, [RFC 10008][] is a proposed standard.
From the RFC Abstract:
> This specification defines the QUERY method for HTTP. A QUERY requests
> that the request target process the enclosed content in a safe and
> idempotent manner and then respond with the result of that processing.
> This is similar to POST requests, but QUERY requests can be
> automatically repeated or restarted without concern for partial state
> changes.
This commit proposes support for the HTTP Query method by way of the
`Net::HTTP::Query` class along with the corresponding `#query` and
`#request_query` methods.
[RFC 10008]: https://www.rfc-editor.org/rfc/rfc10008.htmlhttps://github.com/ruby/net-http/commit/76b11b3a26
each_response_header read header lines until the blank separator with no
bound on their total size, so a server could exhaust client memory by
sending a large header block. Cap the cumulative size at 1 MiB and raise
Net::HTTPBadResponse once it is exceeded.
https://github.com/ruby/net-http/commit/ebe8f3876f
Co-authored-by: Yusuke Endoh <mame@ruby-lang.org>
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The length limits only ran in initialize_http_header, which responses
bypass: each_response_header builds the response through add_field and
set_field, so an oversized response header field was never bounded. Check
the field value length in set_field and append_field_value, and fold the
key length check into validate_field_name so set_field and
initialize_http_header share one place for validating field names.
https://github.com/ruby/net-http/commit/ab084fdc0c
Co-authored-by: Yusuke Endoh <mame@ruby-lang.org>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CR/LF check via Regexp#match? requires a String, but the per-part
content type was previously interpolated through to_s and accepted any
object such as a Symbol. Coerce it first to keep that behavior.
https://github.com/ruby/net-http/commit/c07d6581ce
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Field values and the request line are already validated against CR/LF,
but field names were interpolated into the request as-is, allowing
header injection via the key. Validate names in set_field and
initialize_http_header, which cover all paths into @header with a
user-supplied key.
https://github.com/ruby/net-http/commit/4f13ea5047
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Freeze `Net::HTTP::SSL_IVNAMES`, `Net::HTTPResponse::CODE_CLASS_TO_OBJ`
and `Net::HTTPResponse::CODE_TO_OBJ` to improve Ractor compatibility.
This change allows the following code to work:
Ractor.new {
uri = URI.parse('http://example.com')
http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = nil
http.read_timeout = nil
http.get('/index.html')
}
https://github.com/ruby/net-http/commit/9f0f5e4b4d
In the existing implementation, `main_type` and `sub_type` would end up
being called multiple times potentially.
Instead of doing that, save the result so it can be re-used.
https://github.com/ruby/net-http/commit/179976f7ea