Merge pull request #808 from apache/feature/WW-5371-modern-upload

[WW-5371] Modern upload
This commit is contained in:
Lukasz Lenart
2023-12-15 10:06:35 +01:00
committed by GitHub
16 changed files with 1414 additions and 417 deletions
@@ -21,75 +21,67 @@
package org.apache.struts2.showcase.fileupload;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.action.UploadedFilesAware;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import java.io.File;
import java.util.List;
/**
* Show case File Upload example's action. <code>FileUploadAction</code>
*/
public class FileUploadAction extends ActionSupport {
public class FileUploadAction extends ActionSupport implements UploadedFilesAware {
private static final long serialVersionUID = 5156288255337069381L;
private String contentType;
private UploadedFile uploadedFile;
private String fileName;
private String caption;
private String originalName;
private String contentType;
private File upload;
private String fileName;
private String caption;
public String input() throws Exception {
return SUCCESS;
}
public String input() throws Exception {
return SUCCESS;
}
public String upload() throws Exception {
return SUCCESS;
}
public String upload() throws Exception {
return SUCCESS;
}
public String getContentType() {
return contentType;
}
// since we are using <s:file name="upload" .../> the file name will be
// obtained through getter/setter of <file-tag-name>FileName
public String getUploadFileName() {
return fileName;
}
public String getFileName() {
return fileName;
}
public void setUploadFileName(String fileName) {
this.fileName = fileName;
}
public String getOriginalName() {
return originalName;
}
public Object getUploadedFile() {
return uploadedFile.getContent();
}
// since we are using <s:file name="upload" ... /> the content type will be
// obtained through getter/setter of <file-tag-name>ContentType
public String getUploadContentType() {
return contentType;
}
public String getCaption() {
return caption;
}
public void setUploadContentType(String contentType) {
this.contentType = contentType;
}
public void setCaption(String caption) {
this.caption = caption;
}
// since we are using <s:file name="upload" ... /> the File itself will be
// obtained through getter/setter of <file-tag-name>
public File getUpload() {
return upload;
}
public void setUpload(File upload) {
this.upload = upload;
}
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
public long getUploadSize() {
if (upload != null) {
return upload.length();
} else {
return 0;
}
public long getUploadSize() {
if (uploadedFile != null) {
return uploadedFile.length();
} else {
return 0;
}
}
@Override
public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
this.uploadedFile = uploadedFiles.get(0);
this.fileName = uploadedFile.getName();
this.contentType = uploadedFile.getContentType();
this.originalName = uploadedFile.getOriginalName();
}
}
@@ -19,7 +19,7 @@
*/
-->
<%@ page
language="java"
language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
@@ -37,9 +37,10 @@
<div class="row">
<div class="col-md-12">
<ul>
<li>ContentType: <s:property value="uploadContentType" /></li>
<li>FileName: <s:property value="uploadFileName" /></li>
<li>File: <s:property value="upload" /></li>
<li>ContentType: <s:property value="contentType" /></li>
<li>FileName: <s:property value="fileName" /></li>
<li>Original FileName: <s:property value="originalName" /></li>
<li>File: <s:property value="uploadedFile" /></li>
<li>Caption:<s:property value="caption" /></li>
</ul>
</div>
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.action;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import java.util.List;
/**
* Actions that want to be aware of all the uploaded file should implement this interface.
* The {@link org.apache.struts2.interceptor.ActionFileUploadInterceptor} will use the interface
* to notify action about the multiple uploaded files.
*/
public interface UploadedFilesAware {
/**
* Notifies action about the multiple uploaded files, when a single file is uploaded
* the list will have just one element
*
* @param uploadedFiles a list of {@link UploadedFile}, cannot be null. It can be empty.
*/
void withUploadedFiles(List<UploadedFile> uploadedFiles);
}
@@ -243,7 +243,11 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
LOG.error("Cannot write uploaded empty file to disk: {}", storeLocation.getAbsolutePath(), e);
}
}
fileList.add(new StrutsUploadedFile(storeLocation));
UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(storeLocation)
.withContentType(fileItem.getContentType())
.withOriginalName(fileItem.getName())
.build();
fileList.add(uploadedFile);
}
return fileList.toArray(new UploadedFile[0]);
@@ -22,6 +22,7 @@ import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.apache.logging.log4j.LogManager;
@@ -108,12 +109,12 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
return null;
}
List<UploadedFile> files = new ArrayList<>(infos.size());
for (FileInfo fileInfo : infos) {
files.add(new StrutsUploadedFile(fileInfo.getFile()));
}
return files.toArray(new UploadedFile[0]);
return infos.stream().map(fileInfo ->
StrutsUploadedFile.Builder.create(fileInfo.getFile())
.withContentType(fileInfo.contentType)
.withOriginalName(fileInfo.originalName)
.build()
).toArray(UploadedFile[]::new);
}
/* (non-Javadoc)
@@ -162,7 +163,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
*/
public String getParameter(String name) {
List<String> values = parameters.get(name);
if (values != null && values.size() > 0) {
if (values != null && !values.isEmpty()) {
return values.get(0);
}
return null;
@@ -180,7 +181,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
*/
public String[] getParameterValues(String name) {
List<String> values = parameters.get(name);
if (values != null && values.size() > 0) {
if (values != null && !values.isEmpty()) {
return values.toArray(new String[0]);
}
return null;
@@ -207,9 +208,8 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
*
* @param request the servlet request
* @param saveDir location of the save dir
* @throws Exception
*/
protected void processUpload(HttpServletRequest request, String saveDir) throws Exception {
protected void processUpload(HttpServletRequest request, String saveDir) throws IOException, FileUploadException {
// Sanity check that the request is a multi-part/form-data request.
if (ServletFileUpload.isMultipartContent(request)) {
@@ -292,7 +292,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* Add a file skipped message notification for action messages.
*
* @param fileName file name
* @param request the servlet request
* @param request the servlet request
*/
protected void addFileSkippedError(String fileName, HttpServletRequest request) {
String exceptionMessage = "Skipped file " + fileName + "; request size limit exceeded.";
@@ -330,11 +330,11 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* Processes the FileItemStream as a file field.
*
* @param itemStream file item stream
* @param location location
* @param location location
*/
protected void processFileItemStreamAsFileField(FileItemStream itemStream, String location) {
// Skip file uploads that don't have a file name - meaning that no file was selected.
if (itemStream.getName() == null || itemStream.getName().trim().length() < 1) {
if (itemStream.getName() == null || itemStream.getName().trim().isEmpty()) {
LOG.debug("No file has been uploaded for the field: {}", itemStream.getFieldName());
return;
}
@@ -368,8 +368,8 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
*/
protected File createTemporaryFile(String fileName, String location) throws IOException {
String name = fileName
.substring(fileName.lastIndexOf('/') + 1)
.substring(fileName.lastIndexOf('\\') + 1);
.substring(fileName.lastIndexOf('/') + 1)
.substring(fileName.lastIndexOf('\\') + 1);
String prefix = name;
String suffix = "";
@@ -392,14 +392,14 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* Streams the file upload stream to the specified file.
*
* @param itemStream file item stream
* @param file the file
* @param file the file
* @return true if stream was successfully
* @throws IOException in case of IO errors
*/
protected boolean streamFileToDisk(FileItemStream itemStream, File file) throws IOException {
boolean result;
try (InputStream input = itemStream.openStream();
OutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()), bufferSize)) {
OutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()), bufferSize)) {
byte[] buffer = new byte[bufferSize];
LOG.debug("Streaming file using buffer size {}.", bufferSize);
for (int length; ((length = input.read(buffer)) > 0); ) {
@@ -416,7 +416,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* invocation process.
*
* @param itemStream file item stream
* @param file the file
* @param file the file
*/
protected void createFileInfoFromItemStream(FileItemStream itemStream, File file) {
// gather attributes from file upload stream.
@@ -440,7 +440,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
*
* @since 7.0.0
*/
public static class FileInfo implements Serializable {
public static class FileInfo implements Serializable {
private static final long serialVersionUID = 1083158552766906037L;
@@ -451,8 +451,8 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
/**
* Default constructor.
*
* @param file the file
* @param contentType content type
* @param file the file
* @param contentType content type
* @param originalName original file name
*/
public FileInfo(File file, String contentType, String originalName) {
@@ -22,10 +22,26 @@ import java.io.File;
public class StrutsUploadedFile implements UploadedFile {
private File file;
private final File file;
private final String contentType;
private final String originalName;
/**
* Use builder instead of constructor
* @param file an uploaded file
* @deprecated since Struts 6.4.0
*/
@Deprecated
public StrutsUploadedFile(File file) {
this.file = file;
this.contentType = null;
this.originalName = null;
}
private StrutsUploadedFile(File file, String contentType, String originalName) {
this.file = file;
this.contentType = contentType;
this.originalName = originalName;
}
@Override
@@ -57,4 +73,50 @@ public class StrutsUploadedFile implements UploadedFile {
public File getContent() {
return file;
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public String getOriginalName() {
return originalName;
}
@Override
public String toString() {
return "StrutsUploadedFile{" +
"contentType='" + contentType + '\'' +
", originalName='" + originalName + '\'' +
'}';
}
public static class Builder {
private final File file;
private String contentType;
private String originalName;
private Builder(File file) {
this.file = file;
}
public static Builder create(File file) {
return new Builder(file);
}
public Builder withContentType(String contentType) {
this.contentType = contentType;
return this;
}
public Builder withOriginalName(String originalName) {
this.originalName = originalName;
return this;
}
public UploadedFile build() {
return new StrutsUploadedFile(this.file, this.contentType, this.originalName);
}
}
}
@@ -18,15 +18,19 @@
*/
package org.apache.struts2.dispatcher.multipart;
import java.io.Serializable;
/**
* Virtual representation of a uploaded file used by {@link MultiPartRequest}
*/
public interface UploadedFile {
public interface UploadedFile extends Serializable {
Long length();
String getName();
String getOriginalName();
boolean isFile();
boolean delete();
@@ -35,4 +39,6 @@ public interface UploadedFile {
Object getContent();
String getContentType();
}
@@ -0,0 +1,263 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.TextProviderFactory;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.ValidationAware;
import com.opensymphony.xwork2.util.TextParseUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.dispatcher.LocalizedMessage;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import org.apache.struts2.util.ContentTypeMatcher;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public abstract class AbstractFileUploadInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(AbstractFileUploadInterceptor.class);
public static final String STRUTS_MESSAGES_BYPASS_REQUEST_KEY = "struts.messages.bypass.request";
public static final String STRUTS_MESSAGES_ERROR_UPLOADING_KEY = "struts.messages.error.uploading";
public static final String STRUTS_MESSAGES_ERROR_FILE_TOO_LARGE_KEY = "struts.messages.error.file.too.large";
public static final String STRUTS_MESSAGES_INVALID_FILE_KEY = "struts.messages.invalid.file";
public static final String STRUTS_MESSAGES_INVALID_CONTENT_TYPE_KEY = "struts.messages.invalid.content.type";
public static final String STRUTS_MESSAGES_ERROR_CONTENT_TYPE_NOT_ALLOWED_KEY = "struts.messages.error.content.type.not.allowed";
public static final String STRUTS_MESSAGES_ERROR_FILE_EXTENSION_NOT_ALLOWED_KEY = "struts.messages.error.file.extension.not.allowed";
private Long maximumSize;
private Set<String> allowedTypesSet = Collections.emptySet();
private Set<String> allowedExtensionsSet = Collections.emptySet();
private ContentTypeMatcher<Object> matcher;
private Container container;
@Inject
public void setMatcher(ContentTypeMatcher<Object> matcher) {
this.matcher = matcher;
}
@Inject
public void setContainer(Container container) {
this.container = container;
}
/**
* Sets the allowed extensions
*
* @param allowedExtensions A comma-delimited list of extensions
*/
public void setAllowedExtensions(String allowedExtensions) {
allowedExtensionsSet = TextParseUtil.commaDelimitedStringToSet(allowedExtensions);
}
/**
* Sets the allowed mimetypes
*
* @param allowedTypes A comma-delimited list of types
*/
public void setAllowedTypes(String allowedTypes) {
allowedTypesSet = TextParseUtil.commaDelimitedStringToSet(allowedTypes);
}
/**
* Sets the maximum size of an uploaded file
*
* @param maximumSize The maximum size in bytes
*/
public void setMaximumSize(Long maximumSize) {
this.maximumSize = maximumSize;
}
/**
* Override for added functionality. Checks if the proposed file is acceptable based on contentType and size.
*
* @param action - uploading action for message retrieval.
* @param file - proposed upload file.
* @param originalFilename - name of the file.
* @param contentType - contentType of the file.
* @param inputName - inputName of the file.
* @return true if the proposed file is acceptable by contentType and size.
*/
protected boolean acceptFile(Object action, UploadedFile file, String originalFilename, String contentType, String inputName) {
Set<String> errorMessages = new HashSet<>();
ValidationAware validation = null;
if (action instanceof ValidationAware) {
validation = (ValidationAware) action;
}
// If it's null the upload failed
if (file == null) {
String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_UPLOADING_KEY, new String[]{inputName});
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.warn(errMsg);
return false;
}
if (file.getContent() == null) {
String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_UPLOADING_KEY, new String[]{originalFilename});
errorMessages.add(errMsg);
LOG.warn(errMsg);
}
if (maximumSize != null && maximumSize < file.length()) {
String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_FILE_TOO_LARGE_KEY, new String[]{
inputName, originalFilename, file.getName(), "" + file.length(), getMaximumSizeStr(action)
});
errorMessages.add(errMsg);
LOG.warn(errMsg);
}
if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) {
String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_CONTENT_TYPE_NOT_ALLOWED_KEY, new String[]{
inputName, originalFilename, file.getName(), contentType
});
errorMessages.add(errMsg);
LOG.warn(errMsg);
}
if ((!allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, originalFilename))) {
String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_FILE_EXTENSION_NOT_ALLOWED_KEY, new String[]{
inputName, originalFilename, file.getName(), contentType
});
errorMessages.add(errMsg);
LOG.warn(errMsg);
}
if (validation != null) {
for (String errorMsg : errorMessages) {
validation.addFieldError(inputName, errorMsg);
}
}
return errorMessages.isEmpty();
}
private String getMaximumSizeStr(Object action) {
return NumberFormat.getNumberInstance(getLocaleProvider(action).getLocale()).format(maximumSize);
}
/**
* @param extensionCollection - Collection of extensions (all lowercase).
* @param filename - filename to check.
* @return true if the filename has an allowed extension, false otherwise.
*/
private boolean hasAllowedExtension(Collection<String> extensionCollection, String filename) {
if (filename == null) {
return false;
}
String lowercaseFilename = filename.toLowerCase();
for (String extension : extensionCollection) {
if (lowercaseFilename.endsWith(extension)) {
return true;
}
}
return false;
}
/**
* @param itemCollection - Collection of string items (all lowercase).
* @param item - Item to search for.
* @return true if itemCollection contains the item, false otherwise.
*/
private boolean containsItem(Collection<String> itemCollection, String item) {
for (String pattern : itemCollection)
if (matchesWildcard(pattern, item))
return true;
return false;
}
private boolean matchesWildcard(String pattern, String text) {
Object o = matcher.compilePattern(pattern);
return matcher.match(new HashMap<>(), text, o);
}
protected boolean isNonEmpty(Object[] objArray) {
boolean result = false;
for (Object o : objArray) {
if (o != null) {
result = true;
break;
}
}
return result;
}
protected String getTextMessage(String messageKey, String[] args) {
return getTextMessage(this, messageKey, args);
}
protected String getTextMessage(Object action, String messageKey, String[] args) {
if (action instanceof TextProvider) {
return ((TextProvider) action).getText(messageKey, args);
}
return getTextProvider(action).getText(messageKey, args);
}
protected TextProvider getTextProvider(Object action) {
TextProviderFactory tpf = container.getInstance(TextProviderFactory.class);
return tpf.createInstance(action.getClass());
}
private LocaleProvider getLocaleProvider(Object action) {
LocaleProvider localeProvider;
if (action instanceof LocaleProvider) {
localeProvider = (LocaleProvider) action;
} else {
LocaleProviderFactory localeProviderFactory = container.getInstance(LocaleProviderFactory.class);
localeProvider = localeProviderFactory.createLocaleProvider();
}
return localeProvider;
}
protected void applyValidation(Object action, MultiPartRequestWrapper multiWrapper) {
ValidationAware validation = null;
if (action instanceof ValidationAware) {
validation = (ValidationAware) action;
}
if (multiWrapper.hasErrors() && validation != null) {
TextProvider textProvider = getTextProvider(action);
for (LocalizedMessage error : multiWrapper.getErrors()) {
String errorMessage;
if (textProvider.hasKey(error.getTextKey())) {
errorMessage = textProvider.getText(error.getTextKey(), Arrays.asList(error.getArgs()));
} else {
errorMessage = textProvider.getText(STRUTS_MESSAGES_ERROR_UPLOADING_KEY, error.getDefaultMessage());
}
validation.addActionError(errorMessage);
}
}
}
}
@@ -0,0 +1,191 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.interceptor.ValidationAware;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.action.UploadedFilesAware;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* <p>
* Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically applied for any request that
* includes a file when the support for multi-part request is enabled,
* see <a href="https://struts.apache.org/core-developers/file-upload.html#disabling-file-upload-support">Disabling file upload</a>.
* </p>
*
* <p>
* You can get access to these files by implementing {@link UploadedFilesAware} interface. The interceptor will then
* call {@link UploadedFilesAware#withUploadedFiles(List)} when there are files which were accepted during the upload process.
* </p>
*
* <p>
* This interceptor will add several field errors, assuming that the action implements {@link ValidationAware}.
* These error messages are based on several i18n values stored in struts-messages.properties, a default i18n file
* processed for all i18n requests. You can override the text of these messages by providing text for the following
* keys:
* </p>
*
* <ul>
* <li>struts.messages.error.uploading - a general error that occurs when the file could not be uploaded</li>
* <li>struts.messages.error.file.too.large - occurs when the uploaded file is too large</li>
* <li>struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected
* content types specified</li>
* <li>struts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not match the expected
* file extensions specified</li>
* </ul>
*
* <p>Interceptor parameters:</p>
* <ul>
* <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set
* on the action. Note, this is <b>not</b> related to the various properties found in struts.properties.
* Default to approximately 2MB.</li>
* <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow
* a file reference to be set on the action. If none is specified allow all types to be uploaded.</li>
* <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow
* a file reference to be set on the action. If none is specified allow all extensions to be uploaded.</li>
* </ul>
*
* <p>Example code:</p>
*
* <pre>
* &lt;action name="doUpload" class="com.example.UploadAction"&gt;
* &lt;interceptor-ref name="actionFileUpload"/&gt;
* &lt;interceptor-ref name="basicStack"/&gt;
* &lt;result name="success"&gt;good_result.jsp&lt;/result&gt;
* &lt;/action&gt;
* </pre>
* <p>
* <p>
* You must set the encoding to <code>multipart/form-data</code> in the form where the user selects the file to upload.
* </p>
* <pre>
* &lt;s:form action="doUpload" method="post" enctype="multipart/form-data"&gt;
* &lt;s:file name="upload" label="File"/&gt;
* &lt;s:submit/&gt;
* &lt;/s:form&gt;
* </pre>
* <p>
* And then in your action code you'll have access to the File object if you provide setters according to the
* naming convention documented in the start.
* </p>
*
* <pre>
* package com.example;
*
* import java.io.File;
* import com.opensymphony.xwork2.ActionSupport;
* import org.apache.struts2.action.UploadedFilesAware;
*
* public UploadAction extends ActionSupport implements UploadedFilesAware {
* private UploadedFile uploadedFile;
* private String contentType;
* private String fileName;
* private String originalName;
*
* &#064;Override
* public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
* if (!uploadedFiles.isEmpty() > 0) {
* this.uploadedFile = uploadedFiles.get(0);
* this.fileName = uploadedFile.getName();
* this.contentType = uploadedFile.getContentType();
* this.originalName = uploadedFile.getOriginalName();
* }
* }
*
* public String execute() {
* //...
* return SUCCESS;
* }
* }
* </pre>
*/
public class ActionFileUploadInterceptor extends AbstractFileUploadInterceptor {
protected static final Logger LOG = LogManager.getLogger(ActionFileUploadInterceptor.class);
/* (non-Javadoc)
* @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
*/
public String intercept(ActionInvocation invocation) throws Exception {
HttpServletRequest request = invocation.getInvocationContext().getServletRequest();
if (!(request instanceof MultiPartRequestWrapper)) {
if (LOG.isDebugEnabled()) {
ActionProxy proxy = invocation.getProxy();
LOG.debug(getTextMessage(STRUTS_MESSAGES_BYPASS_REQUEST_KEY, new String[]{proxy.getNamespace(), proxy.getActionName()}));
}
return invocation.invoke();
}
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
if (!(invocation.getAction() instanceof UploadedFilesAware)) {
LOG.debug("Action: {} doesn't implement: {}, ignoring file upload",
invocation.getProxy().getActionName(),
UploadedFilesAware.class.getSimpleName());
return invocation.invoke();
}
UploadedFilesAware action = (UploadedFilesAware) invocation.getAction();
applyValidation(action, multiWrapper);
// bind allowed Files
Enumeration<String> fileParameterNames = multiWrapper.getFileParameterNames();
List<UploadedFile> acceptedFiles = new ArrayList<>();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
// get the value of this input tag
String inputName = fileParameterNames.nextElement();
UploadedFile[] uploadedFiles = multiWrapper.getFiles(inputName);
if (uploadedFiles == null || uploadedFiles.length == 0) {
if (LOG.isWarnEnabled()) {
LOG.warn(getTextMessage(action, STRUTS_MESSAGES_INVALID_FILE_KEY, new String[]{inputName}));
}
} else {
for (UploadedFile uploadedFile : uploadedFiles) {
if (acceptFile(action, uploadedFile, uploadedFile.getOriginalName(), uploadedFile.getContentType(), inputName)) {
acceptedFiles.add(uploadedFile);
}
}
}
}
if (acceptedFiles.isEmpty()) {
LOG.debug("No files have been uploaded/accepted");
} else {
LOG.debug("Passing: {} uploaded file(s) to action", acceptedFiles.size());
action.withUploadedFiles(acceptedFiles);
}
// invoke action
return invocation.invoke();
}
}
@@ -18,23 +18,22 @@
*/
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.*;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.interceptor.ValidationAware;
import com.opensymphony.xwork2.util.TextParseUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.dispatcher.LocalizedMessage;
import org.apache.struts2.dispatcher.Parameter;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import org.apache.struts2.util.ContentTypeMatcher;
import javax.servlet.http.HttpServletRequest;
import java.text.NumberFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <!-- START SNIPPET: description -->
@@ -77,11 +76,11 @@ import java.util.*;
* file extensions specified</li>
*
* </ul>
*
* <p>
* <!-- END SNIPPET: description -->
*
* <p><u>Interceptor parameters:</u></p>
*
* <p>
* <!-- START SNIPPET: parameters -->
*
* <ul>
@@ -96,14 +95,14 @@ import java.util.*;
* <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow
* a file reference to be set on the action. If none is specified allow all extensions to be uploaded.</li>
* </ul>
*
*
* <p>
* <p>
* <!-- END SNIPPET: parameters -->
*
* <p><u>Extending the interceptor:</u></p>
*
*
*
* <p>
* <p>
* <p>
* <!-- START SNIPPET: extending -->
* <p>
* You can extend this interceptor and override the acceptFile method to provide more control over which files
@@ -122,7 +121,7 @@ import java.util.*;
* &lt;/action&gt;
* <!-- END SNIPPET: example-configuration -->
* </pre>
*
* <p>
* <!-- START SNIPPET: multipart-note -->
* <p>
* You must set the encoding to <code>multipart/form-data</code> in the form where the user selects the file to upload.
@@ -173,57 +172,16 @@ import java.util.*;
* }
* <!-- END SNIPPET: example-action -->
* </pre>
*
* @deprecated since Struts 6.4.0, use {@link ActionFileUploadInterceptor} instead
*/
public class FileUploadInterceptor extends AbstractInterceptor {
@Deprecated
public class FileUploadInterceptor extends AbstractFileUploadInterceptor {
private static final long serialVersionUID = -4764627478894962478L;
protected static final Logger LOG = LogManager.getLogger(FileUploadInterceptor.class);
protected Long maximumSize;
protected Set<String> allowedTypesSet = Collections.emptySet();
protected Set<String> allowedExtensionsSet = Collections.emptySet();
private ContentTypeMatcher matcher;
private Container container;
@Inject
public void setMatcher(ContentTypeMatcher matcher) {
this.matcher = matcher;
}
@Inject
public void setContainer(Container container) {
this.container = container;
}
/**
* Sets the allowed extensions
*
* @param allowedExtensions A comma-delimited list of extensions
*/
public void setAllowedExtensions(String allowedExtensions) {
allowedExtensionsSet = TextParseUtil.commaDelimitedStringToSet(allowedExtensions);
}
/**
* Sets the allowed mimetypes
*
* @param allowedTypes A comma-delimited list of types
*/
public void setAllowedTypes(String allowedTypes) {
allowedTypesSet = TextParseUtil.commaDelimitedStringToSet(allowedTypes);
}
/**
* Sets the maximum size of an uploaded file
*
* @param maximumSize The maximum size in bytes
*/
public void setMaximumSize(Long maximumSize) {
this.maximumSize = maximumSize;
}
/* (non-Javadoc)
* @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
*/
@@ -236,40 +194,22 @@ public class FileUploadInterceptor extends AbstractInterceptor {
if (!(request instanceof MultiPartRequestWrapper)) {
if (LOG.isDebugEnabled()) {
ActionProxy proxy = invocation.getProxy();
LOG.debug(getTextMessage("struts.messages.bypass.request", new String[]{proxy.getNamespace(), proxy.getActionName()}));
LOG.debug(getTextMessage(STRUTS_MESSAGES_BYPASS_REQUEST_KEY, new String[]{proxy.getNamespace(), proxy.getActionName()}));
}
return invocation.invoke();
}
ValidationAware validation = null;
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
validation = (ValidationAware) action;
}
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
if (multiWrapper.hasErrors() && validation != null) {
TextProvider textProvider = getTextProvider(action);
for (LocalizedMessage error : multiWrapper.getErrors()) {
String errorMessage;
if (textProvider.hasKey(error.getTextKey())) {
errorMessage = textProvider.getText(error.getTextKey(), Arrays.asList(error.getArgs()));
} else {
errorMessage = textProvider.getText("struts.messages.error.uploading", error.getDefaultMessage());
}
validation.addActionError(errorMessage);
}
}
applyValidation(action, multiWrapper);
// bind allowed Files
Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
Enumeration<String> fileParameterNames = multiWrapper.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
// get the value of this input tag
String inputName = (String) fileParameterNames.nextElement();
String inputName = fileParameterNames.nextElement();
// get the content type
String[] contentType = multiWrapper.getContentTypes(inputName);
@@ -289,7 +229,7 @@ public class FileUploadInterceptor extends AbstractInterceptor {
String fileNameName = inputName + "FileName";
for (int index = 0; index < files.length; index++) {
if (acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation)) {
if (acceptFile(action, files[index], fileName[index], contentType[index], inputName)) {
acceptedFiles.add(files[index]);
acceptedContentTypes.add(contentType[index]);
acceptedFileNames.add(fileName[index]);
@@ -298,20 +238,20 @@ public class FileUploadInterceptor extends AbstractInterceptor {
if (!acceptedFiles.isEmpty()) {
Map<String, Parameter> newParams = new HashMap<>();
newParams.put(inputName, new Parameter.File(inputName, acceptedFiles.toArray(new UploadedFile[acceptedFiles.size()])));
newParams.put(contentTypeName, new Parameter.File(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()])));
newParams.put(fileNameName, new Parameter.File(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()])));
newParams.put(inputName, new Parameter.File(inputName, acceptedFiles.toArray(new UploadedFile[0])));
newParams.put(contentTypeName, new Parameter.File(contentTypeName, acceptedContentTypes.toArray(new String[0])));
newParams.put(fileNameName, new Parameter.File(fileNameName, acceptedFileNames.toArray(new String[0])));
ac.getParameters().appendAll(newParams);
}
}
} else {
if (LOG.isWarnEnabled()) {
LOG.warn(getTextMessage(action, "struts.messages.invalid.file", new String[]{inputName}));
LOG.warn(getTextMessage(action, STRUTS_MESSAGES_INVALID_FILE_KEY, new String[]{inputName}));
}
}
} else {
if (LOG.isWarnEnabled()) {
LOG.warn(getTextMessage(action, "struts.messages.invalid.content.type", new String[]{inputName}));
LOG.warn(getTextMessage(action, STRUTS_MESSAGES_INVALID_CONTENT_TYPE_KEY, new String[]{inputName}));
}
}
}
@@ -320,149 +260,4 @@ public class FileUploadInterceptor extends AbstractInterceptor {
return invocation.invoke();
}
/**
* Override for added functionality. Checks if the proposed file is acceptable based on contentType and size.
*
* @param action - uploading action for message retrieval.
* @param file - proposed upload file.
* @param filename - name of the file.
* @param contentType - contentType of the file.
* @param inputName - inputName of the file.
* @param validation - Non-null ValidationAware if the action implements ValidationAware, allowing for better
* logging.
* @return true if the proposed file is acceptable by contentType and size.
*/
protected boolean acceptFile(Object action, UploadedFile file, String filename, String contentType, String inputName, ValidationAware validation) {
boolean fileIsAcceptable = false;
// If it's null the upload failed
if (file == null) {
String errMsg = getTextMessage(action, "struts.messages.error.uploading", new String[]{inputName});
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
if (LOG.isWarnEnabled()) {
LOG.warn(errMsg);
}
} else if (file.getContent() == null) {
String errMsg = getTextMessage(action, "struts.messages.error.uploading", new String[]{filename});
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
if (LOG.isWarnEnabled()) {
LOG.warn(errMsg);
}
} else if (maximumSize != null && maximumSize < file.length()) {
String errMsg = getTextMessage(action, "struts.messages.error.file.too.large", new String[]{inputName, filename, file.getName(), "" + file.length(), getMaximumSizeStr(action)});
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
if (LOG.isWarnEnabled()) {
LOG.warn(errMsg);
}
} else if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) {
String errMsg = getTextMessage(action, "struts.messages.error.content.type.not.allowed", new String[]{inputName, filename, file.getName(), contentType});
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
if (LOG.isWarnEnabled()) {
LOG.warn(errMsg);
}
} else if ((!allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, filename))) {
String errMsg = getTextMessage(action, "struts.messages.error.file.extension.not.allowed", new String[]{inputName, filename, file.getName(), contentType});
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
if (LOG.isWarnEnabled()) {
LOG.warn(errMsg);
}
} else {
fileIsAcceptable = true;
}
return fileIsAcceptable;
}
private String getMaximumSizeStr(Object action) {
return NumberFormat.getNumberInstance(getLocaleProvider(action).getLocale()).format(maximumSize);
}
/**
* @param extensionCollection - Collection of extensions (all lowercase).
* @param filename - filename to check.
* @return true if the filename has an allowed extension, false otherwise.
*/
private boolean hasAllowedExtension(Collection<String> extensionCollection, String filename) {
if (filename == null) {
return false;
}
String lowercaseFilename = filename.toLowerCase();
for (String extension : extensionCollection) {
if (lowercaseFilename.endsWith(extension)) {
return true;
}
}
return false;
}
/**
* @param itemCollection - Collection of string items (all lowercase).
* @param item - Item to search for.
* @return true if itemCollection contains the item, false otherwise.
*/
private boolean containsItem(Collection<String> itemCollection, String item) {
for (String pattern : itemCollection)
if (matchesWildcard(pattern, item))
return true;
return false;
}
private boolean matchesWildcard(String pattern, String text) {
Object o = matcher.compilePattern(pattern);
return matcher.match(new HashMap<String, String>(), text, o);
}
private boolean isNonEmpty(Object[] objArray) {
boolean result = false;
for (int index = 0; index < objArray.length && !result; index++) {
if (objArray[index] != null) {
result = true;
}
}
return result;
}
protected String getTextMessage(String messageKey, String[] args) {
return getTextMessage(this, messageKey, args);
}
protected String getTextMessage(Object action, String messageKey, String[] args) {
if (action instanceof TextProvider) {
return ((TextProvider) action).getText(messageKey, args);
}
return getTextProvider(action).getText(messageKey, args);
}
private TextProvider getTextProvider(Object action) {
TextProviderFactory tpf = container.getInstance(TextProviderFactory.class);
return tpf.createInstance(action.getClass());
}
private LocaleProvider getLocaleProvider(Object action) {
LocaleProvider localeProvider;
if (action instanceof LocaleProvider) {
localeProvider = (LocaleProvider) action;
} else {
LocaleProviderFactory localeProviderFactory = container.getInstance(LocaleProviderFactory.class);
localeProvider = localeProviderFactory.createLocaleProvider();
}
return localeProvider;
}
}
@@ -25,15 +25,40 @@ struts.internal.invalid.token=Form token {0} does not match the session token {1
struts.messages.bypass.request=Bypassing {0}/{1}
struts.messages.current.file=File {0} {1} {2} {3}
# 0 - input name
struts.messages.invalid.file=Could not find a Filename for {0}. Verify that a valid file was submitted.
# 0 - original filename
struts.messages.invalid.content.type=Could not find a Content-Type for {0}. Verify that a valid file was submitted.
struts.messages.removing.file=Removing file {0} {1}
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=The file is too large to be uploaded: {0} "{1}" "{2}" {3}
# 0 - input name
# 1 - original filename
# 2 - file name after uploading the file
# 3 - size of the uploaded files
# 4 - maximum allowed size
struts.messages.error.file.too.large=The file is too large to be uploaded: {0} "{1}" "{2}" has size {3} and allowed mx size is {4}
# 0 - input name
# 1 - original filename
# 2 - file name after uploading the file
# 3 - content type of the file
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}
# 0 - input name
# 1 - original filename
# 2 - file name after uploading the file
# 3 - content type of the file
struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}
# dedicated messages used to handle various problems with file upload - check {@link JakartaMultiPartRequest#parse(HttpServletRequest, String)}
# params depend on exception being handled
# FileUploadBase.SizeLimitExceededException:
# 0 - permitted size
# 1 - actual size
# FileUploadBase.FileSizeLimitExceededException
# 0 - file name
# 1 - permitted size
# 2 - actual size
# FileCountLimitExceededException
# 0 - limit
struts.messages.upload.error.SizeLimitExceededException=Request exceeded allowed size limit! Max size allowed is: {0} but request was: {1}!
struts.messages.upload.error.IOException=Error uploading: {0}!
@@ -58,6 +58,7 @@
<interceptor name="execAndWait" class="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"/>
<interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
<interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
<interceptor name="actionFileUpload" class="org.apache.struts2.interceptor.ActionFileUploadInterceptor"/>
<interceptor name="i18n" class="org.apache.struts2.interceptor.I18nInterceptor"/>
<interceptor name="logger" class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/>
<interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/>
@@ -121,6 +122,12 @@
<interceptor-ref name="basicStack"/>
</interceptor-stack>
<!-- Action based file upload stack -->
<interceptor-stack name="actionFileUploadStack">
<interceptor-ref name="actionFileUpload"/>
<interceptor-ref name="basicStack"/>
</interceptor-stack>
<!-- Sample model-driven stack -->
<interceptor-stack name="modelDrivenStack">
<interceptor-ref name="modelDriven"/>
@@ -165,6 +172,7 @@
<interceptor-ref name="chain"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="actionFileUpload"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params"/>
@@ -203,6 +211,7 @@
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="actionFileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="datetime"/>
<interceptor-ref name="multiselect"/>
@@ -34,10 +34,12 @@ import static org.assertj.core.api.Assertions.assertThat;
public class UploadedFileConverterTest {
private Map<String, Object> context;
private Class target;
private Class<?> target;
private Member member;
private String propertyName;
private File tempFile;
private String contentType;
private String originalName;
@Before
public void setUp() throws Exception {
@@ -46,6 +48,8 @@ public class UploadedFileConverterTest {
member = File.class.getMethod("length");
propertyName = "ignore";
tempFile = File.createTempFile("struts", "test");
contentType = "text/plain";
originalName = tempFile.getName();
}
@After
@@ -54,10 +58,10 @@ public class UploadedFileConverterTest {
}
@Test
public void convertUploadedFileToFile() throws Exception {
public void convertUploadedFileToFile() {
// given
UploadedFileConverter ufc = new UploadedFileConverter();
UploadedFile uploadedFile = new StrutsUploadedFile(tempFile);
UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(tempFile).withContentType(this.contentType).withOriginalName(this.originalName).build();
// when
Object result = ufc.convertValue(context, target, member, propertyName, uploadedFile, File.class);
@@ -70,10 +74,15 @@ public class UploadedFileConverterTest {
}
@Test
public void convertUploadedFileArrayToFile() throws Exception {
public void convertUploadedFileArrayToFile() {
// given
UploadedFileConverter ufc = new UploadedFileConverter();
UploadedFile[] uploadedFile = new UploadedFile[] { new StrutsUploadedFile(tempFile) };
UploadedFile[] uploadedFile = new UploadedFile[]{
StrutsUploadedFile.Builder.create(tempFile)
.withContentType(this.contentType)
.withOriginalName(this.originalName)
.build()
};
// when
Object result = ufc.convertValue(context, target, member, propertyName, uploadedFile, File.class);
@@ -0,0 +1,591 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.DefaultLocaleProvider;
import com.opensymphony.xwork2.ValidationAwareSupport;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.mock.MockActionProxy;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.action.UploadedFilesAware;
import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.StrutsUploadedFile;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import org.springframework.mock.web.MockHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test case for {@link ActionFileUploadInterceptor}.
*/
public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
public static final UploadedFile EMPTY_FILE = new UploadedFile() {
@Override
public Long length() {
return 0L;
}
@Override
public String getName() {
return "";
}
@Override
public boolean isFile() {
return false;
}
@Override
public boolean delete() {
return false;
}
@Override
public String getAbsolutePath() {
return null;
}
@Override
public byte[] getContent() {
return new byte[0];
}
@Override
public String getOriginalName() {
return null;
}
@Override
public String getContentType() {
return null;
}
};
private ActionFileUploadInterceptor interceptor;
private File tempDir;
private final String htmlContent = "<html><head></head><body>html content</body></html>";
private final String plainContent = "plain content";
private final String boundary = "simple boundary";
private final String endline = "\r\n";
public void testAcceptFileWithEmptyAllowedTypesAndExtensions() {
// when allowed type is empty
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
assertFalse(validation.hasErrors());
}
public void testAcceptFileWithoutEmptyTypes() {
interceptor.setAllowedTypes("text/plain");
// when file is of allowed types
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
assertFalse(validation.hasErrors());
// when file is not of allowed types
validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
}
public void testAcceptFileWithWildcardContent() {
interceptor.setAllowedTypes("text/*");
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
assertFalse(validation.hasErrors());
interceptor.setAllowedTypes("text/h*");
validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/plain", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
}
public void testAcceptFileWithoutEmptyExtensions() {
interceptor.setAllowedExtensions(".txt");
// when file is of allowed extensions
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
assertFalse(validation.hasErrors());
// when file is not of allowed extensions
validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
//test with multiple extensions
interceptor.setAllowedExtensions(".txt,.lol");
validation = new ValidationAwareSupport();
ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.lol", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
assertFalse(validation.hasErrors());
}
public void testAcceptFileWithNoFile() {
ActionFileUploadInterceptor interceptor = new ActionFileUploadInterceptor();
interceptor.setContainer(container);
interceptor.setAllowedTypes("text/plain");
// when file is not of allowed types
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(validation, null, "filename.html", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
List<String> errors = validation.getFieldErrors().get("inputName");
assertEquals(1, errors.size());
String msg = errors.get(0);
assertTrue(msg.startsWith("Error uploading:"));
assertTrue(msg.indexOf("inputName") > 0);
}
public void testAcceptFileWithMaxSize() throws Exception {
interceptor.setMaximumSize(10L);
// when file is not of allowed types
ValidationAwareSupport validation = new ValidationAwareSupport();
URL url = ClassLoaderUtil.getResource("log4j2.xml", ActionFileUploadInterceptorTest.class);
File file = new File(new URI(url.toString()));
assertTrue("log4j2.xml should be in src/test folder", file.exists());
UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(file).withContentType("text/html").withOriginalName("filename").build();
boolean notOk = interceptor.acceptFile(validation, uploadedFile, "filename", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
List<String> errors = validation.getFieldErrors().get("inputName");
assertEquals(1, errors.size());
String msg = errors.get(0);
// the error message should contain at least this test
assertThat(msg).contains(
"The file is too large to be uploaded",
"inputName",
"log4j2.xml",
"allowed mx size is 10"
);
}
public void testNoMultipartRequest() throws Exception {
MyFileUploadAction action = new MyFileUploadAction();
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("NoMultipart");
MockActionProxy proxy = new MockActionProxy();
proxy.setNamespace("/test");
proxy.setActionName("myFileUpload");
mai.setProxy(proxy);
mai.setInvocationContext(ActionContext.getContext());
// if no multipart request it will bypass and execute it
assertEquals("NoMultipart", interceptor.intercept(mai));
}
public void testInvalidContentTypeMultipartRequest() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setContentType("multipart/form-data"); // not a multipart contentype
req.setMethod("post");
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
interceptor.intercept(mai);
assertTrue(action.hasErrors());
}
public void testNoContentMultipartRequest() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("post");
req.addHeader("Content-type", "multipart/form-data");
req.setContent(null); // there is no content
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
interceptor.intercept(mai);
assertTrue(action.hasErrors());
}
public void testSuccessUploadOfATextFileMultipartRequest() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("post");
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
// inspired by the unit tests for jakarta commons fileupload
String content = ("-----1234\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"Unit test of ActionFileUploadInterceptor" +
"\r\n" +
"-----1234--\r\n");
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
MyFileUploadAction action = new MyFileUploadAction();
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
interceptor.intercept(mai);
assertFalse(action.hasErrors());
List<UploadedFile> files = action.getUploadFiles();
assertNotNull(files);
assertEquals(1, files.size());
assertEquals("text/html", files.get(0).getContentType());
assertNotNull("deleteme.txt", files.get(0).getOriginalName());
}
/**
* tests whether with multiple files sent with the same name, the ones with forbiddenTypes (see
* ActionFileUploadInterceptor.setAllowedTypes(...) ) are sorted out.
*/
public void testMultipleAccept() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("POST");
req.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
String content = encodeTextFile("test.html", "text/plain", plainContent) +
encodeTextFile("test1.html", "text/html", htmlContent) +
encodeTextFile("test2.html", "text/html", htmlContent) +
endline +
endline +
endline +
"--" +
boundary +
"--" +
endline;
req.setContent(content.getBytes());
assertTrue(ServletFileUpload.isMultipartContent(req));
MyFileUploadAction action = new MyFileUploadAction();
container.inject(action);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
interceptor.setAllowedTypes("text/html");
interceptor.intercept(mai);
List<UploadedFile> files = action.getUploadFiles();
assertNotNull(files);
assertEquals("files accepted ", 2, files.size());
assertEquals("text/html", files.get(0).getContentType());
assertNotNull("test1.html", files.get(0).getOriginalName());
}
public void testUnacceptedNumberOfFiles() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("POST");
req.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
String content = encodeTextFile("test.html", "text/plain", plainContent) +
encodeTextFile("test1.html", "text/html", htmlContent) +
encodeTextFile("test2.html", "text/html", htmlContent) +
encodeTextFile("test3.html", "text/html", htmlContent) +
endline +
"--" +
boundary +
"--" +
endline;
req.setContent(content.getBytes());
assertTrue(ServletFileUpload.isMultipartContent(req));
MyFileUploadAction action = new MyFileUploadAction();
container.inject(action);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext().withServletRequest(createMultipartRequestMaxFiles(req));
interceptor.setAllowedTypes("text/html");
interceptor.intercept(mai);
assertNull(action.getUploadFiles());
assertEquals(1, action.getActionErrors().size());
assertEquals("Request exceeded allowed number of files! Max allowed files number is: 3!", action.getActionErrors().iterator().next());
}
public void testMultipartRequestMaxFileSize() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("post");
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
// inspired by the unit tests for jakarta commons fileupload
String content = ("-----1234\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"Unit test of ActionFileUploadInterceptor" +
"\r\n" +
"-----1234--\r\n");
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext()
.withServletRequest(createMultipartRequestMaxFileSize(req));
interceptor.intercept(mai);
assertTrue(action.hasActionErrors());
Collection<String> errors = action.getActionErrors();
assertEquals(1, errors.size());
String msg = errors.iterator().next();
assertEquals(
"File in request exceeded allowed file size limit! Max file size allowed is: 10 but file deleteme.txt was: 40!",
msg);
}
public void testMultipartRequestMaxStringLength() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("post");
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
// inspired by the unit tests for jakarta commons fileupload
String content = ("-----1234\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"Unit test of ActionFileUploadInterceptor" +
"\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"normalFormField1\"\r\n" +
"\r\n" +
"it works" +
"\r\n" +
"-----1234\r\n" +
"Content-Disposition: form-data; name=\"normalFormField2\"\r\n" +
"\r\n" +
"long string should not work" +
"\r\n" +
"-----1234--\r\n");
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext()
.withServletRequest(createMultipartRequestMaxStringLength(req));
interceptor.intercept(mai);
assertTrue(action.hasActionErrors());
Collection<String> errors = action.getActionErrors();
assertEquals(1, errors.size());
String msg = errors.iterator().next();
assertEquals(
"The request parameter \"normalFormField2\" was too long. Max length allowed is 20, but found 27!",
msg);
}
public void testMultipartRequestLocalizedError() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("post");
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
// inspired by the unit tests for jakarta commons fileupload
String content = ("-----1234\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"Unit test of ActionFileUploadInterceptor" +
"\r\n" +
"-----1234--\r\n");
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext()
.withLocale(Locale.GERMAN)
.withServletRequest(createMultipartRequestMaxSize(req, 10));
interceptor.intercept(mai);
assertTrue(action.hasActionErrors());
Collection<String> errors = action.getActionErrors();
assertEquals(1, errors.size());
String msg = errors.iterator().next();
// the error message should contain at least this test
assertTrue(msg.startsWith("Der Request übertraf die maximal erlaubte Größe"));
}
private String encodeTextFile(String filename, String contentType, String content) {
return "\r\n" +
"--" +
"simple boundary" +
"\r\n" +
"Content-Disposition: form-data; name=\"" +
"file" +
"\"; filename=\"" +
filename +
"\r\n" +
"Content-Type: " +
contentType +
"\r\n" +
"\r\n" +
content;
}
private MultiPartRequestWrapper createMultipartRequestMaxFileSize(HttpServletRequest req) {
return createMultipartRequest(req, -1, 10, -1, -1);
}
private MultiPartRequestWrapper createMultipartRequestMaxFiles(HttpServletRequest req) {
return createMultipartRequest(req, -1, -1, 3, -1);
}
private MultiPartRequestWrapper createMultipartRequestMaxSize(HttpServletRequest req, int maxsize) {
return createMultipartRequest(req, maxsize, -1, -1, -1);
}
private MultiPartRequestWrapper createMultipartRequestMaxStringLength(HttpServletRequest req) {
return createMultipartRequest(req, -1, -1, -1, 20);
}
private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize, int maxfilesize, int maxfiles, int maxStringLength) {
JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
jak.setMaxSize(String.valueOf(maxsize));
jak.setMaxFileSize(String.valueOf(maxfilesize));
jak.setMaxFiles(String.valueOf(maxfiles));
jak.setMaxStringLength(String.valueOf(maxStringLength));
return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
}
protected void setUp() throws Exception {
super.setUp();
interceptor = new ActionFileUploadInterceptor();
container.inject(interceptor);
tempDir = File.createTempFile("struts", "fileupload");
tempDir.delete();
tempDir.mkdirs();
}
protected void tearDown() throws Exception {
tempDir.delete();
interceptor.destroy();
super.tearDown();
}
public static class MyFileUploadAction extends ActionSupport implements UploadedFilesAware {
private List<UploadedFile> uploadedFiles;
@Override
public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
this.uploadedFiles = uploadedFiles;
}
public List<UploadedFile> getUploadFiles() {
return this.uploadedFiles;
}
}
}
@@ -47,6 +47,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test case for FileUploadInterceptor.
@@ -83,28 +85,37 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
public byte[] getContent() {
return new byte[0];
}
@Override
public String getOriginalName() {
return null;
}
@Override
public String getContentType() {
return null;
}
};
private FileUploadInterceptor interceptor;
private File tempDir;
private TestAction action;
public void testAcceptFileWithEmptyAllowedTypesAndExtensions() throws Exception {
public void testAcceptFileWithEmptyAllowedTypesAndExtensions() {
// when allowed type is empty
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(action, EMPTY_FILE, "filename", "text/plain", "inputName", validation);
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
assertFalse(validation.hasErrors());
}
public void testAcceptFileWithoutEmptyTypes() throws Exception {
public void testAcceptFileWithoutEmptyTypes() {
interceptor.setAllowedTypes("text/plain");
// when file is of allowed types
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(action, EMPTY_FILE, "filename.txt", "text/plain", "inputName", validation);
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
@@ -112,7 +123,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
// when file is not of allowed types
validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(action, EMPTY_FILE, "filename.html", "text/html", "inputName", validation);
boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
@@ -120,11 +131,11 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
}
public void testAcceptFileWithWildcardContent() throws Exception {
public void testAcceptFileWithWildcardContent() {
interceptor.setAllowedTypes("text/*");
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(action, EMPTY_FILE, "filename.txt", "text/plain", "inputName", validation);
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
@@ -132,19 +143,19 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
interceptor.setAllowedTypes("text/h*");
validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(action, EMPTY_FILE, "filename.html", "text/plain", "inputName", validation);
boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/plain", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
}
public void testAcceptFileWithoutEmptyExtensions() throws Exception {
public void testAcceptFileWithoutEmptyExtensions() {
interceptor.setAllowedExtensions(".txt");
// when file is of allowed extensions
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean ok = interceptor.acceptFile(action, EMPTY_FILE, "filename.txt", "text/plain", "inputName", validation);
boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
@@ -152,7 +163,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
// when file is not of allowed extensions
validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(action, EMPTY_FILE, "filename.html", "text/html", "inputName", validation);
boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
@@ -161,34 +172,35 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
//test with multiple extensions
interceptor.setAllowedExtensions(".txt,.lol");
validation = new ValidationAwareSupport();
ok = interceptor.acceptFile(action, EMPTY_FILE, "filename.lol", "text/plain", "inputName", validation);
ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.lol", "text/plain", "inputName");
assertTrue(ok);
assertTrue(validation.getFieldErrors().isEmpty());
assertFalse(validation.hasErrors());
}
public void testAcceptFileWithNoFile() throws Exception {
public void testAcceptFileWithNoFile() {
FileUploadInterceptor interceptor = new FileUploadInterceptor();
interceptor.setContainer(container);
interceptor.setAllowedTypes("text/plain");
// when file is not of allowed types
ValidationAwareSupport validation = new ValidationAwareSupport();
boolean notOk = interceptor.acceptFile(action, null, "filename.html", "text/html", "inputName", validation);
boolean notOk = interceptor.acceptFile(validation, null, "filename.html", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
List errors = (List) validation.getFieldErrors().get("inputName");
List<String> errors = validation.getFieldErrors().get("inputName");
assertEquals(1, errors.size());
String msg = (String) errors.get(0);
String msg = errors.get(0);
assertTrue(msg.startsWith("Error uploading:"));
assertTrue(msg.indexOf("inputName") > 0);
}
public void testAcceptFileWithMaxSize() throws Exception {
interceptor.setAllowedTypes("text/plain");
interceptor.setMaximumSize(new Long(10));
interceptor.setMaximumSize(10L);
// when file is not of allowed types
ValidationAwareSupport validation = new ValidationAwareSupport();
@@ -196,18 +208,22 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
URL url = ClassLoaderUtil.getResource("log4j2.xml", FileUploadInterceptorTest.class);
File file = new File(new URI(url.toString()));
assertTrue("log4j2.xml should be in src/test folder", file.exists());
boolean notOk = interceptor.acceptFile(action, new StrutsUploadedFile(file), "filename", "text/html", "inputName", validation);
UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(file).withContentType("text/html").withOriginalName("filename").build();
boolean notOk = interceptor.acceptFile(validation, uploadedFile, "filename", "text/html", "inputName");
assertFalse(notOk);
assertFalse(validation.getFieldErrors().isEmpty());
assertTrue(validation.hasErrors());
List<String> errors = validation.getFieldErrors().get("inputName");
assertEquals(1, errors.size());
String msg = (String) errors.get(0);
String msg = errors.get(0);
// the error message should contain at least this test
assertTrue(msg.startsWith("The file is too large to be uploaded"));
assertTrue(msg.indexOf("inputName") > 0);
assertTrue(msg.indexOf("log4j2.xml") > 0);
assertThat(msg).contains(
"The file is too large to be uploaded",
"inputName",
"log4j2.xml",
"allowed mx size is 10"
);
}
public void testNoMultipartRequest() throws Exception {
@@ -278,7 +294,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
"Unit test of FileUploadInterceptor" +
"\r\n" +
"-----1234--\r\n");
req.setContent(content.getBytes("US-ASCII"));
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
MyFileupAction action = new MyFileupAction();
@@ -292,10 +308,10 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
interceptor.intercept(mai);
assertTrue(!action.hasErrors());
assertFalse(action.hasErrors());
HttpParameters parameters = mai.getInvocationContext().getParameters();
assertTrue(parameters.keySet().size() == 3);
assertEquals(3, parameters.keySet().size());
UploadedFile[] files = (UploadedFile[]) parameters.get("file").getObject();
String[] fileContentTypes = parameters.get("fileContentType").getMultipleValues();
String[] fileRealFilenames = parameters.get("fileFileName").getMultipleValues();
@@ -303,9 +319,9 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
assertNotNull(files);
assertNotNull(fileContentTypes);
assertNotNull(fileRealFilenames);
assertTrue(files.length == 1);
assertTrue(fileContentTypes.length == 1);
assertTrue(fileRealFilenames.length == 1);
assertEquals(1, files.length);
assertEquals(1, fileContentTypes.length);
assertEquals(1, fileRealFilenames.length);
assertEquals("text/html", fileContentTypes[0]);
assertNotNull("deleteme.txt", fileRealFilenames[0]);
}
@@ -313,8 +329,6 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
/**
* tests whether with multiple files sent with the same name, the ones with forbiddenTypes (see
* FileUploadInterceptor.setAllowedTypes(...) ) are sorted out.
*
* @throws Exception
*/
public void testMultipleAccept() throws Exception {
final String htmlContent = "<html><head></head><body>html content</body></html>";
@@ -326,18 +340,17 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("POST");
req.addHeader("Content-type", "multipart/form-data; boundary=" + bondary);
StringBuilder content = new StringBuilder(128);
content.append(encodeTextFile(bondary, endline, "file", "test.html", "text/plain", plainContent));
content.append(encodeTextFile(bondary, endline, "file", "test1.html", "text/html", htmlContent));
content.append(encodeTextFile(bondary, endline, "file", "test2.html", "text/html", htmlContent));
content.append(endline);
content.append(endline);
content.append(endline);
content.append("--");
content.append(bondary);
content.append("--");
content.append(endline);
req.setContent(content.toString().getBytes());
String content = encodeTextFile("test.html", "text/plain", plainContent) +
encodeTextFile("test1.html", "text/html", htmlContent) +
encodeTextFile("test2.html", "text/html", htmlContent) +
endline +
endline +
endline +
"--" +
bondary +
"--" +
endline;
req.setContent(content.getBytes());
assertTrue(ServletFileUpload.isMultipartContent(req));
@@ -347,7 +360,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
Map<String, Object> param = new HashMap<String, Object>();
Map<String, Object> param = new HashMap<>();
ActionContext.getContext().withParameters(HttpParameters.create(param).build());
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
@@ -380,17 +393,16 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
req.setMethod("POST");
req.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
StringBuilder content = new StringBuilder(128);
content.append(encodeTextFile(boundary, endline, "file", "test.html", "text/plain", plainContent));
content.append(encodeTextFile(boundary, endline, "file", "test1.html", "text/html", htmlContent));
content.append(encodeTextFile(boundary, endline, "file", "test2.html", "text/html", htmlContent));
content.append(encodeTextFile(boundary, endline, "file", "test3.html", "text/html", htmlContent));
content.append(endline);
content.append("--");
content.append(boundary);
content.append("--");
content.append(endline);
req.setContent(content.toString().getBytes());
String content = encodeTextFile("test.html", "text/plain", plainContent) +
encodeTextFile("test1.html", "text/html", htmlContent) +
encodeTextFile("test2.html", "text/html", htmlContent) +
encodeTextFile("test3.html", "text/html", htmlContent) +
endline +
"--" +
boundary +
"--" +
endline;
req.setContent(content.getBytes());
assertTrue(ServletFileUpload.isMultipartContent(req));
@@ -402,7 +414,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
mai.setInvocationContext(ActionContext.getContext());
Map<String, Object> param = new HashMap<>();
ActionContext.getContext().withParameters(HttpParameters.create(param).build());
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxFiles(req, 3));
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxFiles(req));
interceptor.setAllowedTypes("text/html");
interceptor.intercept(mai);
@@ -438,7 +450,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
Map<String, Object> param = new HashMap<>();
ActionContext.getContext()
.withParameters(HttpParameters.create(param).build())
.withServletRequest(createMultipartRequestMaxFileSize(req, 10));
.withServletRequest(createMultipartRequestMaxFileSize(req));
interceptor.intercept(mai);
@@ -487,7 +499,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
Map<String, Object> param = new HashMap<>();
ActionContext.getContext()
.withParameters(HttpParameters.create(param).build())
.withServletRequest(createMultipartRequestMaxStringLength(req, 20));
.withServletRequest(createMultipartRequestMaxStringLength(req));
interceptor.intercept(mai);
@@ -540,44 +552,40 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
assertTrue(msg.startsWith("Der Request übertraf die maximal erlaubte Größe"));
}
private String encodeTextFile(String bondary, String endline, String name, String filename, String contentType, String content) {
final StringBuilder sb = new StringBuilder(64);
sb.append(endline);
sb.append("--");
sb.append(bondary);
sb.append(endline);
sb.append("Content-Disposition: form-data; name=\"");
sb.append(name);
sb.append("\"; filename=\"");
sb.append(filename);
sb.append(endline);
sb.append("Content-Type: ");
sb.append(contentType);
sb.append(endline);
sb.append(endline);
sb.append(content);
return sb.toString();
private String encodeTextFile(String filename, String contentType, String content) {
return "\r\n" +
"--" +
"simple boundary" +
"\r\n" +
"Content-Disposition: form-data; name=\"" +
"file" +
"\"; filename=\"" +
filename +
"\r\n" +
"Content-Type: " +
contentType +
"\r\n" +
"\r\n" +
content;
}
private MultiPartRequestWrapper createMultipartRequestMaxFileSize(HttpServletRequest req, int maxfilesize) throws IOException {
return createMultipartRequest(req, -1, maxfilesize, -1, -1);
private MultiPartRequestWrapper createMultipartRequestMaxFileSize(HttpServletRequest req) {
return createMultipartRequest(req, -1, 10, -1, -1);
}
private MultiPartRequestWrapper createMultipartRequestMaxFiles(HttpServletRequest req, int maxfiles) throws IOException {
return createMultipartRequest(req, -1, -1, maxfiles, -1);
private MultiPartRequestWrapper createMultipartRequestMaxFiles(HttpServletRequest req) {
return createMultipartRequest(req, -1, -1, 3, -1);
}
private MultiPartRequestWrapper createMultipartRequestMaxSize(HttpServletRequest req, int maxsize) throws IOException {
private MultiPartRequestWrapper createMultipartRequestMaxSize(HttpServletRequest req, int maxsize) {
return createMultipartRequest(req, maxsize, -1, -1, -1);
}
private MultiPartRequestWrapper createMultipartRequestMaxStringLength(HttpServletRequest req, int maxStringLength) throws IOException {
return createMultipartRequest(req, -1, -1, -1, maxStringLength);
private MultiPartRequestWrapper createMultipartRequestMaxStringLength(HttpServletRequest req) {
return createMultipartRequest(req, -1, -1, -1, 20);
}
private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize, int maxfilesize,
int maxfiles, int maxStringLength) throws IOException {
private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize, int maxfilesize, int maxfiles, int maxStringLength) {
JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
jak.setMaxSize(String.valueOf(maxsize));
@@ -589,8 +597,6 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
protected void setUp() throws Exception {
super.setUp();
action = new TestAction();
container.inject(action);
interceptor = new FileUploadInterceptor();
container.inject(interceptor);
@@ -31,7 +31,6 @@ import java.util.List;
/**
* Multipart form data request adapter for Jason Pell's multipart utils package.
*
*/
public class PellMultiPartRequest extends AbstractMultiPartRequest {
@@ -69,7 +68,11 @@ public class PellMultiPartRequest extends AbstractMultiPartRequest {
}
public UploadedFile[] getFile(String fieldName) {
return new UploadedFile[]{ new StrutsUploadedFile(multi.getFile(fieldName)) };
return new UploadedFile[]{StrutsUploadedFile.Builder.create(multi.getFile(fieldName))
.withContentType(multi.getContentType(fieldName))
.withOriginalName(multi.getFileSystemName(fieldName))
.build()
};
}
public String[] getFileNames(String fieldName) {
@@ -132,7 +135,7 @@ public class PellMultiPartRequest extends AbstractMultiPartRequest {
}
} catch (IllegalArgumentException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Could not get encoding property 'struts.i18n.encoding' for file upload. Using system default");
LOG.info("Could not get encoding property 'struts.i18n.encoding' for file upload. Using system default");
}
} catch (UnsupportedEncodingException e) {
LOG.error("Encoding " + encoding + " is not a valid encoding. Please check your struts.properties file.");
@@ -140,8 +143,8 @@ public class PellMultiPartRequest extends AbstractMultiPartRequest {
}
/* (non-Javadoc)
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
*/
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
*/
public void cleanUp() {
Enumeration fileParameterNames = multi.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {