0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.fileupload;
0018:
0019: import java.io.IOException;
0020: import java.io.InputStream;
0021: import java.io.UnsupportedEncodingException;
0022: import java.util.ArrayList;
0023: import java.util.HashMap;
0024: import java.util.List;
0025: import java.util.Map;
0026: import java.util.NoSuchElementException;
0027:
0028: import javax.servlet.http.HttpServletRequest;
0029:
0030: import org.apache.commons.fileupload.servlet.ServletFileUpload;
0031: import org.apache.commons.fileupload.servlet.ServletRequestContext;
0032: import org.apache.commons.fileupload.util.Closeable;
0033: import org.apache.commons.fileupload.util.LimitedInputStream;
0034: import org.apache.commons.fileupload.util.Streams;
0035:
0036: /**
0037: * <p>High level API for processing file uploads.</p>
0038: *
0039: * <p>This class handles multiple files per single HTML widget, sent using
0040: * <code>multipart/mixed</code> encoding type, as specified by
0041: * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
0042: * #parseRequest(HttpServletRequest)} to acquire a list of {@link
0043: * org.apache.commons.fileupload.FileItem}s associated with a given HTML
0044: * widget.</p>
0045: *
0046: * <p>How the data for individual parts is stored is determined by the factory
0047: * used to create them; a given part may be in memory, on disk, or somewhere
0048: * else.</p>
0049: *
0050: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
0051: * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
0052: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
0053: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
0054: * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
0055: * @author Sean C. Sullivan
0056: *
0057: * @version $Id: FileUploadBase.java 502350 2007-02-01 20:42:48Z jochen $
0058: */
0059: public abstract class FileUploadBase {
0060:
0061: // ---------------------------------------------------------- Class methods
0062:
0063: /**
0064: * <p>Utility method that determines whether the request contains multipart
0065: * content.</p>
0066: *
0067: * <p><strong>NOTE:</strong>This method will be moved to the
0068: * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
0069: * Unfortunately, since this method is static, it is not possible to
0070: * provide its replacement until this method is removed.</p>
0071: *
0072: * @param ctx The request context to be evaluated. Must be non-null.
0073: *
0074: * @return <code>true</code> if the request is multipart;
0075: * <code>false</code> otherwise.
0076: */
0077: public static final boolean isMultipartContent(RequestContext ctx) {
0078: String contentType = ctx.getContentType();
0079: if (contentType == null) {
0080: return false;
0081: }
0082: if (contentType.toLowerCase().startsWith(MULTIPART)) {
0083: return true;
0084: }
0085: return false;
0086: }
0087:
0088: /**
0089: * Utility method that determines whether the request contains multipart
0090: * content.
0091: *
0092: * @param req The servlet request to be evaluated. Must be non-null.
0093: *
0094: * @return <code>true</code> if the request is multipart;
0095: * <code>false</code> otherwise.
0096: *
0097: * @deprecated Use the method on <code>ServletFileUpload</code> instead.
0098: */
0099: public static boolean isMultipartContent(HttpServletRequest req) {
0100: return ServletFileUpload.isMultipartContent(req);
0101: }
0102:
0103: // ----------------------------------------------------- Manifest constants
0104:
0105: /**
0106: * HTTP content type header name.
0107: */
0108: public static final String CONTENT_TYPE = "Content-type";
0109:
0110: /**
0111: * HTTP content disposition header name.
0112: */
0113: public static final String CONTENT_DISPOSITION = "Content-disposition";
0114:
0115: /**
0116: * Content-disposition value for form data.
0117: */
0118: public static final String FORM_DATA = "form-data";
0119:
0120: /**
0121: * Content-disposition value for file attachment.
0122: */
0123: public static final String ATTACHMENT = "attachment";
0124:
0125: /**
0126: * Part of HTTP content type header.
0127: */
0128: public static final String MULTIPART = "multipart/";
0129:
0130: /**
0131: * HTTP content type header for multipart forms.
0132: */
0133: public static final String MULTIPART_FORM_DATA = "multipart/form-data";
0134:
0135: /**
0136: * HTTP content type header for multiple uploads.
0137: */
0138: public static final String MULTIPART_MIXED = "multipart/mixed";
0139:
0140: /**
0141: * The maximum length of a single header line that will be parsed
0142: * (1024 bytes).
0143: * @deprecated This constant is no longer used. As of commons-fileupload
0144: * 1.2, the only applicable limit is the total size of a parts headers,
0145: * {@link MultipartStream#HEADER_PART_SIZE_MAX}.
0146: */
0147: public static final int MAX_HEADER_SIZE = 1024;
0148:
0149: // ----------------------------------------------------------- Data members
0150:
0151: /**
0152: * The maximum size permitted for the complete request, as opposed to
0153: * {@link #fileSizeMax}. A value of -1 indicates no maximum.
0154: */
0155: private long sizeMax = -1;
0156:
0157: /**
0158: * The maximum size permitted for a single uploaded file, as opposed
0159: * to {@link #sizeMax}. A value of -1 indicates no maximum.
0160: */
0161: private long fileSizeMax = -1;
0162:
0163: /**
0164: * The content encoding to use when reading part headers.
0165: */
0166: private String headerEncoding;
0167:
0168: /**
0169: * The progress listener.
0170: */
0171: private ProgressListener listener;
0172:
0173: // ----------------------------------------------------- Property accessors
0174:
0175: /**
0176: * Returns the factory class used when creating file items.
0177: *
0178: * @return The factory class for new file items.
0179: */
0180: public abstract FileItemFactory getFileItemFactory();
0181:
0182: /**
0183: * Sets the factory class to use when creating file items.
0184: *
0185: * @param factory The factory class for new file items.
0186: */
0187: public abstract void setFileItemFactory(FileItemFactory factory);
0188:
0189: /**
0190: * Returns the maximum allowed size of a complete request, as opposed
0191: * to {@link #getFileSizeMax()}.
0192: *
0193: * @return The maximum allowed size, in bytes. The default value of
0194: * -1 indicates, that there is no limit.
0195: *
0196: * @see #setSizeMax(long)
0197: *
0198: */
0199: public long getSizeMax() {
0200: return sizeMax;
0201: }
0202:
0203: /**
0204: * Sets the maximum allowed size of a complete request, as opposed
0205: * to {@link #setFileSizeMax(long)}.
0206: *
0207: * @param sizeMax The maximum allowed size, in bytes. The default value of
0208: * -1 indicates, that there is no limit.
0209: *
0210: * @see #getSizeMax()
0211: *
0212: */
0213: public void setSizeMax(long sizeMax) {
0214: this .sizeMax = sizeMax;
0215: }
0216:
0217: /**
0218: * Returns the maximum allowed size of a single uploaded file,
0219: * as opposed to {@link #getSizeMax()}.
0220: *
0221: * @see #setFileSizeMax(long)
0222: * @return Maximum size of a single uploaded file.
0223: */
0224: public long getFileSizeMax() {
0225: return fileSizeMax;
0226: }
0227:
0228: /**
0229: * Sets the maximum allowed size of a single uploaded file,
0230: * as opposed to {@link #getSizeMax()}.
0231: *
0232: * @see #getFileSizeMax()
0233: * @param fileSizeMax Maximum size of a single uploaded file.
0234: */
0235: public void setFileSizeMax(long fileSizeMax) {
0236: this .fileSizeMax = fileSizeMax;
0237: }
0238:
0239: /**
0240: * Retrieves the character encoding used when reading the headers of an
0241: * individual part. When not specified, or <code>null</code>, the request
0242: * encoding is used. If that is also not specified, or <code>null</code>,
0243: * the platform default encoding is used.
0244: *
0245: * @return The encoding used to read part headers.
0246: */
0247: public String getHeaderEncoding() {
0248: return headerEncoding;
0249: }
0250:
0251: /**
0252: * Specifies the character encoding to be used when reading the headers of
0253: * individual part. When not specified, or <code>null</code>, the request
0254: * encoding is used. If that is also not specified, or <code>null</code>,
0255: * the platform default encoding is used.
0256: *
0257: * @param encoding The encoding used to read part headers.
0258: */
0259: public void setHeaderEncoding(String encoding) {
0260: headerEncoding = encoding;
0261: }
0262:
0263: // --------------------------------------------------------- Public methods
0264:
0265: /**
0266: * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
0267: * compliant <code>multipart/form-data</code> stream.
0268: *
0269: * @param req The servlet request to be parsed.
0270: *
0271: * @return A list of <code>FileItem</code> instances parsed from the
0272: * request, in the order that they were transmitted.
0273: *
0274: * @throws FileUploadException if there are problems reading/parsing
0275: * the request or storing files.
0276: *
0277: * @deprecated Use the method in <code>ServletFileUpload</code> instead.
0278: */
0279: public List /* FileItem */parseRequest(HttpServletRequest req)
0280: throws FileUploadException {
0281: return parseRequest(new ServletRequestContext(req));
0282: }
0283:
0284: /**
0285: * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
0286: * compliant <code>multipart/form-data</code> stream.
0287: *
0288: * @param ctx The context for the request to be parsed.
0289: *
0290: * @return An iterator to instances of <code>FileItemStream</code>
0291: * parsed from the request, in the order that they were
0292: * transmitted.
0293: *
0294: * @throws FileUploadException if there are problems reading/parsing
0295: * the request or storing files.
0296: * @throws IOException An I/O error occurred. This may be a network
0297: * error while communicating with the client or a problem while
0298: * storing the uploaded content.
0299: */
0300: public FileItemIterator getItemIterator(RequestContext ctx)
0301: throws FileUploadException, IOException {
0302: return new FileItemIteratorImpl(ctx);
0303: }
0304:
0305: /**
0306: * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
0307: * compliant <code>multipart/form-data</code> stream.
0308: *
0309: * @param ctx The context for the request to be parsed.
0310: *
0311: * @return A list of <code>FileItem</code> instances parsed from the
0312: * request, in the order that they were transmitted.
0313: *
0314: * @throws FileUploadException if there are problems reading/parsing
0315: * the request or storing files.
0316: */
0317: public List /* FileItem */parseRequest(RequestContext ctx)
0318: throws FileUploadException {
0319: try {
0320: FileItemIterator iter = getItemIterator(ctx);
0321: List items = new ArrayList();
0322: FileItemFactory fac = getFileItemFactory();
0323: if (fac == null) {
0324: throw new NullPointerException(
0325: "No FileItemFactory has been set.");
0326: }
0327: while (iter.hasNext()) {
0328: FileItemStream item = iter.next();
0329: FileItem fileItem = fac.createItem(item.getFieldName(),
0330: item.getContentType(), item.isFormField(), item
0331: .getName());
0332: try {
0333: Streams.copy(item.openStream(), fileItem
0334: .getOutputStream(), true);
0335: } catch (FileUploadIOException e) {
0336: throw (FileUploadException) e.getCause();
0337: } catch (IOException e) {
0338: throw new IOFileUploadException("Processing of "
0339: + MULTIPART_FORM_DATA + " request failed. "
0340: + e.getMessage(), e);
0341: }
0342: items.add(fileItem);
0343: }
0344: return items;
0345: } catch (FileUploadIOException e) {
0346: throw (FileUploadException) e.getCause();
0347: } catch (IOException e) {
0348: throw new FileUploadException(e.getMessage(), e);
0349: }
0350: }
0351:
0352: // ------------------------------------------------------ Protected methods
0353:
0354: /**
0355: * Retrieves the boundary from the <code>Content-type</code> header.
0356: *
0357: * @param contentType The value of the content type header from which to
0358: * extract the boundary value.
0359: *
0360: * @return The boundary, as a byte array.
0361: */
0362: protected byte[] getBoundary(String contentType) {
0363: ParameterParser parser = new ParameterParser();
0364: parser.setLowerCaseNames(true);
0365: // Parameter parser can handle null input
0366: Map params = parser.parse(contentType, ';');
0367: String boundaryStr = (String) params.get("boundary");
0368:
0369: if (boundaryStr == null) {
0370: return null;
0371: }
0372: byte[] boundary;
0373: try {
0374: boundary = boundaryStr.getBytes("ISO-8859-1");
0375: } catch (UnsupportedEncodingException e) {
0376: boundary = boundaryStr.getBytes();
0377: }
0378: return boundary;
0379: }
0380:
0381: /**
0382: * Retrieves the file name from the <code>Content-disposition</code>
0383: * header.
0384: *
0385: * @param headers A <code>Map</code> containing the HTTP request headers.
0386: *
0387: * @return The file name for the current <code>encapsulation</code>.
0388: */
0389: protected String getFileName(Map /* String, String */headers) {
0390: String fileName = null;
0391: String cd = getHeader(headers, CONTENT_DISPOSITION);
0392: if (cd != null) {
0393: String cdl = cd.toLowerCase();
0394: if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
0395: ParameterParser parser = new ParameterParser();
0396: parser.setLowerCaseNames(true);
0397: // Parameter parser can handle null input
0398: Map params = parser.parse(cd, ';');
0399: if (params.containsKey("filename")) {
0400: fileName = (String) params.get("filename");
0401: if (fileName != null) {
0402: fileName = fileName.trim();
0403: } else {
0404: // Even if there is no value, the parameter is present,
0405: // so we return an empty file name rather than no file
0406: // name.
0407: fileName = "";
0408: }
0409: }
0410: }
0411: }
0412: return fileName;
0413: }
0414:
0415: /**
0416: * Retrieves the field name from the <code>Content-disposition</code>
0417: * header.
0418: *
0419: * @param headers A <code>Map</code> containing the HTTP request headers.
0420: *
0421: * @return The field name for the current <code>encapsulation</code>.
0422: */
0423: protected String getFieldName(Map /* String, String */headers) {
0424: String fieldName = null;
0425: String cd = getHeader(headers, CONTENT_DISPOSITION);
0426: if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) {
0427:
0428: ParameterParser parser = new ParameterParser();
0429: parser.setLowerCaseNames(true);
0430: // Parameter parser can handle null input
0431: Map params = parser.parse(cd, ';');
0432: fieldName = (String) params.get("name");
0433: if (fieldName != null) {
0434: fieldName = fieldName.trim();
0435: }
0436: }
0437: return fieldName;
0438: }
0439:
0440: /**
0441: * Creates a new {@link FileItem} instance.
0442: *
0443: * @param headers A <code>Map</code> containing the HTTP request
0444: * headers.
0445: * @param isFormField Whether or not this item is a form field, as
0446: * opposed to a file.
0447: *
0448: * @return A newly created <code>FileItem</code> instance.
0449: *
0450: * @throws FileUploadException if an error occurs.
0451: * @deprecated This method is no longer used in favour of
0452: * internally created instances of {@link FileItem}.
0453: */
0454: protected FileItem createItem(Map /* String, String */headers,
0455: boolean isFormField) throws FileUploadException {
0456: return getFileItemFactory().createItem(getFieldName(headers),
0457: getHeader(headers, CONTENT_TYPE), isFormField,
0458: getFileName(headers));
0459: }
0460:
0461: /**
0462: * <p> Parses the <code>header-part</code> and returns as key/value
0463: * pairs.
0464: *
0465: * <p> If there are multiple headers of the same names, the name
0466: * will map to a comma-separated list containing the values.
0467: *
0468: * @param headerPart The <code>header-part</code> of the current
0469: * <code>encapsulation</code>.
0470: *
0471: * @return A <code>Map</code> containing the parsed HTTP request headers.
0472: */
0473: protected Map /* String, String */parseHeaders(String headerPart) {
0474: final int len = headerPart.length();
0475: Map headers = new HashMap();
0476: int start = 0;
0477: for (;;) {
0478: int end = parseEndOfLine(headerPart, start);
0479: if (start == end) {
0480: break;
0481: }
0482: String header = headerPart.substring(start, end);
0483: start = end + 2;
0484: while (start < len) {
0485: int nonWs = start;
0486: while (nonWs < len) {
0487: char c = headerPart.charAt(nonWs);
0488: if (c != ' ' && c != '\t') {
0489: break;
0490: }
0491: ++nonWs;
0492: }
0493: if (nonWs == start) {
0494: break;
0495: }
0496: // Continuation line found
0497: end = parseEndOfLine(headerPart, nonWs);
0498: header += " " + headerPart.substring(nonWs, end);
0499: start = end + 2;
0500: }
0501: parseHeaderLine(headers, header);
0502: }
0503: return headers;
0504: }
0505:
0506: /**
0507: * Skips bytes until the end of the current line.
0508: * @param headerPart The headers, which are being parsed.
0509: * @param end Index of the last byte, which has yet been
0510: * processed.
0511: * @return Index of the \r\n sequence, which indicates
0512: * end of line.
0513: */
0514: private int parseEndOfLine(String headerPart, int end) {
0515: int index = end;
0516: for (;;) {
0517: int offset = headerPart.indexOf('\r', index);
0518: if (offset == -1 || offset + 1 >= headerPart.length()) {
0519: throw new IllegalStateException(
0520: "Expected headers to be terminated by an empty line.");
0521: }
0522: if (headerPart.charAt(offset + 1) == '\n') {
0523: return offset;
0524: }
0525: index = offset + 1;
0526: }
0527: }
0528:
0529: /**
0530: * Reads the next header line.
0531: * @param headers String with all headers.
0532: * @param header Map where to store the current header.
0533: */
0534: private void parseHeaderLine(Map headers, String header) {
0535: final int colonOffset = header.indexOf(':');
0536: if (colonOffset == -1) {
0537: // This header line is malformed, skip it.
0538: return;
0539: }
0540: String headerName = header.substring(0, colonOffset).trim()
0541: .toLowerCase();
0542: String headerValue = header.substring(header.indexOf(':') + 1)
0543: .trim();
0544: if (getHeader(headers, headerName) != null) {
0545: // More that one heder of that name exists,
0546: // append to the list.
0547: headers.put(headerName, getHeader(headers, headerName)
0548: + ',' + headerValue);
0549: } else {
0550: headers.put(headerName, headerValue);
0551: }
0552: }
0553:
0554: /**
0555: * Returns the header with the specified name from the supplied map. The
0556: * header lookup is case-insensitive.
0557: *
0558: * @param headers A <code>Map</code> containing the HTTP request headers.
0559: * @param name The name of the header to return.
0560: *
0561: * @return The value of specified header, or a comma-separated list if
0562: * there were multiple headers of that name.
0563: */
0564: protected final String getHeader(Map /* String, String */headers,
0565: String name) {
0566: return (String) headers.get(name.toLowerCase());
0567: }
0568:
0569: /**
0570: * The iterator, which is returned by
0571: * {@link FileUploadBase#getItemIterator(RequestContext)}.
0572: */
0573: private class FileItemIteratorImpl implements FileItemIterator {
0574: /**
0575: * Default implementation of {@link FileItemStream}.
0576: */
0577: private class FileItemStreamImpl implements FileItemStream {
0578: /** The file items content type.
0579: */
0580: private final String contentType;
0581: /** The file items field name.
0582: */
0583: private final String fieldName;
0584: /** The file items file name.
0585: */
0586: private final String name;
0587: /** Whether the file item is a form field.
0588: */
0589: private final boolean formField;
0590: /** The file items input stream.
0591: */
0592: private final InputStream stream;
0593: /** Whether the file item was already opened.
0594: */
0595: private boolean opened;
0596:
0597: /**
0598: * CReates a new instance.
0599: * @param pName The items file name, or null.
0600: * @param pFieldName The items field name.
0601: * @param pContentType The items content type, or null.
0602: * @param pFormField Whether the item is a form field.
0603: */
0604: FileItemStreamImpl(String pName, String pFieldName,
0605: String pContentType, boolean pFormField) {
0606: name = pName;
0607: fieldName = pFieldName;
0608: contentType = pContentType;
0609: formField = pFormField;
0610: InputStream istream = multi.newInputStream();
0611: if (fileSizeMax != -1) {
0612: istream = new LimitedInputStream(istream,
0613: fileSizeMax) {
0614: protected void raiseError(long pSizeMax,
0615: long pCount) throws IOException {
0616: FileUploadException e = new FileSizeLimitExceededException(
0617: "The field "
0618: + fieldName
0619: + " exceeds its maximum permitted "
0620: + " size of " + pSizeMax
0621: + " characters.", pCount,
0622: pSizeMax);
0623: throw new FileUploadIOException(e);
0624: }
0625: };
0626: }
0627: stream = istream;
0628: }
0629:
0630: /**
0631: * Returns the items content type, or null.
0632: * @return Content type, if known, or null.
0633: */
0634: public String getContentType() {
0635: return contentType;
0636: }
0637:
0638: /**
0639: * Returns the items field name.
0640: * @return Field name.
0641: */
0642: public String getFieldName() {
0643: return fieldName;
0644: }
0645:
0646: /**
0647: * Returns the items file name.
0648: * @return File name, if known, or null.
0649: */
0650: public String getName() {
0651: return name;
0652: }
0653:
0654: /**
0655: * Returns, whether this is a form field.
0656: * @return True, if the item is a form field,
0657: * otherwise false.
0658: */
0659: public boolean isFormField() {
0660: return formField;
0661: }
0662:
0663: /**
0664: * Returns an input stream, which may be used to
0665: * read the items contents.
0666: * @return Opened input stream.
0667: * @throws IOException An I/O error occurred.
0668: */
0669: public InputStream openStream() throws IOException {
0670: if (opened) {
0671: throw new IllegalStateException(
0672: "The stream was already opened.");
0673: }
0674: if (((Closeable) stream).isClosed()) {
0675: throw new FileItemStream.ItemSkippedException();
0676: }
0677: return stream;
0678: }
0679:
0680: /**
0681: * Closes the file item.
0682: * @throws IOException An I/O error occurred.
0683: */
0684: void close() throws IOException {
0685: stream.close();
0686: }
0687: }
0688:
0689: /**
0690: * The multi part stream to process.
0691: */
0692: private final MultipartStream multi;
0693: /**
0694: * The notifier, which used for triggering the
0695: * {@link ProgressListener}.
0696: */
0697: private final MultipartStream.ProgressNotifier notifier;
0698: /**
0699: * The boundary, which separates the various parts.
0700: */
0701: private final byte[] boundary;
0702: /**
0703: * The item, which we currently process.
0704: */
0705: private FileItemStreamImpl currentItem;
0706: /**
0707: * The current items field name.
0708: */
0709: private String currentFieldName;
0710: /**
0711: * Whether we are currently skipping the preamble.
0712: */
0713: private boolean skipPreamble;
0714: /**
0715: * Whether the current item may still be read.
0716: */
0717: private boolean itemValid;
0718: /**
0719: * Whether we have seen the end of the file.
0720: */
0721: private boolean eof;
0722:
0723: /**
0724: * Creates a new instance.
0725: * @param ctx The request context.
0726: * @throws FileUploadException An error occurred while
0727: * parsing the request.
0728: * @throws IOException An I/O error occurred.
0729: */
0730: FileItemIteratorImpl(RequestContext ctx)
0731: throws FileUploadException, IOException {
0732: if (ctx == null) {
0733: throw new NullPointerException("ctx parameter");
0734: }
0735:
0736: String contentType = ctx.getContentType();
0737: if ((null == contentType)
0738: || (!contentType.toLowerCase()
0739: .startsWith(MULTIPART))) {
0740: throw new InvalidContentTypeException(
0741: "the request doesn't contain a "
0742: + MULTIPART_FORM_DATA + " or "
0743: + MULTIPART_MIXED
0744: + " stream, content type header is "
0745: + contentType);
0746: }
0747:
0748: InputStream input = ctx.getInputStream();
0749:
0750: if (sizeMax >= 0) {
0751: int requestSize = ctx.getContentLength();
0752: if (requestSize == -1) {
0753: input = new LimitedInputStream(input, sizeMax) {
0754: protected void raiseError(long pSizeMax,
0755: long pCount) throws IOException {
0756: FileUploadException ex = new SizeLimitExceededException(
0757: "the request was rejected because"
0758: + " its size ("
0759: + pCount
0760: + ") exceeds the configured maximum"
0761: + " (" + pSizeMax + ")",
0762: pCount, pSizeMax);
0763: throw new FileUploadIOException(ex);
0764: }
0765: };
0766: } else {
0767: if (sizeMax >= 0 && requestSize > sizeMax) {
0768: throw new SizeLimitExceededException(
0769: "the request was rejected because its size ("
0770: + requestSize
0771: + ") exceeds the configured maximum ("
0772: + sizeMax + ")", requestSize,
0773: sizeMax);
0774: }
0775: }
0776: }
0777:
0778: String charEncoding = headerEncoding;
0779: if (charEncoding == null) {
0780: charEncoding = ctx.getCharacterEncoding();
0781: }
0782:
0783: boundary = getBoundary(contentType);
0784: if (boundary == null) {
0785: throw new FileUploadException(
0786: "the request was rejected because "
0787: + "no multipart boundary was found");
0788: }
0789:
0790: notifier = new MultipartStream.ProgressNotifier(listener,
0791: ctx.getContentLength());
0792: multi = new MultipartStream(input, boundary, notifier);
0793: multi.setHeaderEncoding(charEncoding);
0794:
0795: skipPreamble = true;
0796: findNextItem();
0797: }
0798:
0799: /**
0800: * Called for finding the nex item, if any.
0801: * @return True, if an next item was found, otherwise false.
0802: * @throws IOException An I/O error occurred.
0803: */
0804: private boolean findNextItem() throws IOException {
0805: if (eof) {
0806: return false;
0807: }
0808: if (currentItem != null) {
0809: currentItem.close();
0810: currentItem = null;
0811: }
0812: for (;;) {
0813: boolean nextPart;
0814: if (skipPreamble) {
0815: nextPart = multi.skipPreamble();
0816: } else {
0817: nextPart = multi.readBoundary();
0818: }
0819: if (!nextPart) {
0820: if (currentFieldName == null) {
0821: // Outer multipart terminated -> No more data
0822: eof = true;
0823: return false;
0824: }
0825: // Inner multipart terminated -> Return to parsing the outer
0826: multi.setBoundary(boundary);
0827: currentFieldName = null;
0828: continue;
0829: }
0830: Map headers = parseHeaders(multi.readHeaders());
0831: if (currentFieldName == null) {
0832: // We're parsing the outer multipart
0833: String fieldName = getFieldName(headers);
0834: if (fieldName != null) {
0835: String subContentType = getHeader(headers,
0836: CONTENT_TYPE);
0837: if (subContentType != null
0838: && subContentType.toLowerCase()
0839: .startsWith(MULTIPART_MIXED)) {
0840: currentFieldName = fieldName;
0841: // Multiple files associated with this field name
0842: byte[] subBoundary = getBoundary(subContentType);
0843: multi.setBoundary(subBoundary);
0844: skipPreamble = true;
0845: continue;
0846: }
0847: String fileName = getFileName(headers);
0848: currentItem = new FileItemStreamImpl(fileName,
0849: fieldName, getHeader(headers,
0850: CONTENT_TYPE), fileName == null);
0851: notifier.noteItem();
0852: itemValid = true;
0853: return true;
0854: }
0855: } else {
0856: String fileName = getFileName(headers);
0857: if (fileName != null) {
0858: currentItem = new FileItemStreamImpl(fileName,
0859: currentFieldName, getHeader(headers,
0860: CONTENT_TYPE), false);
0861: notifier.noteItem();
0862: itemValid = true;
0863: return true;
0864: }
0865: }
0866: multi.discardBodyData();
0867: }
0868: }
0869:
0870: /**
0871: * Returns, whether another instance of {@link FileItemStream}
0872: * is available.
0873: * @throws FileUploadException Parsing or processing the
0874: * file item failed.
0875: * @throws IOException Reading the file item failed.
0876: * @return True, if one or more additional file items
0877: * are available, otherwise false.
0878: */
0879: public boolean hasNext() throws FileUploadException,
0880: IOException {
0881: if (eof) {
0882: return false;
0883: }
0884: if (itemValid) {
0885: return true;
0886: }
0887: return findNextItem();
0888: }
0889:
0890: /**
0891: * Returns the next available {@link FileItemStream}.
0892: * @throws java.util.NoSuchElementException No more items are
0893: * available. Use {@link #hasNext()} to prevent this exception.
0894: * @throws FileUploadException Parsing or processing the
0895: * file item failed.
0896: * @throws IOException Reading the file item failed.
0897: * @return FileItemStream instance, which provides
0898: * access to the next file item.
0899: */
0900: public FileItemStream next() throws FileUploadException,
0901: IOException {
0902: if (eof || (!itemValid && !hasNext())) {
0903: throw new NoSuchElementException();
0904: }
0905: itemValid = false;
0906: return currentItem;
0907: }
0908: }
0909:
0910: /**
0911: * This exception is thrown for hiding an inner
0912: * {@link FileUploadException} in an {@link IOException}.
0913: */
0914: public static class FileUploadIOException extends IOException {
0915: /** The exceptions UID, for serializing an instance.
0916: */
0917: private static final long serialVersionUID = -7047616958165584154L;
0918: /** The exceptions cause; we overwrite the parent
0919: * classes field, which is available since Java
0920: * 1.4 only.
0921: */
0922: private final FileUploadException cause;
0923:
0924: /**
0925: * Creates a <code>FileUploadIOException</code> with the
0926: * given cause.
0927: * @param pCause The exceptions cause, if any, or null.
0928: */
0929: public FileUploadIOException(FileUploadException pCause) {
0930: // We're not doing super(pCause) cause of 1.3 compatibility.
0931: cause = pCause;
0932: }
0933:
0934: /**
0935: * Returns the exceptions cause.
0936: * @return The exceptions cause, if any, or null.
0937: */
0938: public Throwable getCause() {
0939: return cause;
0940: }
0941: }
0942:
0943: /**
0944: * Thrown to indicate that the request is not a multipart request.
0945: */
0946: public static class InvalidContentTypeException extends
0947: FileUploadException {
0948: /** The exceptions UID, for serializing an instance.
0949: */
0950: private static final long serialVersionUID = -9073026332015646668L;
0951:
0952: /**
0953: * Constructs a <code>InvalidContentTypeException</code> with no
0954: * detail message.
0955: */
0956: public InvalidContentTypeException() {
0957: // Nothing to do.
0958: }
0959:
0960: /**
0961: * Constructs an <code>InvalidContentTypeException</code> with
0962: * the specified detail message.
0963: *
0964: * @param message The detail message.
0965: */
0966: public InvalidContentTypeException(String message) {
0967: super (message);
0968: }
0969: }
0970:
0971: /**
0972: * Thrown to indicate an IOException.
0973: */
0974: public static class IOFileUploadException extends
0975: FileUploadException {
0976: /** The exceptions UID, for serializing an instance.
0977: */
0978: private static final long serialVersionUID = 1749796615868477269L;
0979: /** The exceptions cause; we overwrite the parent
0980: * classes field, which is available since Java
0981: * 1.4 only.
0982: */
0983: private final IOException cause;
0984:
0985: /**
0986: * Creates a new instance with the given cause.
0987: * @param pMsg The detail message.
0988: * @param pException The exceptions cause.
0989: */
0990: public IOFileUploadException(String pMsg, IOException pException) {
0991: super (pMsg);
0992: cause = pException;
0993: }
0994:
0995: /**
0996: * Returns the exceptions cause.
0997: * @return The exceptions cause, if any, or null.
0998: */
0999: public Throwable getCause() {
1000: return cause;
1001: }
1002: }
1003:
1004: /** This exception is thrown, if a requests permitted size
1005: * is exceeded.
1006: */
1007: protected abstract static class SizeException extends
1008: FileUploadException {
1009: /**
1010: * The actual size of the request.
1011: */
1012: private final long actual;
1013:
1014: /**
1015: * The maximum permitted size of the request.
1016: */
1017: private final long permitted;
1018:
1019: /**
1020: * Creates a new instance.
1021: * @param message The detail message.
1022: * @param actual The actual number of bytes in the request.
1023: * @param permitted The requests size limit, in bytes.
1024: */
1025: protected SizeException(String message, long actual,
1026: long permitted) {
1027: super (message);
1028: this .actual = actual;
1029: this .permitted = permitted;
1030: }
1031:
1032: /**
1033: * Retrieves the actual size of the request.
1034: *
1035: * @return The actual size of the request.
1036: */
1037: public long getActualSize() {
1038: return actual;
1039: }
1040:
1041: /**
1042: * Retrieves the permitted size of the request.
1043: *
1044: * @return The permitted size of the request.
1045: */
1046: public long getPermittedSize() {
1047: return permitted;
1048: }
1049: }
1050:
1051: /**
1052: * Thrown to indicate that the request size is not specified. In other
1053: * words, it is thrown, if the content-length header is missing or
1054: * contains the value -1.
1055: * @deprecated As of commons-fileupload 1.2, the presence of a
1056: * content-length header is no longer required.
1057: */
1058: public static class UnknownSizeException extends
1059: FileUploadException {
1060: /** The exceptions UID, for serializing an instance.
1061: */
1062: private static final long serialVersionUID = 7062279004812015273L;
1063:
1064: /**
1065: * Constructs a <code>UnknownSizeException</code> with no
1066: * detail message.
1067: */
1068: public UnknownSizeException() {
1069: super ();
1070: }
1071:
1072: /**
1073: * Constructs an <code>UnknownSizeException</code> with
1074: * the specified detail message.
1075: *
1076: * @param message The detail message.
1077: */
1078: public UnknownSizeException(String message) {
1079: super (message);
1080: }
1081: }
1082:
1083: /**
1084: * Thrown to indicate that the request size exceeds the configured maximum.
1085: */
1086: public static class SizeLimitExceededException extends
1087: SizeException {
1088: /** The exceptions UID, for serializing an instance.
1089: */
1090: private static final long serialVersionUID = -2474893167098052828L;
1091:
1092: /**
1093: * @deprecated Replaced by
1094: * {@link #SizeLimitExceededException(String, long, long)}
1095: */
1096: public SizeLimitExceededException() {
1097: this (null, 0, 0);
1098: }
1099:
1100: /**
1101: * @deprecated Replaced by
1102: * {@link #SizeLimitExceededException(String, long, long)}
1103: * @param message The exceptions detail message.
1104: */
1105: public SizeLimitExceededException(String message) {
1106: this (message, 0, 0);
1107: }
1108:
1109: /**
1110: * Constructs a <code>SizeExceededException</code> with
1111: * the specified detail message, and actual and permitted sizes.
1112: *
1113: * @param message The detail message.
1114: * @param actual The actual request size.
1115: * @param permitted The maximum permitted request size.
1116: */
1117: public SizeLimitExceededException(String message, long actual,
1118: long permitted) {
1119: super (message, actual, permitted);
1120: }
1121: }
1122:
1123: /**
1124: * Thrown to indicate that A files size exceeds the configured maximum.
1125: */
1126: public static class FileSizeLimitExceededException extends
1127: SizeException {
1128: /** The exceptions UID, for serializing an instance.
1129: */
1130: private static final long serialVersionUID = 8150776562029630058L;
1131:
1132: /**
1133: * Constructs a <code>SizeExceededException</code> with
1134: * the specified detail message, and actual and permitted sizes.
1135: *
1136: * @param message The detail message.
1137: * @param actual The actual request size.
1138: * @param permitted The maximum permitted request size.
1139: */
1140: public FileSizeLimitExceededException(String message,
1141: long actual, long permitted) {
1142: super (message, actual, permitted);
1143: }
1144: }
1145:
1146: /**
1147: * Returns the progress listener.
1148: * @return The progress listener, if any, or null.
1149: */
1150: public ProgressListener getProgressListener() {
1151: return listener;
1152: }
1153:
1154: /**
1155: * Sets the progress listener.
1156: * @param pListener The progress listener, if any. Defaults to null.
1157: */
1158: public void setProgressListener(ProgressListener pListener) {
1159: listener = pListener;
1160: }
1161: }
|