Merge branch '7.0.x'

This commit is contained in:
Brian Clozel
2026-09-10 14:34:01 +02:00
4 changed files with 49 additions and 12 deletions
@@ -546,6 +546,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
// iterate over buffers in reverse order
DataBufferUtils.release(boundaryBuffer);
DataBuffer prev;
boolean found = false;
while ((prev = this.queue.pollLast()) != null) {
int prevByteCount = prev.readableByteCount();
int prevLen = prevByteCount + len;
@@ -555,6 +556,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
DataBufferUtils.release(prev);
enqueue(body);
flush();
found = true;
break;
}
else {
@@ -563,11 +565,21 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
len += prevByteCount;
}
}
if (!found) {
// all buffered bytes were boundary bytes: the part had an empty body
emitBody(buffer.factory().allocateBuffer(0), true);
}
}
else /* if (len == 0) */ {
// buffer starts with complete delimiter, flush out the previous buffers
DataBufferUtils.release(boundaryBuffer);
flush();
if (this.queue.isEmpty()) {
// nothing was ever buffered for this part: the part had an empty body
emitBody(buffer.factory().allocateBuffer(0), true);
}
else {
flush();
}
}
changeState(this, new HeadersState(), buffer);
@@ -316,7 +316,7 @@ class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests {
@ParameterizedDefaultPartHttpMessageReaderTest
void emptyLastPart(DefaultPartHttpMessageReader reader) throws InterruptedException {
MockServerHttpRequest request = createRequest(
"empty-part.multipart", "LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD");
"empty-part-last.multipart", "LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD");
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
@@ -329,6 +329,22 @@ class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests {
latch.await();
}
@ParameterizedDefaultPartHttpMessageReaderTest // gh-37264
void emptyPartNotLast(DefaultPartHttpMessageReader reader) throws InterruptedException {
MockServerHttpRequest request = createRequest(
"empty-part.multipart", "simple-boundary");
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
CountDownLatch latch = new CountDownLatch(2);
StepVerifier.create(result)
.consumeNextWith(part -> testPart(part, "file", "", latch)).as("file")
.consumeNextWith(part -> testPart(part, "action", "asd", latch)).as("action")
.verifyComplete();
latch.await();
}
private void testBrowser(DefaultPartHttpMessageReader reader, String fileName, String boundary)
throws InterruptedException {
@@ -0,0 +1,13 @@
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD
Content-Disposition: form-data; name="files"; filename="file17312898095703516893.tmp"
Content-Type: application/octet-stream
Content-Length: 0
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD
Content-Disposition: form-data; name="files"; filename="file14790463448453253614.tmp"
Content-Type: application/octet-stream
Content-Length: 0
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD--
@@ -1,13 +1,9 @@
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD
Content-Disposition: form-data; name="files"; filename="file17312898095703516893.tmp"
Content-Type: application/octet-stream
Content-Length: 0
--simple-boundary
Content-Disposition: form-data; name="file"; filename="test.txt"
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD
Content-Disposition: form-data; name="files"; filename="file14790463448453253614.tmp"
Content-Type: application/octet-stream
Content-Length: 0
--simple-boundary
Content-Disposition: form-data; name="action"
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD--
asd
--simple-boundary--