This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch 1.4.x
in repository https://gitbox.apache.org/repos/asf/pekko-http.git
The following commit(s) were added to refs/heads/1.4.x by this push:
new d5593bfb0 fix: start each multipart part with its own header state
(#1279) (#1327)
d5593bfb0 is described below
commit d5593bfb07e31604f183ceb34913dc61fc74feeb
Author: PJ Fanning <[email protected]>
AuthorDate: Fri Oct 9 20:01:12 2026 +0100
fix: start each multipart part with its own header state (#1279) (#1327)
When a body part ends without a header-separating empty line, the
`BoundaryHeader` branch of `parseHeaderLines` emits that part and continues
into the next one, but passed its own `headers` and `headerCount` along. The
boundary starts a new part, so both should start empty -- `cth` was already
reset to `None` on the same call, which suggests the other two were simply
missed.
Two visible effects, both fixed here:
- the following part was reported carrying headers it never declared;
- `headerCount` was never reset either, so a run of such parts accumulated
towards `max-header-count` across parts rather than per part, failing
parts that are individually well within the limit.
The reset stays a direct self-call to `parseHeaderLines`: that call is what
makes the method `@tailrec`, and routing it through a helper would turn it
into an unoptimised mutual recursion.
Fixes #1278.
(cherry picked from commit dc22225857f0cdbbe32d80b3685ea4de74764392)
---
.../http/impl/engine/parsing/BodyPartParser.scala | 3 +-
.../unmarshalling/MultipartUnmarshallersSpec.scala | 40 ++++++++++++++++++++++
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
index 04e0a16cb..a0c9e0bf7 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
@@ -178,7 +178,8 @@ private[http] final class BodyPartParser(
emit(BodyPartStart(headers.toList, _ =>
HttpEntity.empty(contentType)))
val ix = lineStart + eolConfiguration.boundaryLength
if (eolConfiguration.isEndOfLine(input, ix))
- parseHeaderLines(input, ix + eolConfiguration.eolLength,
headers, headerCount, None)
+ // an empty part; the boundary starts another one, so its header
state starts empty
+ parseHeaderLines(input, ix + eolConfiguration.eolLength,
ListBuffer[HttpHeader](), 0, None)
else if (doubleDash(input, ix)) setShouldTerminate()
else fail("Illegal multipart boundary in message content")
diff --git
a/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
index a1d65687d..8f43d31a8 100644
---
a/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
+++
b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
@@ -18,6 +18,7 @@ import pekko.http.impl.util._
import pekko.http.scaladsl.model.MediaTypes._
import pekko.http.scaladsl.model._
import pekko.http.scaladsl.model.headers._
+import pekko.http.scaladsl.settings.ParserSettings
import pekko.http.scaladsl.util.FastFuture._
import pekko.stream.scaladsl._
import pekko.testkit._
@@ -61,6 +62,45 @@ trait MultipartUnmarshallersSpec extends
PekkoSpecWithMaterializer {
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should
haveParts(
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/xml(UTF-8)`),
List(Age(12))))
}
+ "consecutive parts without header separation, each keeping its own
headers" in {
+ Unmarshal(HttpEntity(
+ `multipart/mixed`.withBoundary("XYZABC"),
+ ByteString("""--XYZABC
+ |Age: 12
+ |--XYZABC
+ |Age: 13
+ |--XYZABC
+
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should
haveParts(
+
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
List(Age(12))),
+
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
List(Age(13))),
+
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`)))
+ }
+ "a part without header separation not carrying its Content-Type into the
next part" in {
+ Unmarshal(HttpEntity(
+ `multipart/mixed`.withBoundary("XYZABC"),
+ ByteString("""--XYZABC
+ |Content-type: text/xml; charset=UTF-8
+ |--XYZABC
+
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should
haveParts(
+
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/xml(UTF-8)`)),
+
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`)))
+ }
+ "parts without header separation counting headers per part, not across
parts" in {
+ implicit val parserSettings: ParserSettings =
ParserSettings(system).withMaxHeaderCount(2)
+ Unmarshal(HttpEntity(
+ `multipart/mixed`.withBoundary("XYZABC"),
+ ByteString("""--XYZABC
+ |Age: 12
+ |X-Foo: bar
+ |--XYZABC
+ |Age: 13
+ |X-Foo: baz
+
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should
haveParts(
+
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
+ List(Age(12), RawHeader("X-Foo", "bar"))),
+
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
+ List(Age(13), RawHeader("X-Foo", "baz"))))
+ }
"an implicitly typed part (without headers) (Strict)" in {
Unmarshal(HttpEntity(
`multipart/mixed`.withBoundary("XYZABC"),
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]