Skip to content

parse Content-Length as digits with surrounding whitespace only (#1221) - #1243

Merged
pjfanning merged 2 commits into
apache:1.4.xfrom
pjfanning:content-length-1.4
Aug 27, 2026
Merged

parse Content-Length as digits with surrounding whitespace only (#1221)#1243
pjfanning merged 2 commits into
apache:1.4.xfrom
pjfanning:content-length-1.4

Conversation

@pjfanning

@pjfanning pjfanning commented Aug 27, 2026

Copy link
Copy Markdown
Member

Backport of #1221 (cherry pick 5bdfd6d) to 1.4.x.

Motivation

ContentLengthParser skips whitespace anywhere in the field value:

if (DIGIT(c)) { ... } else if (WSP(c)) recurse(ix + 1, result)

So Content-Length: 1 2 is read as 12, Content-Length: 5 5 as 55, and an empty Content-Length: as 0. The field value is 1*DIGIT surrounded by optional whitespace (RFC 9110, section 8.6 and RFC 9112, section 5). This line dates back to the original Akka HTTP code, so 1.4.x is affected exactly as main was — it is not a regression introduced on main.

The other ways two implementations could disagree about a body length are already closed here — two Content-Length headers with different values are rejected, Transfer-Encoding accepts only a single chunked, and chunked together with a Content-Length is rejected — which is what makes this one worth tightening: it is the remaining case where pekko-http reads a length that a proxy or another server in the request path reads differently, or rejects.

Modification

Skip whitespace before and after the digits, require at least one digit, and end the value at the first non-digit.

This PR also brings SpecializedHeaderValueParsers up to date with the overflow fix from #1049, which landed on main but was never backported. #1221 rewrites the same lines, so the two changes cannot be separated cleanly; rather than cherry pick a partial version of #1221, the whole file is brought to parity with main and #1049's test is backported alongside it.

1.4.x currently checks for overflow with if (result < 0) after the fact, which misses values that wrap back to a positive Long:

Value 1.4.x today with this PR
92233720368547758080 rejected (already covered by a test) rejected
18446744073709551617 parsed as 1 rejected
184467440737095516160 parsed as 0 rejected

The replacement checks result > (Long.MaxValue - digit) / 10 before each multiply, so overflow is caught on the digit that would cause it.

Result

A value with whitespace between digits, or with no digit at all, is rejected with the same "Illegal `Content-Length` header value" error that other malformed values already produce. Leading and trailing whitespace still parse as before. A value that overflows Long is rejected with the existing "must not exceed 63-bit integer range" error in the cases listed above that previously slipped through.

This rejects three inputs that were accepted before: whitespace inside the digits, an empty value that used to be read as 0, and an overflowing value that wrapped back to a positive Long. All are malformed, but it is a behaviour change for anyone who was relying on the lenient reading.

After this PR http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/SpecializedHeaderValueParsers.scala and http-core/src/test/scala/org/apache/pekko/http/impl/engine/parsing/ContentLengthHeaderParserSpec.scala are byte-identical to main.

No MiMa exclude is needed — SpecializedHeaderValueParsers is @InternalApi private[parsing] and only method bodies change. #1049's num-overflow.excludes covers its HTTP/2 and Timestamp changes, which are not part of this backport.

Tests

…he#1221)

Motivation:
`ContentLengthParser` skipped whitespace anywhere in the field value, so
`Content-Length: 1 2` was read as 12 and `Content-Length: 5 5` as 55, and
an empty value was read as 0. The field value is `1*DIGIT` surrounded by
optional whitespace (RFC 9110, section 8.6 and RFC 9112, section 5). Every
other length ambiguity is rejected by this parser already: two differing
Content-Length headers, a Transfer-Encoding other than a single chunked,
and chunked together with a Content-Length. This was the remaining spot
where pekko-http could read a body length that another implementation in
the request path reads differently or rejects.

Modification:
Skip whitespace before and after the digits, but require at least one digit
and stop the value at the first non-digit.

Result:
A Content-Length value with whitespace between digits, or without any
digit, is rejected with the "Illegal `Content-Length` header value" error
that other malformed values already produce. Values with leading or
trailing whitespace keep parsing as before.

Note this rejects two inputs that were accepted before: whitespace inside
the digits, and an empty value that was read as 0.

Tests:
- sbt "http-core/testOnly org.apache.pekko.http.impl.engine.parsing.RequestParserCRLFSpec org.apache.pekko.http.impl.engine.parsing.RequestParserLFSpec org.apache.pekko.http.impl.engine.parsing.ResponseParserSpec" - pass, 2 new tests that both fail without the change; leading whitespace stays covered by the existing "Content-length:    17" tests
- sbt http-core/test - pass
- sbt http-tests/test - pass
- sbt http-core/mimaReportBinaryIssues - pass
- sbt http-core/scalafmt http-core/Test/scalafmt - clean

References:
None - tightens Content-Length parsing to the grammar
@pjfanning pjfanning added this to the 1.4.1 milestone Aug 27, 2026
apache#1221 rewrites the same lines that apache#1049 changed, so cherry picking it
onto 1.4.x brings the pre-multiply overflow check along with it. apache#1049
was never backported, so its test came too: a value that wraps back to a
small positive Long, which the old `result < 0` check did not catch.

SpecializedHeaderValueParsers.scala and ContentLengthHeaderParserSpec.scala
are now identical to main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pjfanning
pjfanning merged commit 54f50c3 into apache:1.4.x Aug 27, 2026
10 checks passed
@pjfanning
pjfanning deleted the content-length-1.4 branch August 27, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants