package frame.upload;
|
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.UnsupportedEncodingException;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.Iterator;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.NoSuchElementException;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import frame.upload.MultipartStream.ItemInputStream;
|
import frame.upload.servlet.ServletFileUpload;
|
import frame.upload.servlet.ServletRequestContext;
|
import frame.upload.util.Closeable;
|
import frame.upload.util.FileItemHeadersImpl;
|
import frame.upload.util.LimitedInputStream;
|
import frame.upload.util.Streams;
|
|
|
public abstract class FileUploadBase {
|
|
public static final boolean isMultipartContent(RequestContext ctx) {
|
String contentType = ctx.getContentType();
|
|
if (contentType == null) {
|
return false;
|
}
|
|
if (contentType.toLowerCase().startsWith(MULTIPART)) {
|
return true;
|
}
|
return false;
|
}
|
|
public static boolean isMultipartContent(HttpServletRequest req) {
|
return ServletFileUpload.isMultipartContent(req);
|
}
|
|
public static final String CONTENT_TYPE = "Content-type";
|
public static final String CONTENT_DISPOSITION = "Content-disposition";
|
public static final String CONTENT_LENGTH = "Content-length";
|
public static final String FORM_DATA = "form-data";
|
public static final String ATTACHMENT = "attachment";
|
public static final String MULTIPART = "multipart/";
|
public static final String MULTIPART_FORM_DATA = "multipart/form-data";
|
public static final String MULTIPART_MIXED = "multipart/mixed";
|
public static final int MAX_HEADER_SIZE = 1024;
|
|
private long sizeMax = -1;
|
private long fileSizeMax = -1;
|
private String headerEncoding;
|
private ProgressListener listener;
|
|
public abstract FileItemFactory getFileItemFactory();
|
public abstract void setFileItemFactory(FileItemFactory factory);
|
|
public long getSizeMax() {
|
return sizeMax;
|
}
|
|
public void setSizeMax(long sizeMax) {
|
this.sizeMax = sizeMax;
|
}
|
|
public long getFileSizeMax() {
|
return fileSizeMax;
|
}
|
|
public void setFileSizeMax(long fileSizeMax) {
|
this.fileSizeMax = fileSizeMax;
|
}
|
|
public String getHeaderEncoding() {
|
return headerEncoding;
|
}
|
|
public void setHeaderEncoding(String encoding) {
|
headerEncoding = encoding;
|
}
|
|
public List parseRequest(HttpServletRequest req) throws FileUploadException {
|
return parseRequest(new ServletRequestContext(req));
|
}
|
|
public FileItemIterator getItemIterator(RequestContext ctx) throws FileUploadException, IOException {
|
return new FileItemIteratorImpl(ctx);
|
}
|
|
public List parseRequest(RequestContext ctx) throws FileUploadException {
|
List items = new ArrayList();
|
boolean successful = false;
|
|
try {
|
FileItemIterator iter = getItemIterator(ctx);
|
FileItemFactory fac = getFileItemFactory();
|
|
if (fac == null) {
|
throw new NullPointerException("No FileItemFactory has been set.");
|
}
|
|
while (iter.hasNext()) {
|
final FileItemStream item = iter.next();
|
final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
|
FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(), item.isFormField(), fileName);
|
items.add(fileItem);
|
try {
|
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
|
}
|
catch (FileUploadIOException e) {
|
throw (FileUploadException) e.getCause();
|
}
|
catch (IOException e) {
|
throw new IOFileUploadException("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage(), e);
|
}
|
|
if (fileItem instanceof FileItemHeadersSupport) {
|
final FileItemHeaders fih = item.getHeaders();
|
((FileItemHeadersSupport) fileItem).setHeaders(fih);
|
}
|
}
|
successful = true;
|
return items;
|
}
|
catch (FileUploadIOException e) {
|
throw (FileUploadException) e.getCause();
|
}
|
catch (IOException e) {
|
throw new FileUploadException(e.getMessage(), e);
|
}
|
finally {
|
if (!successful) {
|
for (Iterator iterator = items.iterator(); iterator.hasNext();) {
|
FileItem fileItem = (FileItem) iterator.next();
|
try {
|
fileItem.delete();
|
} catch (Throwable e) {
|
}
|
}
|
}
|
}
|
}
|
|
protected byte[] getBoundary(String contentType) {
|
ParameterParser parser = new ParameterParser();
|
parser.setLowerCaseNames(true);
|
// Parameter parser can handle null input
|
Map params = parser.parse(contentType, new char[] {';', ','});
|
String boundaryStr = (String) params.get("boundary");
|
|
if (boundaryStr == null) {
|
return null;
|
}
|
byte[] boundary;
|
try {
|
boundary = boundaryStr.getBytes("ISO-8859-1");
|
} catch (UnsupportedEncodingException e) {
|
boundary = boundaryStr.getBytes();
|
}
|
return boundary;
|
}
|
|
|
protected String getFileName(Map /* String, String */ headers) {
|
return getFileName(getHeader(headers, CONTENT_DISPOSITION));
|
}
|
|
protected String getFileName(FileItemHeaders headers) {
|
return getFileName(headers.getHeader(CONTENT_DISPOSITION));
|
}
|
|
private String getFileName(String pContentDisposition) {
|
String fileName = null;
|
if (pContentDisposition != null) {
|
String cdl = pContentDisposition.toLowerCase();
|
if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
|
ParameterParser parser = new ParameterParser();
|
parser.setLowerCaseNames(true);
|
// Parameter parser can handle null input
|
Map params = parser.parse(pContentDisposition, ';');
|
if (params.containsKey("filename")) {
|
fileName = (String) params.get("filename");
|
if (fileName != null) {
|
fileName = fileName.trim();
|
} else {
|
// Even if there is no value, the parameter is present,
|
// so we return an empty file name rather than no file
|
// name.
|
fileName = "";
|
}
|
}
|
}
|
}
|
return fileName;
|
}
|
|
|
protected String getFieldName(FileItemHeaders headers) {
|
return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
|
}
|
|
private String getFieldName(String pContentDisposition) {
|
String fieldName = null;
|
if (pContentDisposition != null && pContentDisposition.toLowerCase().startsWith(FORM_DATA)) {
|
ParameterParser parser = new ParameterParser();
|
parser.setLowerCaseNames(true);
|
// Parameter parser can handle null input
|
Map params = parser.parse(pContentDisposition, ';');
|
fieldName = (String) params.get("name");
|
if (fieldName != null) {
|
fieldName = fieldName.trim();
|
}
|
}
|
return fieldName;
|
}
|
|
protected String getFieldName(Map /* String, String */ headers) {
|
return getFieldName(getHeader(headers, CONTENT_DISPOSITION));
|
}
|
|
protected FileItem createItem(Map /* String, String */ headers, boolean isFormField)
|
throws FileUploadException {
|
return getFileItemFactory().createItem(getFieldName(headers),
|
getHeader(headers, CONTENT_TYPE),
|
isFormField,
|
getFileName(headers));
|
}
|
|
protected FileItemHeaders getParsedHeaders(String headerPart) {
|
final int len = headerPart.length();
|
FileItemHeadersImpl headers = newFileItemHeaders();
|
int start = 0;
|
for (;;) {
|
int end = parseEndOfLine(headerPart, start);
|
if (start == end) {
|
break;
|
}
|
String header = headerPart.substring(start, end);
|
start = end + 2;
|
while (start < len) {
|
int nonWs = start;
|
while (nonWs < len) {
|
char c = headerPart.charAt(nonWs);
|
if (c != ' ' && c != '\t') {
|
break;
|
}
|
++nonWs;
|
}
|
if (nonWs == start) {
|
break;
|
}
|
// Continuation line found
|
end = parseEndOfLine(headerPart, nonWs);
|
header += " " + headerPart.substring(nonWs, end);
|
start = end + 2;
|
}
|
parseHeaderLine(headers, header);
|
}
|
return headers;
|
}
|
|
protected FileItemHeadersImpl newFileItemHeaders() {
|
return new FileItemHeadersImpl();
|
}
|
|
protected Map /* String, String */ parseHeaders(String headerPart) {
|
FileItemHeaders headers = getParsedHeaders(headerPart);
|
Map result = new HashMap();
|
for (Iterator iter = headers.getHeaderNames(); iter.hasNext();) {
|
String headerName = (String) iter.next();
|
Iterator iter2 = headers.getHeaders(headerName);
|
String headerValue = (String) iter2.next();
|
while (iter2.hasNext()) {
|
headerValue += "," + iter2.next();
|
}
|
result.put(headerName, headerValue);
|
}
|
return result;
|
}
|
|
private int parseEndOfLine(String headerPart, int end) {
|
int index = end;
|
for (;;) {
|
int offset = headerPart.indexOf('\r', index);
|
if (offset == -1 || offset + 1 >= headerPart.length()) {
|
throw new IllegalStateException(
|
"Expected headers to be terminated by an empty line.");
|
}
|
if (headerPart.charAt(offset + 1) == '\n') {
|
return offset;
|
}
|
index = offset + 1;
|
}
|
}
|
|
private void parseHeaderLine(FileItemHeadersImpl headers, String header) {
|
final int colonOffset = header.indexOf(':');
|
if (colonOffset == -1) {
|
// This header line is malformed, skip it.
|
return;
|
}
|
String headerName = header.substring(0, colonOffset).trim();
|
String headerValue =
|
header.substring(header.indexOf(':') + 1).trim();
|
headers.addHeader(headerName, headerValue);
|
}
|
|
protected final String getHeader(Map /* String, String */ headers, String name) {
|
return (String) headers.get(name.toLowerCase());
|
}
|
|
private class FileItemIteratorImpl implements FileItemIterator {
|
|
class FileItemStreamImpl implements FileItemStream {
|
private final String contentType;
|
private final String fieldName;
|
private final String name;
|
private final boolean formField;
|
private final InputStream stream;
|
private boolean opened;
|
private FileItemHeaders headers;
|
|
FileItemStreamImpl(String pName, String pFieldName, String pContentType, boolean pFormField, long pContentLength) throws IOException {
|
name = pName;
|
fieldName = pFieldName;
|
contentType = pContentType;
|
formField = pFormField;
|
final ItemInputStream itemStream = multi.newInputStream();
|
InputStream istream = itemStream;
|
if (fileSizeMax != -1) {
|
if (pContentLength != -1
|
&& pContentLength > fileSizeMax) {
|
FileSizeLimitExceededException e =
|
new FileSizeLimitExceededException(
|
"The field " + fieldName
|
+ " exceeds its maximum permitted "
|
+ " size of " + fileSizeMax
|
+ " bytes.",
|
pContentLength, fileSizeMax);
|
e.setFileName(pName);
|
e.setFieldName(pFieldName);
|
throw new FileUploadIOException(e);
|
}
|
istream = new LimitedInputStream(istream, fileSizeMax) {
|
protected void raiseError(long pSizeMax, long pCount)
|
throws IOException {
|
itemStream.close(true);
|
FileSizeLimitExceededException e =
|
new FileSizeLimitExceededException(
|
"The field " + fieldName
|
+ " exceeds its maximum permitted "
|
+ " size of " + pSizeMax
|
+ " bytes.",
|
pCount, pSizeMax);
|
e.setFieldName(fieldName);
|
e.setFileName(name);
|
throw new FileUploadIOException(e);
|
}
|
};
|
}
|
stream = istream;
|
}
|
|
public String getContentType() {
|
return contentType;
|
}
|
|
public String getFieldName() {
|
return fieldName;
|
}
|
|
public String getName() {
|
return Streams.checkFileName(name);
|
}
|
|
public boolean isFormField() {
|
return formField;
|
}
|
|
public InputStream openStream() throws IOException {
|
if (opened) {
|
throw new IllegalStateException("The stream was already opened.");
|
}
|
if (((Closeable) stream).isClosed()) {
|
throw new FileItemStream.ItemSkippedException();
|
}
|
return stream;
|
}
|
|
void close() throws IOException {
|
stream.close();
|
}
|
|
public FileItemHeaders getHeaders() {
|
return headers;
|
}
|
|
@Override
|
public void setHeaders(FileItemHeaders pHeaders) {
|
headers = pHeaders;
|
}
|
}
|
|
private final MultipartStream multi;
|
private final MultipartStream.ProgressNotifier notifier;
|
private final byte[] boundary;
|
private FileItemStreamImpl currentItem;
|
private String currentFieldName;
|
private boolean skipPreamble;
|
private boolean itemValid;
|
private boolean eof;
|
|
FileItemIteratorImpl(RequestContext ctx) throws FileUploadException, IOException {
|
if (ctx == null) {
|
throw new NullPointerException("ctx parameter");
|
}
|
|
String contentType = ctx.getContentType();
|
if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) {
|
throw new InvalidContentTypeException(
|
"the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is " + contentType);
|
}
|
|
InputStream input = ctx.getInputStream();
|
|
if (sizeMax >= 0) {
|
int requestSize = ctx.getContentLength();
|
if (requestSize == -1) {
|
input = new LimitedInputStream(input, sizeMax) {
|
protected void raiseError(long pSizeMax, long pCount) throws IOException {
|
FileUploadException ex = new SizeLimitExceededException("the request was rejected because" + " its size (" + pCount + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax);
|
throw new FileUploadIOException(ex);
|
}
|
};
|
} else {
|
if (sizeMax >= 0 && requestSize > sizeMax) {
|
throw new SizeLimitExceededException("the request was rejected because its size (" + requestSize + ") exceeds the configured maximum (" + sizeMax + ")", requestSize, sizeMax);
|
}
|
}
|
}
|
|
String charEncoding = headerEncoding;
|
if (charEncoding == null) {
|
charEncoding = ctx.getCharacterEncoding();
|
}
|
|
boundary = getBoundary(contentType);
|
if (boundary == null) {
|
throw new FileUploadException("the request was rejected because " + "no multipart boundary was found");
|
}
|
|
notifier = new MultipartStream.ProgressNotifier(listener, ctx.getContentLength());
|
multi = new MultipartStream(input, boundary, notifier);
|
multi.setHeaderEncoding(charEncoding);
|
|
skipPreamble = true;
|
findNextItem();
|
}
|
|
private boolean findNextItem() throws IOException {
|
if (eof) {
|
return false;
|
}
|
|
if (currentItem != null) {
|
currentItem.close();
|
currentItem = null;
|
}
|
|
for (;;) {
|
boolean nextPart;
|
if (skipPreamble) {
|
nextPart = multi.skipPreamble();
|
} else {
|
nextPart = multi.readBoundary();
|
}
|
|
if (!nextPart) {
|
if (currentFieldName == null) {
|
// Outer multipart terminated -> No more data
|
eof = true;
|
return false;
|
}
|
// Inner multipart terminated -> Return to parsing the outer
|
multi.setBoundary(boundary);
|
currentFieldName = null;
|
continue;
|
}
|
|
FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
|
if (currentFieldName == null) {
|
// We're parsing the outer multipart
|
String fieldName = getFieldName(headers);
|
if (fieldName != null) {
|
String subContentType = headers.getHeader(CONTENT_TYPE);
|
if (subContentType != null && subContentType.toLowerCase().startsWith(MULTIPART_MIXED)) {
|
currentFieldName = fieldName;
|
// Multiple files associated with this field name
|
byte[] subBoundary = getBoundary(subContentType);
|
multi.setBoundary(subBoundary);
|
skipPreamble = true;
|
continue;
|
}
|
String fileName = getFileName(headers);
|
currentItem = new FileItemStreamImpl(fileName, fieldName, headers.getHeader(CONTENT_TYPE), fileName == null, getContentLength(headers));
|
notifier.noteItem();
|
itemValid = true;
|
return true;
|
}
|
} else {
|
String fileName = getFileName(headers);
|
if (fileName != null) {
|
currentItem = new FileItemStreamImpl(fileName, currentFieldName, headers.getHeader(CONTENT_TYPE), false, getContentLength(headers));
|
notifier.noteItem();
|
itemValid = true;
|
return true;
|
}
|
}
|
multi.discardBodyData();
|
}
|
}
|
|
private long getContentLength(FileItemHeaders pHeaders) {
|
try {
|
return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
|
} catch (Exception e) {
|
return -1;
|
}
|
}
|
|
public boolean hasNext() throws FileUploadException, IOException {
|
if (eof) {
|
return false;
|
}
|
if (itemValid) {
|
return true;
|
}
|
return findNextItem();
|
}
|
|
public FileItemStream next() throws FileUploadException, IOException {
|
if (eof || (!itemValid && !hasNext())) {
|
throw new NoSuchElementException();
|
}
|
itemValid = false;
|
return currentItem;
|
}
|
}
|
|
public static class FileUploadIOException extends IOException {
|
private static final long serialVersionUID = -7047616958165584154L;
|
private final FileUploadException cause;
|
|
public FileUploadIOException(FileUploadException pCause) {
|
cause = pCause;
|
}
|
|
public Throwable getCause() {
|
return cause;
|
}
|
}
|
|
public static class InvalidContentTypeException extends FileUploadException {
|
private static final long serialVersionUID = -9073026332015646668L;
|
|
public InvalidContentTypeException() {
|
// Nothing to do.
|
}
|
|
public InvalidContentTypeException(String message) {
|
super(message);
|
}
|
}
|
|
public static class IOFileUploadException extends FileUploadException {
|
private static final long serialVersionUID = 1749796615868477269L;
|
private final IOException cause;
|
|
public IOFileUploadException(String pMsg, IOException pException) {
|
super(pMsg);
|
cause = pException;
|
}
|
|
public Throwable getCause() {
|
return cause;
|
}
|
}
|
|
protected abstract static class SizeException extends FileUploadException {
|
private static final long serialVersionUID = -8776225574705254126L;
|
private final long actual;
|
private final long permitted;
|
|
protected SizeException(String message, long actual, long permitted) {
|
super(message);
|
this.actual = actual;
|
this.permitted = permitted;
|
}
|
|
public long getActualSize() {
|
return actual;
|
}
|
|
public long getPermittedSize() {
|
return permitted;
|
}
|
}
|
|
public static class UnknownSizeException
|
extends FileUploadException {
|
private static final long serialVersionUID = 7062279004812015273L;
|
|
public UnknownSizeException() {
|
super();
|
}
|
|
public UnknownSizeException(String message) {
|
super(message);
|
}
|
}
|
|
public static class SizeLimitExceededException extends SizeException {
|
private static final long serialVersionUID = -2474893167098052828L;
|
|
public SizeLimitExceededException() {
|
this(null, 0, 0);
|
}
|
|
public SizeLimitExceededException(String message) {
|
this(message, 0, 0);
|
}
|
|
public SizeLimitExceededException(String message, long actual, long permitted) {
|
super(message, actual, permitted);
|
}
|
}
|
|
public static class FileSizeLimitExceededException extends SizeException {
|
private static final long serialVersionUID = 8150776562029630058L;
|
|
private String fileName;
|
private String fieldName;
|
|
public FileSizeLimitExceededException(String message, long actual, long permitted) {
|
super(message, actual, permitted);
|
}
|
|
public String getFileName() {
|
return fileName;
|
}
|
|
public void setFileName(String pFileName) {
|
fileName = pFileName;
|
}
|
|
public String getFieldName() {
|
return fieldName;
|
}
|
|
public void setFieldName(String pFieldName) {
|
fieldName = pFieldName;
|
}
|
}
|
|
public ProgressListener getProgressListener() {
|
return listener;
|
}
|
|
public void setProgressListener(ProgressListener pListener) {
|
listener = pListener;
|
}
|
}
|