#1497·hertz

improvement(http1): robustness of request header parsing in req/header.go

Author: Cholesterol12138Created May 15, 2026Updated Aug 23, 2026

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

  1. parse(): Move h.SetRawHeaders(rawHeaders) to after the error check
  2. parseFirstLine(): Add if len(buf) == 0 { return 0, err } before the loop
  3. ReadHeaderWithLimit(): Add if n == 0 { n = 1 } after n = r.Len()
  4. parseHeaders(): After parsing a duplicate Content-Length, compare with
    the existing value. If different, store err and set contentLength to -2
    (using the existing error-accumulation pattern in parseHeaders).
    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.