mirror of
https://github.com/apache/struts.git
synced 2026-08-05 14:47:09 +00:00
Merge pull request #808 from apache/feature/WW-5371-modern-upload
[WW-5371] Modern upload
This commit is contained in:
+47
-55
@@ -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);
|
||||
|
||||
}
|
||||
+5
-1
@@ -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]);
|
||||
|
||||
+21
-21
@@ -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>
|
||||
* <action name="doUpload" class="com.example.UploadAction">
|
||||
* <interceptor-ref name="actionFileUpload"/>
|
||||
* <interceptor-ref name="basicStack"/>
|
||||
* <result name="success">good_result.jsp</result>
|
||||
* </action>
|
||||
* </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>
|
||||
* <s:form action="doUpload" method="post" enctype="multipart/form-data">
|
||||
* <s:file name="upload" label="File"/>
|
||||
* <s:submit/>
|
||||
* </s:form>
|
||||
* </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;
|
||||
*
|
||||
* @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.*;
|
||||
* </action>
|
||||
* <!-- 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);
|
||||
|
||||
+591
@@ -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);
|
||||
|
||||
+8
-5
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user