Skip to content

Commit

Permalink
[UNDERTOW-1325] Servlet MultipartConfig attribute file-size-threshold…
Browse files Browse the repository at this point in the history
… not working
  • Loading branch information
spyrkob authored and stuartwdouglas committed Aug 29, 2018
1 parent 3440e83 commit 3fc0c9d
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 46 deletions.
5 changes: 4 additions & 1 deletion core/src/main/java/io/undertow/UndertowMessages.java
Expand Up @@ -89,7 +89,7 @@ public interface UndertowMessages {
// @Message(id = 16, value = "Could not add cookie as cookie handler was not present in the handler chain") // @Message(id = 16, value = "Could not add cookie as cookie handler was not present in the handler chain")
// IllegalStateException cookieHandlerNotPresent(); // IllegalStateException cookieHandlerNotPresent();


@Message(id = 17, value = "Form value is a file, use getFile() instead") @Message(id = 17, value = "Form value is a file, use getFileItem() instead")
IllegalStateException formValueIsAFile(); IllegalStateException formValueIsAFile();


@Message(id = 18, value = "Form value is a String, use getValue() instead") @Message(id = 18, value = "Form value is a String, use getValue() instead")
Expand Down Expand Up @@ -594,4 +594,7 @@ public interface UndertowMessages {


@Message(id = 191, value = "Default context cannot be null") @Message(id = 191, value = "Default context cannot be null")
IllegalStateException defaultContextCannotBeNull(); IllegalStateException defaultContextCannotBeNull();

@Message(id = 192, value = "Form value is a in-memory file, use getFileItem() instead")
IllegalStateException formValueIsInMemoryFile();
} }
Expand Up @@ -174,7 +174,7 @@ private void dumpRequestBody(HttpServerExchange exchange, StringBuilder sb) {
sb.append(formField) sb.append(formField)
.append("="); .append("=");
for (FormData.FormValue formValue : formValues) { for (FormData.FormValue formValue : formValues) {
sb.append(formValue.isFile() ? "[file-content]" : formValue.getValue()); sb.append(formValue.isFileItem() ? "[file-content]" : formValue.getValue());
sb.append("\n"); sb.append("\n");


if (formValue.getHeaders() != null) { if (formValue.getHeaders() != null) {
Expand Down
119 changes: 113 additions & 6 deletions core/src/main/java/io/undertow/server/handlers/form/FormData.java
Expand Up @@ -18,7 +18,13 @@


package io.undertow.server.handlers.form; package io.undertow.server.handlers.form;


import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
Expand Down Expand Up @@ -64,6 +70,17 @@ public Deque<FormValue> get(String name) {
return values.get(name); return values.get(name);
} }


public void add(String name, byte[] value, String fileName, HeaderMap headers) {
Deque<FormValue> values = this.values.get(name);
if (values == null) {
this.values.put(name, values = new ArrayDeque<>(1));
}
values.add(new FormValueImpl(value, fileName, headers));
if (++valueCount > maxValues) {
throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues));
}
}

public void add(String name, String value) { public void add(String name, String value) {
add(name, value, null); add(name, value, null);
} }
Expand Down Expand Up @@ -157,17 +174,24 @@ public interface FormValue {
* *
* @return * @return
*/ */
@Deprecated
boolean isFile(); boolean isFile();


/** /**
* @return The temp file that the file data was saved to * @return The temp file that the file data was saved to
*
* @throws IllegalStateException if this is not a file * @throws IllegalStateException if this is not a file
*/ */
@Deprecated
Path getPath(); Path getPath();


@Deprecated @Deprecated
File getFile(); File getFile();


FileItem getFileItem();

boolean isFileItem();

/** /**
* @return The filename specified in the disposition header. * @return The filename specified in the disposition header.
*/ */
Expand All @@ -181,28 +205,95 @@ public interface FormValue {


} }


public static class FileItem {
private final Path file;
private final byte[] content;

public FileItem(Path file) {
this.file = file;
this.content = null;
}

public FileItem(byte[] content) {
this.file = null;
this.content = content;
}

public boolean isInMemory() {
return file == null;
}

public Path getFile() {
return file;
}

public long getFileSize() throws IOException {
if (isInMemory()) {
return content.length;
} else {
return Files.size(file);
}
}

public InputStream getInputStream() throws IOException {
if (file != null) {
return new BufferedInputStream(Files.newInputStream(file));
} else {
return new ByteArrayInputStream(content);
}
}

public void delete() throws IOException {
if (file != null) {
try {
Files.delete(file);
} catch (NoSuchFileException e) { //already deleted
}
}
}

public void write(Path target) throws IOException {
if (file != null) {
try {
Files.move(file, target);
} catch (IOException e) {
Files.copy(getInputStream(), target);
}
} else {
Files.copy(getInputStream(), target);
}
}
}



static class FormValueImpl implements FormValue { static class FormValueImpl implements FormValue {


private final String value; private final String value;
private final String fileName; private final String fileName;
private final Path file;
private final HeaderMap headers; private final HeaderMap headers;
private final FileItem fileItem;


FormValueImpl(String value, HeaderMap headers) { FormValueImpl(String value, HeaderMap headers) {
this.value = value; this.value = value;
this.headers = headers; this.headers = headers;
this.file = null;
this.fileName = null; this.fileName = null;
this.fileItem = null;
} }


FormValueImpl(Path file, final String fileName, HeaderMap headers) { FormValueImpl(Path file, final String fileName, HeaderMap headers) {
this.file = file; this.fileItem = new FileItem(file);
this.headers = headers; this.headers = headers;
this.fileName = fileName; this.fileName = fileName;
this.value = null; this.value = null;
} }


FormValueImpl(byte[] data, String fileName, HeaderMap headers) {
this.fileItem = new FileItem(data);
this.fileName = fileName;
this.headers = headers;
this.value = null;
}



@Override @Override
public String getValue() { public String getValue() {
Expand All @@ -214,22 +305,38 @@ public String getValue() {


@Override @Override
public boolean isFile() { public boolean isFile() {
return file != null; return fileItem != null && !fileItem.isInMemory();
} }


@Override @Override
public Path getPath() { public Path getPath() {
if (file == null) { if (fileItem == null) {
throw UndertowMessages.MESSAGES.formValueIsAString(); throw UndertowMessages.MESSAGES.formValueIsAString();
} }
return file; if (fileItem.isInMemory()) {
throw UndertowMessages.MESSAGES.formValueIsInMemoryFile();
}
return fileItem.getFile();
} }


@Override @Override
public File getFile() { public File getFile() {
return getPath().toFile(); return getPath().toFile();
} }


@Override
public FileItem getFileItem() {
if (fileItem == null) {
throw UndertowMessages.MESSAGES.formValueIsAString();
}
return fileItem;
}

@Override
public boolean isFileItem() {
return fileItem != null;
}

@Override @Override
public HeaderMap getHeaders() { public HeaderMap getHeaders() {
return headers; return headers;
Expand Down
Expand Up @@ -36,6 +36,7 @@
import org.xnio.channels.StreamSourceChannel; import org.xnio.channels.StreamSourceChannel;


import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
Expand All @@ -48,6 +49,7 @@
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;


Expand All @@ -66,6 +68,8 @@ public class MultiPartParserDefinition implements FormParserFactory.ParserDefini


private long maxIndividualFileSize = -1; private long maxIndividualFileSize = -1;


private long fileSizeThreshold;

public MultiPartParserDefinition() { public MultiPartParserDefinition() {
tempFileLocation = Paths.get(System.getProperty("java.io.tmpdir")); tempFileLocation = Paths.get(System.getProperty("java.io.tmpdir"));
} }
Expand All @@ -83,7 +87,7 @@ public FormDataParser create(final HttpServerExchange exchange) {
UndertowLogger.REQUEST_LOGGER.debugf("Could not find boundary in multipart request with ContentType: %s, multipart data will not be available", mimeType); UndertowLogger.REQUEST_LOGGER.debugf("Could not find boundary in multipart request with ContentType: %s, multipart data will not be available", mimeType);
return null; return null;
} }
final MultiPartUploadHandler parser = new MultiPartUploadHandler(exchange, boundary, maxIndividualFileSize, defaultEncoding); final MultiPartUploadHandler parser = new MultiPartUploadHandler(exchange, boundary, maxIndividualFileSize, fileSizeThreshold, defaultEncoding);
exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { exchange.addExchangeCompleteListener(new ExchangeCompletionListener() {
@Override @Override
public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) { public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) {
Expand Down Expand Up @@ -138,12 +142,17 @@ public void setMaxIndividualFileSize(final long maxIndividualFileSize) {
this.maxIndividualFileSize = maxIndividualFileSize; this.maxIndividualFileSize = maxIndividualFileSize;
} }


public void setFileSizeThreshold(long fileSizeThreshold) {
this.fileSizeThreshold = fileSizeThreshold;
}

private final class MultiPartUploadHandler implements FormDataParser, MultipartParser.PartHandler { private final class MultiPartUploadHandler implements FormDataParser, MultipartParser.PartHandler {


private final HttpServerExchange exchange; private final HttpServerExchange exchange;
private final FormData data; private final FormData data;
private final List<Path> createdFiles = new ArrayList<>(); private final List<Path> createdFiles = new ArrayList<>();
private final long maxIndividualFileSize; private final long maxIndividualFileSize;
private final long fileSizeThreshold;
private String defaultEncoding; private String defaultEncoding;


private final ByteArrayOutputStream contentBytes = new ByteArrayOutputStream(); private final ByteArrayOutputStream contentBytes = new ByteArrayOutputStream();
Expand All @@ -157,10 +166,11 @@ private final class MultiPartUploadHandler implements FormDataParser, MultipartP
private final MultipartParser.ParseState parser; private final MultipartParser.ParseState parser;




private MultiPartUploadHandler(final HttpServerExchange exchange, final String boundary, final long maxIndividualFileSize, final String defaultEncoding) { private MultiPartUploadHandler(final HttpServerExchange exchange, final String boundary, final long maxIndividualFileSize, final long fileSizeThreshold, final String defaultEncoding) {
this.exchange = exchange; this.exchange = exchange;
this.maxIndividualFileSize = maxIndividualFileSize; this.maxIndividualFileSize = maxIndividualFileSize;
this.defaultEncoding = defaultEncoding; this.defaultEncoding = defaultEncoding;
this.fileSizeThreshold = fileSizeThreshold;
this.data = new FormData(exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, 1000)); this.data = new FormData(exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, 1000));
String charset = defaultEncoding; String charset = defaultEncoding;
String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
Expand Down Expand Up @@ -238,7 +248,7 @@ public void beginPart(final HeaderMap headers) {
if (disposition.startsWith("form-data")) { if (disposition.startsWith("form-data")) {
currentName = Headers.extractQuotedValueFromHeader(disposition, "name"); currentName = Headers.extractQuotedValueFromHeader(disposition, "name");
fileName = Headers.extractQuotedValueFromHeaderWithEncoding(disposition, "filename"); fileName = Headers.extractQuotedValueFromHeaderWithEncoding(disposition, "filename");
if (fileName != null) { if (fileName != null && fileSizeThreshold == 0) {
try { try {
if (tempFileLocation != null) { if (tempFileLocation != null) {
file = Files.createTempFile(tempFileLocation, "undertow", "upload"); file = Files.createTempFile(tempFileLocation, "undertow", "upload");
Expand All @@ -261,6 +271,24 @@ public void data(final ByteBuffer buffer) throws IOException {
if (this.maxIndividualFileSize > 0 && this.currentFileSize > this.maxIndividualFileSize) { if (this.maxIndividualFileSize > 0 && this.currentFileSize > this.maxIndividualFileSize) {
throw UndertowMessages.MESSAGES.maxFileSizeExceeded(this.maxIndividualFileSize); throw UndertowMessages.MESSAGES.maxFileSizeExceeded(this.maxIndividualFileSize);
} }
if (file == null && fileName != null && fileSizeThreshold < this.currentFileSize) {
try {
if (tempFileLocation != null) {
file = Files.createTempFile(tempFileLocation, "undertow", "upload");
} else {
file = Files.createTempFile("undertow", "upload");
}
createdFiles.add(file);

FileOutputStream fileOutputStream = new FileOutputStream(file.toFile());
contentBytes.writeTo(fileOutputStream);

fileChannel = fileOutputStream.getChannel();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

if (file == null) { if (file == null) {
while (buffer.hasRemaining()) { while (buffer.hasRemaining()) {
contentBytes.write(buffer.get()); contentBytes.write(buffer.get());
Expand All @@ -275,12 +303,16 @@ public void endPart() {
if (file != null) { if (file != null) {
data.add(currentName, file, fileName, headers); data.add(currentName, file, fileName, headers);
file = null; file = null;
contentBytes.reset();
try { try {
fileChannel.close(); fileChannel.close();
fileChannel = null; fileChannel = null;
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} else if (fileName != null) {
data.add(currentName, Arrays.copyOf(contentBytes.toByteArray(), contentBytes.size()), fileName, headers);
contentBytes.reset();
} else { } else {




Expand Down

0 comments on commit 3fc0c9d

Please sign in to comment.