improvement(http1): robustness of request header parsing in req/header.go
- Dominant language
- Go
- Stars
- 7.4k
- Forks
- 643
- Avg merge
- 14h 5m
- Merged PRs (30d)
- 2
Description
## 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.
---
Contributor guide
Assessment
This issue has not been assessed yet.