mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Emit empty multipart body for all cases
Prior to this commit, gh-30953 fixed a case where multipart file parts were not emitted properly when the body itself is empty. There are other cases like this, depending on the order and slicing of data buffers received by the parser. Here, a buffer containing the entire boundary would not cause an empty file part to be emitted and instead switch to the next header. This commit ensures that empty file parts are always emitted as they should. Fixes gh-37264
This commit is contained in:
+13
-1
@@ -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);
|
||||
|
||||
+17
-1
@@ -317,7 +317,7 @@ class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests {
|
||||
@ParameterizedDefaultPartHttpMessageReaderTest
|
||||
void emptyLastPart(DefaultPartHttpMessageReader reader) throws InterruptedException {
|
||||
MockServerHttpRequest request = createRequest(
|
||||
new ClassPathResource("empty-part.multipart", getClass()), "LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD");
|
||||
new ClassPathResource("empty-part-last.multipart", getClass()), "LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD");
|
||||
|
||||
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
|
||||
|
||||
@@ -330,6 +330,22 @@ class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests {
|
||||
latch.await();
|
||||
}
|
||||
|
||||
@ParameterizedDefaultPartHttpMessageReaderTest // gh-37264
|
||||
void emptyPartNotLast(DefaultPartHttpMessageReader reader) throws InterruptedException {
|
||||
MockServerHttpRequest request = createRequest(
|
||||
new ClassPathResource("empty-part.multipart", getClass()), "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, Resource resource, String boundary)
|
||||
throws InterruptedException {
|
||||
|
||||
+13
@@ -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--
|
||||
+6
-10
@@ -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--
|
||||
|
||||
Reference in New Issue
Block a user