Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public MultiValueMap<String, Part> getParts() {

@Override
public void onHeaders(HttpHeaders headers) {
this.state.onComplete();
if (isFormField(headers)) {
this.state = new FormFieldState(headers);
}
Expand Down Expand Up @@ -127,6 +128,7 @@ void deleteParts() {

@Override
public void onComplete() {
this.state.onComplete();
if (logger.isTraceEnabled()) {
logger.trace("Finished reading " + this.partCount + " part(s)");
}
Expand Down Expand Up @@ -171,6 +173,14 @@ private interface State {
*/
void onBody(DataBuffer dataBuffer, boolean last);

/**
* Invoked when no further {@link #onBody(DataBuffer, boolean) body} is
* expected for the current part, that is when a new part begins, or when
* parsing completed. This emits the part, also when it has an empty body.
*/
default void onComplete() {
}

/**
* Clean up resources.
*/
Expand Down Expand Up @@ -210,6 +220,8 @@ private final class FormFieldState implements State {

private final HttpHeaders headers;

private boolean emitted;

public FormFieldState(HttpHeaders headers) {
this.headers = headers;
}
Expand All @@ -227,6 +239,14 @@ public void onBody(DataBuffer dataBuffer, boolean last) {
PartGenerator.this.maxInMemorySize + " bytes");
}
if (last) {
onComplete();
}
}

@Override
public void onComplete() {
if (!this.emitted) {
this.emitted = true;
byte[] bytes = this.value.toByteArrayUnsafe();
String value = new String(bytes, MultipartUtils.charset(this.headers));
FormFieldPart formFieldPart = DefaultParts.formFieldPart(this.headers, value);
Expand Down Expand Up @@ -268,6 +288,7 @@ private final class InMemoryState implements State {

private final HttpHeaders headers;

private boolean emitted;

public InMemoryState(HttpHeaders headers) {
this.headers = headers;
Expand All @@ -282,6 +303,14 @@ public void onBody(DataBuffer dataBuffer, boolean last) {
}
this.content.add(dataBuffer);
if (last) {
onComplete();
}
}

@Override
public void onComplete() {
if (!this.emitted) {
this.emitted = true;
emitMemoryPart();
}
}
Expand Down Expand Up @@ -339,6 +368,8 @@ private final class FileState implements State {

private long byteCount;

private boolean emitted;

public FileState(HttpHeaders headers, Path folder) {
this.headers = headers;
this.file = createFile(folder);
Expand Down Expand Up @@ -377,6 +408,14 @@ public void onBody(DataBuffer dataBuffer, boolean last) {
}
writeBuffer(dataBuffer);
if (last) {
onComplete();
}
}

@Override
public void onComplete() {
if (!this.emitted) {
this.emitted = true;
Part part = DefaultParts.part(this.headers, this.file);
PartGenerator.this.addPart(part);
closeOutputStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,26 @@ void readMultipartBrowser() throws Exception {
assertThat(result.get("text2")).anyMatch(isFormData("text2", "b"));
}

@Test
void readMultipartEmptyPart() throws Exception {
MockHttpInputMessage response = createMultipartResponse("servlet-empty-part.multipart", "boundary");
MultiValueMap<String, Part> result = converter.read(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class), response, null);

assertThat(result).containsOnlyKeys("text1", "text2");
assertThat(result.get("text1")).anyMatch(isFormData("text1", ""));
assertThat(result.get("text2")).anyMatch(isFormData("text2", "a"));
}

@Test
void readMultipartEmptyLastPart() throws Exception {
MockHttpInputMessage response = createMultipartResponse("servlet-empty-last-part.multipart", "boundary");
MultiValueMap<String, Part> result = converter.read(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class), response, null);

assertThat(result).containsOnlyKeys("text1", "text2");
assertThat(result.get("text1")).anyMatch(isFormData("text1", "a"));
assertThat(result.get("text2")).anyMatch(isFormData("text2", ""));
}

@Test
void readMultipartInvalid() throws Exception {
MockHttpInputMessage response = createMultipartResponse("garbage-1.multipart", "boundary");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--boundary
Content-Disposition: form-data; name="text1"

a
--boundary
Content-Disposition: form-data; name="text2"


--boundary--
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--boundary
Content-Disposition: form-data; name="text1"


--boundary
Content-Disposition: form-data; name="text2"

a
--boundary--