improvement(http1): robustness of request header parsing in req/header.go
Description
While reviewing the HTTP/1.1 request header parsing code in
pkg/protocol/http1/req/header.go, I found several places where
defensive checks or small adjustments could make the code more robust
and RFC-compliant.
Issues Identified
1. parse() sets RawHeaders before confirming ReadRawHeaders succeeded
parse() line 131 calls h.SetRawHeaders(rawHeaders) before checking
if ext.ReadRawHeaders() returned an error. When the read fails, it
unnecessarily mutates header state before bailing out.
https://github.com/cloudwego/hertz/blob/main/pkg/protocol/http1/req/header.go#L130-L13 6
2. Missing empty-buffer guard in parseFirstLine()
When buf is empty, the method-char validation loop (for i, c := range buf)
becomes a no-op and falls through. An explicit len(buf) == 0 guard makes
the control flow immediately obvious and is a standard defensive practice.
https://github.com/cloudwego/hertz/blob/main/pkg/protocol/http1/req/header.go#L163-L17 6
3. Degenerate n=0 path in ReadHeaderWithLimit()
When r.Len() returns 0, n becomes 0, and the next
tryReadWithLimit(h, r, 0, ...) peeks 0 bytes (degenerate path).
Setting n=1 when r.Len() returns 0 keeps the reader blocking for
incoming data, which is the clearer and more correct behavior.
https://github.com/cloudwego/hertz/blob/main/pkg/protocol/http1/req/header.go#L90
4. Conflicting Content-Length values silently accepted
RFC 7230 Section 3.3.2 requires that messages with multiple Content-Length
headers with different values MUST be rejected as invalid. Currently
Hertz silently overwrites the first value with the second (last-one-wins),
which is non-compliant and could mask bugs in upstream proxies or clients.
https://github.com/cloudwego/hertz/blob/main/pkg/protocol/http1/req/header.go#L260-L27 3
Proposed Changes
parse(): Moveh.SetRawHeaders(rawHeaders)to after the error checkparseFirstLine(): Addif len(buf) == 0 { return 0, err }before the loopReadHeaderWithLimit(): Addif n == 0 { n = 1 }aftern = r.Len()parseHeaders(): After parsing a duplicate Content-Length, compare with
the existing value. If different, storeerrand set contentLength to -2
(using the existing error-accumulation pattern inparseHeaders).
Identical duplicate values are accepted (RFC compliant).
Impact
- Changes 1–3 are pure defensive improvements with zero behavioral change
for valid inputs - Change 4 introduces a behavioral change: only HTTP/1.1 requests that are
already invalid per RFC 7230 (conflicting Content-Length) are rejected
instead of silently accepted
Hertz Version
v0.10.3
Additional Context
The same issues exist in the response header parser at
pkg/protocol/http1/resp/header.go (particularly Content-Length conflict
detection and the n=0 path), which could be addressed in a follow-up PR.
I have a working branch ready and would like to open a PR if the
maintainers are interested.
Source: cloudwego/hertz