Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
against the request header block limit (`2 * max_header_bytes`). Empty
CONTINUATION frames added nothing to the block, so a client could keep one
header block open across any number of them. ([#1479])
- The HTTP/2 server rejects a request whose `:path` is not an absolute path,
or `*` for OPTIONS, as RFC 9113 §8.3.1 requires. An empty `:path` reset the
stream with `INTERNAL_ERROR` and logged a server error, and a `:path` such as
`abc` reached the handler, though HTTP/1 answers that target with 400. ([#1484])
- `serve!` answers a handler's response that cannot be sent with 500 and logs
it. Such a response has an unsupported `Transfer-Encoding`, a control
character in a header value, an invalid header name, reason phrase or
Expand Down Expand Up @@ -1459,6 +1463,7 @@ See changes for 0.9.15: this release is equivalent to 0.9.15 with [#752] reverte
[#1479]: https://github.com/JuliaWeb/HTTP.jl/issues/1479
[#1480]: https://github.com/JuliaWeb/HTTP.jl/issues/1480
[#1483]: https://github.com/JuliaWeb/HTTP.jl/issues/1483
[#1484]: https://github.com/JuliaWeb/HTTP.jl/issues/1484
[#1486]: https://github.com/JuliaWeb/HTTP.jl/issues/1486
[#1487]: https://github.com/JuliaWeb/HTTP.jl/issues/1487
[#1491]: https://github.com/JuliaWeb/HTTP.jl/issues/1491
3 changes: 3 additions & 0 deletions src/http2_server.jl
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,8 @@ function _validate_h2_request_headers!(headers::Vector{HeaderField})::Tuple{Stri
# written verbatim into an HTTP/1 request line, splitting the target
# and enabling request smuggling past path-based ACLs.
_string_contains_whitespace_byte(normalized) && throw(ProtocolError("invalid HTTP/2 :path pseudo-header: $(repr(normalized))"))
# It is an absolute path, or `*` (checked against the method below).
(startswith(normalized, '/') || normalized == "*") || throw(ProtocolError("invalid HTTP/2 :path pseudo-header: $(repr(normalized))"))
path = normalized
elseif name == ":authority"
authority === nothing || throw(ProtocolError("duplicate HTTP/2 :authority pseudo-header"))
Expand Down Expand Up @@ -1035,6 +1037,7 @@ function _validate_h2_request_headers!(headers::Vector{HeaderField})::Tuple{Stri
else
scheme === nothing && throw(ProtocolError("missing HTTP/2 :scheme pseudo-header"))
path === nothing && throw(ProtocolError("missing HTTP/2 :path pseudo-header"))
path == "*" && method != "OPTIONS" && throw(ProtocolError("HTTP/2 :path * is only valid for OPTIONS"))
end
return method::String, scheme, path, authority, out_headers
end
Expand Down
9 changes: 9 additions & 0 deletions test/http2_server_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2692,6 +2692,15 @@ end
@test_throws HT.ProtocolError HT._validate_h2_request_headers!(_h2_fields(path="/public /admin"))
@test_throws HT.ProtocolError HT._validate_h2_request_headers!(_h2_fields(path="/a\tb"))

# RFC 9113 §8.3.1: :path is an absolute path, or `*` for OPTIONS only. An
# empty :path reached the Request constructor, and "abc" was served, which
# HTTP/1 answers with 400.
@test_throws HT.ProtocolError HT._validate_h2_request_headers!(_h2_fields(path=""))
@test_throws HT.ProtocolError HT._validate_h2_request_headers!(_h2_fields(path="abc"))
@test_throws HT.ProtocolError HT._validate_h2_request_headers!(_h2_fields(path="http://example.com/"))
@test_throws HT.ProtocolError HT._validate_h2_request_headers!(_h2_fields(path="*"))
@test HT._validate_h2_request_headers!(_h2_fields(method="OPTIONS", path="*"))[3] == "*"

# ANT-2026-565042FN: :authority must pass host validation (no whitespace).
@test_throws HT.ProtocolError HT._validate_h2_request_headers!(_h2_fields(authority="allowed.example.com evil"))

Expand Down
Loading