fix #2842 Add http.fileupload.max.file.count property and refactor file upload handling logic

This commit is contained in:
Shinsuke Sugaya 2024-09-08 14:41:41 +09:00
parent e7657528ef
commit 9ac9f96948
3 changed files with 168 additions and 110 deletions

View file

@ -310,6 +310,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
/** The key of the configuration. e.g. 262144 */
String HTTP_FILEUPLOAD_THRESHOLD_SIZE = "http.fileupload.threshold.size";
/** The key of the configuration. e.g. 10 */
String HTTP_FILEUPLOAD_MAX_FILE_COUNT = "http.fileupload.max.file.count";
/** The key of the configuration. e.g. groovy */
String CRAWLER_DEFAULT_SCRIPT = "crawler.default.script";
@ -2646,6 +2649,21 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
*/
Integer getHttpFileuploadThresholdSizeAsInteger();
/**
* Get the value for the key 'http.fileupload.max.file.count'. <br>
* The value is, e.g. 10 <br>
* @return The value of found property. (NotNull: if not found, exception but basically no way)
*/
String getHttpFileuploadMaxFileCount();
/**
* Get the value for the key 'http.fileupload.max.file.count' as {@link Integer}. <br>
* The value is, e.g. 10 <br>
* @return The value of found property. (NotNull: if not found, exception but basically no way)
* @throws NumberFormatException When the property is not integer.
*/
Integer getHttpFileuploadMaxFileCountAsInteger();
/**
* Get the value for the key 'crawler.default.script'. <br>
* The value is, e.g. groovy <br>
@ -8221,6 +8239,14 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
return getAsInteger(FessConfig.HTTP_FILEUPLOAD_THRESHOLD_SIZE);
}
public String getHttpFileuploadMaxFileCount() {
return get(FessConfig.HTTP_FILEUPLOAD_MAX_FILE_COUNT);
}
public Integer getHttpFileuploadMaxFileCountAsInteger() {
return getAsInteger(FessConfig.HTTP_FILEUPLOAD_MAX_FILE_COUNT);
}
public String getCrawlerDefaultScript() {
return get(FessConfig.CRAWLER_DEFAULT_SCRIPT);
}
@ -11066,6 +11092,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
defaultMap.put(FessConfig.HTTP_PROXY_PASSWORD, "");
defaultMap.put(FessConfig.HTTP_FILEUPLOAD_MAX_SIZE, "262144000");
defaultMap.put(FessConfig.HTTP_FILEUPLOAD_THRESHOLD_SIZE, "262144");
defaultMap.put(FessConfig.HTTP_FILEUPLOAD_MAX_FILE_COUNT, "10");
defaultMap.put(FessConfig.CRAWLER_DEFAULT_SCRIPT, "groovy");
defaultMap.put(FessConfig.CRAWLER_HTTP_thread_pool_SIZE, "0");
defaultMap.put(FessConfig.CRAWLER_DOCUMENT_MAX_SITE_LENGTH, "100");

View file

@ -19,10 +19,11 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -36,9 +37,7 @@ import org.apache.logging.log4j.Logger;
import org.codelibs.fess.util.ComponentUtil;
import org.dbflute.helper.message.ExceptionMessageBuilder;
import org.lastaflute.core.message.UserMessages;
import org.lastaflute.web.LastaWebKey;
import org.lastaflute.web.exception.Forced404NotFoundException;
import org.lastaflute.web.ruts.config.ModuleConfig;
import org.lastaflute.web.ruts.multipart.MultipartFormFile;
import org.lastaflute.web.ruts.multipart.MultipartRequestHandler;
import org.lastaflute.web.ruts.multipart.MultipartRequestWrapper;
@ -46,6 +45,9 @@ import org.lastaflute.web.ruts.multipart.exception.MultipartExceededException;
import org.lastaflute.web.util.LaServletContextUtil;
/**
* The handler of multipart request (fileupload request). <br>
* This instance is created per one multipart request.
*
* @author modified by jflute (originated in Seasar)
*/
public class FessMultipartRequestHandler implements MultipartRequestHandler {
@ -54,53 +56,92 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
// Definition
// ==========
private static final Logger logger = LogManager.getLogger(FessMultipartRequestHandler.class);
protected static final String CONTEXT_TEMPDIR_KEY = "javax.servlet.context.tempdir";
protected static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir";
// -----------------------------------------------------
// Temporary Directory
// -------------------
// used as repository for requested parameters
protected static final String CONTEXT_TEMPDIR_KEY = "javax.servlet.context.tempdir"; // prior
protected static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir"; // secondary
// ===================================================================================
// Attribute
// =========
protected Map<String, Object> elementsAll;
protected Map<String, MultipartFormFile> elementsFile;
protected Map<String, String[]> elementsText;
// keeping parsed request parameters, normal texts or uploaded files
// keys are requested parameter names (treated as field name here)
protected Map<String, Object> elementsAll; // lazy-loaded, then after not null
protected Map<String, MultipartFormFile> elementsFile; // me too
protected Map<String, String[]> elementsText; // me too
// ===================================================================================
// Handle Request
// ==============
@Override
public void handleRequest(final HttpServletRequest request) throws ServletException {
// /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// copied from super's method and extends it
// basically for JVN#14876762
// thought not all problems are resolved however the main case is safety
// - - - - - - - - - -/
final ServletFileUpload upload = createServletFileUpload(request);
prepareElementsHash();
try {
final List<FileItem> items = parseRequest(request, upload);
mappingParameter(request, items);
} catch (final SizeLimitExceededException e) {
} catch (final SizeLimitExceededException e) { // special handling
handleSizeLimitExceededException(request, e);
} catch (final FileUploadException e) {
} catch (final FileUploadException e) { // contains fileCount exceeded
handleFileUploadException(e);
}
}
protected ModuleConfig getModuleConfig(final HttpServletRequest request) {
return (ModuleConfig) request.getAttribute(LastaWebKey.MODULE_CONFIG_KEY);
protected void prepareElementsHash() { // traditional name
// #thinking jflute might lazy-loaded be unneeded? because created per request
// (2024/09/08)
elementsAll = new HashMap<>();
elementsText = new HashMap<>();
elementsFile = new HashMap<>();
}
protected List<FileItem> parseRequest(final HttpServletRequest request, final ServletFileUpload upload) throws FileUploadException {
return upload.parseRequest(request);
}
// ===================================================================================
// Create ServletFileUpload
// ========================
// ServletFileUpload
// =================
protected ServletFileUpload createServletFileUpload(final HttpServletRequest request) {
final DiskFileItemFactory fileItemFactory = createDiskFileItemFactory();
final ServletFileUpload upload = newServletFileUpload(fileItemFactory);
upload.setHeaderEncoding(request.getCharacterEncoding());
upload.setSizeMax(getSizeMax());
setupServletFileUpload(upload, request);
return upload;
}
// -----------------------------------------------------
// DiskFileItemFactory Settings
// ----------------------------
protected DiskFileItemFactory createDiskFileItemFactory() {
final int sizeThreshold = getSizeThreshold();
final File repository = createRepositoryFile();
return new DiskFileItemFactory(sizeThreshold, repository);
}
protected int getSizeThreshold() {
return ComponentUtil.getFessConfig().getHttpFileuploadThresholdSizeAsInteger().intValue();
}
protected File createRepositoryFile() {
return new File(getRepositoryPath());
}
protected String getRepositoryPath() {
final ServletContext servletContext = LaServletContextUtil.getServletContext();
final File tempDirFile = (File) servletContext.getAttribute(CONTEXT_TEMPDIR_KEY);
String tempDir = tempDirFile.getAbsolutePath();
if (tempDir == null || tempDir.length() == 0) {
tempDir = System.getProperty(JAVA_IO_TMPDIR_KEY);
}
return tempDir; // must be not null
}
// -----------------------------------------------------
// ServletFileUpload Settings
// --------------------------
protected ServletFileUpload newServletFileUpload(final DiskFileItemFactory fileItemFactory) {
return new ServletFileUpload(fileItemFactory) {
@Override
@ -112,6 +153,10 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
};
}
// #for_now jflute to suppress CVE-2014-0050 even if commons-fileupload is older
// than safety version (2024/09/08)
// but if you use safety version, this extension is basically unneeded (or you
// can use it as double check)
protected void checkBoundarySize(final String contentType, final byte[] boundary) {
final int boundarySize = boundary.length;
final int limitSize = getBoundaryLimitSize();
@ -130,12 +175,12 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
br.addNotice("Too long boundary size so treats it as 404.");
br.addItem("Advice");
br.addElement("Against for JVN14876762.");
br.addElement("Against for CVE-2014-0050 (JVN14876762).");
br.addElement("Boundary size is limited by Framework.");
br.addElement("Too long boundary is treated as 404 because it's thought of as attack.");
br.addElement("");
br.addElement("While, you can override the boundary limit size");
br.addElement(" in " + FessMultipartRequestHandler.class.getSimpleName() + ".");
br.addElement(" in " + getClass().getSimpleName() + ".");
br.addItem("Content Type");
br.addElement(contentType);
br.addItem("Boundary Size");
@ -143,31 +188,27 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
br.addItem("Limit Size");
br.addElement(limitSize);
final String msg = br.buildExceptionMessage();
throw new Forced404NotFoundException(msg, UserMessages.empty()); // heavy attack!? so give no page to tell wasted action
throw new Forced404NotFoundException(msg, UserMessages.empty()); // heavy attack!? so give no page to tell
// wasted action
}
protected DiskFileItemFactory createDiskFileItemFactory() {
final File repository = createRepositoryFile();
return new DiskFileItemFactory((int) getSizeThreshold(), repository);
protected void setupServletFileUpload(final ServletFileUpload upload, final HttpServletRequest request) {
upload.setHeaderEncoding(request.getCharacterEncoding());
upload.setSizeMax(getSizeMax());
upload.setFileCountMax(getFileCountMax()); // since commons-fileupload-1.5
}
protected File createRepositoryFile() {
return new File(getRepositoryPath());
protected long getSizeMax() {
return ComponentUtil.getFessConfig().getHttpFileuploadMaxSizeAsInteger().longValue();
}
protected long getFileCountMax() {
return ComponentUtil.getFessConfig().getHttpFileuploadMaxFileCountAsInteger().longValue();
}
// ===================================================================================
// Handling Parts
// ==============
protected void prepareElementsHash() {
elementsText = new Hashtable<>();
elementsFile = new Hashtable<>();
elementsAll = new Hashtable<>();
}
protected List<FileItem> parseRequest(final HttpServletRequest request, final ServletFileUpload upload) throws FileUploadException {
return upload.parseRequest(request);
}
// Parameter Mapping
// =================
protected void mappingParameter(final HttpServletRequest request, final List<FileItem> items) {
showFieldLoggingTitle();
for (final FileItem item : items) {
@ -184,8 +225,11 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
}
}
// -----------------------------------------------------
// Parameter Logging
// -----------------
// logging filter cannot show the parameters when multi-part so logging here
protected void showFieldLoggingTitle() {
// logging filter cannot show the parameters when multi-part so logging here
if (logger.isDebugEnabled()) {
logger.debug("[Multipart Request Parameter]");
}
@ -203,45 +247,11 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
}
}
protected void handleSizeLimitExceededException(final HttpServletRequest request, final SizeLimitExceededException e) {
final long actual = e.getActualSize();
final long permitted = e.getPermittedSize();
final String msg = "Exceeded size of the multipart request: actual=" + actual + " permitted=" + permitted;
request.setAttribute(MAX_LENGTH_EXCEEDED_KEY, new MultipartExceededException(msg, actual, permitted, e));
try {
final InputStream is = request.getInputStream();
try {
final byte[] buf = new byte[1024];
while ((is.read(buf)) != -1) {}
} catch (final Exception ignored) {} finally {
try {
is.close();
} catch (final Exception ignored) {}
}
} catch (final Exception ignored) {}
}
protected void handleFileUploadException(final FileUploadException e) throws ServletException {
// suppress logging because it can be caught by logging filter
//log.error("Failed to parse multipart request", e);
throw new ServletException("Failed to upload the file.", e);
}
// ===================================================================================
// Roll-back
// =========
@Override
public void rollback() {
for (final MultipartFormFile formFile : elementsFile.values()) {
formFile.destroy();
}
}
// ===================================================================================
// Add Text
// ========
// Add Parameter
// =============
protected void addTextParameter(final HttpServletRequest request, final FileItem item) {
final String name = item.getFieldName();
final String fieldName = item.getFieldName();
final String encoding = request.getCharacterEncoding();
String value = null;
boolean haveValue = false;
@ -260,9 +270,9 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
haveValue = true;
}
if (request instanceof final MultipartRequestWrapper wrapper) {
wrapper.setParameter(name, value);
wrapper.setParameter(fieldName, value);
}
final String[] oldArray = elementsText.get(name);
final String[] oldArray = elementsText.get(fieldName);
final String[] newArray;
if (oldArray != null) {
newArray = new String[oldArray.length + 1];
@ -271,20 +281,60 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
} else {
newArray = new String[] { value };
}
elementsText.put(name, newArray);
elementsAll.put(name, newArray);
elementsAll.put(fieldName, newArray);
elementsText.put(fieldName, newArray);
}
protected void addFileParameter(final FileItem item) {
final String fieldName = item.getFieldName();
final MultipartFormFile formFile = newActionMultipartFormFile(item);
elementsFile.put(item.getFieldName(), formFile);
elementsAll.put(item.getFieldName(), formFile);
elementsAll.put(fieldName, formFile);
elementsFile.put(fieldName, formFile);
}
protected ActionMultipartFormFile newActionMultipartFormFile(final FileItem item) {
return new ActionMultipartFormFile(item);
}
// ===================================================================================
// Exception Handling
// ==================
protected void handleSizeLimitExceededException(final HttpServletRequest request, final SizeLimitExceededException e) {
final long actual = e.getActualSize();
final long permitted = e.getPermittedSize();
final String msg = "Exceeded size of the multipart request: actual=" + actual + " permitted=" + permitted;
request.setAttribute(MAX_LENGTH_EXCEEDED_KEY, new MultipartExceededException(msg, actual, permitted, e));
try {
final InputStream is = request.getInputStream();
try {
final byte[] buf = new byte[1024];
@SuppressWarnings("unused")
int len = 0;
while ((len = is.read(buf)) != -1) {}
} catch (final Exception ignored) {} finally {
try {
is.close();
} catch (final Exception ignored) {}
}
} catch (final Exception ignored) {}
}
protected void handleFileUploadException(final FileUploadException e) throws ServletException {
// suppress logging because it can be caught by logging filter
// log.error("Failed to parse multipart request", e);
throw new ServletException("Failed to upload the file.", e);
}
// ===================================================================================
// Roll-back
// =========
@Override
public void rollback() {
for (final MultipartFormFile formFile : elementsFile.values()) {
formFile.destroy();
}
}
// ===================================================================================
// Finish
// ======
@ -293,26 +343,6 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
rollback();
}
// ===================================================================================
// Small Helper
// ============
protected long getSizeMax() {
return ComponentUtil.getFessConfig().getHttpFileuploadMaxSizeAsInteger();
}
protected long getSizeThreshold() {
return ComponentUtil.getFessConfig().getHttpFileuploadThresholdSizeAsInteger();
}
protected String getRepositoryPath() {
final File tempDirFile = (File) LaServletContextUtil.getServletContext().getAttribute(CONTEXT_TEMPDIR_KEY);
String tempDir = tempDirFile.getAbsolutePath();
if (tempDir == null || tempDir.length() == 0) {
tempDir = System.getProperty(JAVA_IO_TMPDIR_KEY);
}
return tempDir;
}
// ===================================================================================
// Form File
// =========
@ -353,11 +383,11 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
protected String getBaseFileName(final String filePath) {
final String fileName = new File(filePath).getName();
int colonIndex = fileName.indexOf(':');
int colonIndex = fileName.indexOf(":");
if (colonIndex == -1) {
colonIndex = fileName.indexOf("\\\\"); // Windows SMB
}
final int backslashIndex = fileName.lastIndexOf('\\');
final int backslashIndex = fileName.lastIndexOf("\\");
if (colonIndex > -1 && backslashIndex > -1) {
return fileName.substring(backslashIndex + 1);
}
@ -379,17 +409,17 @@ public class FessMultipartRequestHandler implements MultipartRequestHandler {
// Accessor
// ========
@Override
public Map<String, Object> getAllElements() {
public Map<String, Object> getAllElements() { // not null after parsing
return elementsAll;
}
@Override
public Map<String, String[]> getTextElements() {
public Map<String, String[]> getTextElements() { // me too
return elementsText;
}
@Override
public Map<String, MultipartFormFile> getFileElements() {
public Map<String, MultipartFormFile> getFileElements() { // me too
return elementsFile;
}
}

View file

@ -195,6 +195,7 @@ http.proxy.username=
http.proxy.password=
http.fileupload.max.size=262144000
http.fileupload.threshold.size=262144
http.fileupload.max.file.count=10
# ========================================================================================
# Index