Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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 @@ -134,6 +134,11 @@ public static final boolean isMultipartContent(final RequestContext ctx) {
*/
private long fileCountMax = -1;

/**
* The maximum size of the all parts headers in bytes that may be uploaded in a single request. A value of -1 indicates no maximum.
*/
private long partHeaderTotalSizeMax = -1;

/**
* The content encoding to use when reading part headers.
*/
Expand Down Expand Up @@ -271,6 +276,15 @@ public long getFileSizeMax() {
return fileSizeMax;
}

/**
* Gets the maximum allowed size of all parts headers in a single uploaded request.
*
* @return Maximum size in bytes of all parts headers.
*/
public long getPartHeaderTotalSizeMax() {
return partHeaderTotalSizeMax;
}

/**
* Gets the character encoding used when reading the headers of an individual part. When not specified, or {@code null}, the request encoding is used. If
* that is also not specified, or {@code null}, the platform default encoding is used.
Expand Down Expand Up @@ -536,6 +550,16 @@ public void setFileSizeMax(final long fileSizeMax) {
this.fileSizeMax = fileSizeMax;
}

/**
* Sets the maximum allowed size in bytes of all parts headers.
*
* @see #getPartHeaderTotalSizeMax()
* @param partHeaderTotalSizeMax Maximum size of all parts headers.
*/
public void setPartHeaderTotalSizeMax(long partHeaderTotalSizeMax) {
this.partHeaderTotalSizeMax = partHeaderTotalSizeMax;
}

/**
* Specifies the character encoding to be used when reading the headers of individual part. When not specified, or {@code null}, the request encoding is
* used. If that is also not specified, or {@code null}, the platform default encoding is used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ private boolean findNextItem() throws FileUploadException, IOException {
currentItem = null;
}
final var multi = getMultiPartInput();
final var phtsm = fileUpload.getPartHeaderTotalSizeMax();
for (;;) {
final boolean nextPart;
if (skipPreamble) {
Expand All @@ -152,6 +153,11 @@ private boolean findNextItem() throws FileUploadException, IOException {
continue;
}
final var headers = fileUpload.getParsedHeaders(multi.readHeaders());
if (phtsm != -1 && multi.getTotalHeaderSizeRead() > phtsm) {
throw new FileUploadSizeException(String.format(
"The request was rejected because total header size of all parts exceeds the configured partHeaderTotalSizeMax (%s) bytes",
Long.valueOf(phtsm)), phtsm, multi.getTotalHeaderSizeRead());
}
if (multipartRelated) {
currentFieldName = "";
currentItem = new FileItemInputImpl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,18 @@ public byte readByte() throws IOException {
return buffer[head++];
}

/**
* The byte size of all headers that have been read.
*/
private long totalHeaderSizeRead = 0L;

/**
* @return The byte size of all headers that have been read
*/
public long getTotalHeaderSizeRead() {
return totalHeaderSizeRead;
}

/**
* Reads the {@code header-part} of the current {@code encapsulation}.
* <p>
Expand Down Expand Up @@ -949,6 +961,7 @@ public String readHeaders() throws FileUploadSizeException, MalformedStreamExcep
baos.write(b);
}
try {
totalHeaderSizeRead += baos.size();
return baos.toString(Charsets.toCharset(headerCharset, Charset.defaultCharset()).name());
} catch (final UnsupportedEncodingException e) {
// not possible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -442,4 +443,75 @@ void testMultipleRelated() throws Exception {
assertEquals("text/plain", part2.getContentType());
assertNull(part2.getName());
}


@Test
public void testExceedTotalPartHeaderSizeLimit() throws IOException {
upload.setPartHeaderTotalSizeMax(250);
try {
// @formatter:off
final var fileItems = parseUpload(upload,
"-----1234\r\n" +
"Content-Disposition: "
+ "form-data; name=\"file\"; filename=\"foo.tab\"\r\n" +
"Content-Type: text/whatever\r\n" +
"\r\n" +
"This is the content of the file\n" +
"\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"field\"\r\n" +
"\r\n" +
"fieldValue\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"multi\"\r\n" +
"\r\n" +
"value1\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"multi\"\r\n" +
"Content-Type: text/plain\r\n" +
"Content-ID: multi-id\r\n" +
"\r\n" +
"value2\r\n" +
"-----1234--\r\n");
// @formatter:on
fail("FileUploadSizeException expected!");
} catch (FileUploadSizeException fse) {
assertEquals(upload.getPartHeaderTotalSizeMax(), fse.getPermitted());
}
}

@Test
public void testPassTotalPartHeaderSizeLimit() throws IOException {
upload.setPartHeaderTotalSizeMax(1 << 10);
try {
// @formatter:off
final var fileItems = parseUpload(upload,
"-----1234\r\n" +
"Content-Disposition: "
+ "form-data; name=\"file\"; filename=\"foo.tab\"\r\n" +
"Content-Type: text/whatever\r\n" +
"\r\n" +
"This is the content of the file\n" +
"\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"field\"\r\n" +
"\r\n" +
"fieldValue\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"multi\"\r\n" +
"\r\n" +
"value1\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"multi\"\r\n" +
"Content-Type: text/plain\r\n" +
"Content-ID: multi-id\r\n" +
"\r\n" +
"value2\r\n" +
"-----1234--\r\n");
// @formatter:on
assertEquals(4, fileItems.size());
} catch (FileUploadSizeException fse) {
fail(fse);
}
}
}