Browse Source

Support empty part in DefaultPartHttpMessageReader

This commit fixes a bug in DefaultPartHttpMessageReader's
MultipartParser, due to which the last token in a part window was not
properly indicated.

See gh-30953
Closes gh-31766
pull/33657/head
Arjen Poutsma 2 years ago
parent
commit
29a39b617e
  1. 2
      spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java
  2. 17
      spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java
  3. 13
      spring-web/src/test/resources/org/springframework/http/codec/multipart/empty-part.multipart
  4. 8
      spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java

2
spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java

@ -540,7 +540,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> { @@ -540,7 +540,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
while ((prev = this.queue.pollLast()) != null) {
int prevByteCount = prev.readableByteCount();
int prevLen = prevByteCount + len;
if (prevLen > 0) {
if (prevLen >= 0) {
// slice body part of previous buffer, and flush it
DataBuffer body = prev.split(prevLen + prev.readPosition());
DataBufferUtils.release(prev);

17
spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java

@ -301,6 +301,23 @@ class DefaultPartHttpMessageReaderTests { @@ -301,6 +301,23 @@ class DefaultPartHttpMessageReaderTests {
latch.await();
}
@ParameterizedDefaultPartHttpMessageReaderTest
void emptyLastPart(DefaultPartHttpMessageReader reader) throws InterruptedException {
MockServerHttpRequest request = createRequest(
new ClassPathResource("empty-part.multipart", getClass()), "LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD");
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
CountDownLatch latch = new CountDownLatch(2);
StepVerifier.create(result)
.consumeNextWith(part -> testPart(part, null, "", latch))
.consumeNextWith(part -> testPart(part, null, "", latch))
.verifyComplete();
latch.await();
}
private void testBrowser(DefaultPartHttpMessageReader reader, Resource resource, String boundary)
throws InterruptedException {

13
spring-web/src/test/resources/org/springframework/http/codec/multipart/empty-part.multipart

@ -0,0 +1,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--

8
spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java

@ -259,13 +259,19 @@ class MultipartRouterFunctionIntegrationTests extends AbstractRouterFunctionInte @@ -259,13 +259,19 @@ class MultipartRouterFunctionIntegrationTests extends AbstractRouterFunctionInte
assertThat(data).hasSize(2);
List<PartEvent> fileData = data.get(0);
assertThat(fileData).hasSize(1);
assertThat(fileData).hasSize(2);
assertThat(fileData.get(0)).isInstanceOf(FilePartEvent.class);
FilePartEvent filePartEvent = (FilePartEvent) fileData.get(0);
assertThat(filePartEvent.name()).isEqualTo("fooPart");
assertThat(filePartEvent.filename()).isEqualTo("foo.txt");
DataBufferUtils.release(filePartEvent.content());
assertThat(fileData.get(1)).isInstanceOf(FilePartEvent.class);
filePartEvent = (FilePartEvent) fileData.get(1);
assertThat(filePartEvent.name()).isEqualTo("fooPart");
assertThat(filePartEvent.filename()).isEqualTo("foo.txt");
DataBufferUtils.release(filePartEvent.content());
List<PartEvent> fieldData = data.get(1);
assertThat(fieldData).hasSize(1);
assertThat(fieldData.get(0)).isInstanceOf(FormPartEvent.class);

Loading…
Cancel
Save