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; } }