Home » commons-fileupload-1.2-src » org.apache.commons » fileupload » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.commons.fileupload;
   18   
   19   import java.io.IOException;
   20   import java.io.InputStream;
   21   import java.io.UnsupportedEncodingException;
   22   import java.util.ArrayList;
   23   import java.util.HashMap;
   24   import java.util.List;
   25   import java.util.Map;
   26   import java.util.NoSuchElementException;
   27   
   28   import javax.servlet.http.HttpServletRequest;
   29   
   30   import org.apache.commons.fileupload.servlet.ServletFileUpload;
   31   import org.apache.commons.fileupload.servlet.ServletRequestContext;
   32   import org.apache.commons.fileupload.util.Closeable;
   33   import org.apache.commons.fileupload.util.LimitedInputStream;
   34   import org.apache.commons.fileupload.util.Streams;
   35   
   36   
   37   /**
   38    * <p>High level API for processing file uploads.</p>
   39    *
   40    * <p>This class handles multiple files per single HTML widget, sent using
   41    * <code>multipart/mixed</code> encoding type, as specified by
   42    * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
   43    * #parseRequest(HttpServletRequest)} to acquire a list of {@link
   44    * org.apache.commons.fileupload.FileItem}s associated with a given HTML
   45    * widget.</p>
   46    *
   47    * <p>How the data for individual parts is stored is determined by the factory
   48    * used to create them; a given part may be in memory, on disk, or somewhere
   49    * else.</p>
   50    *
   51    * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
   52    * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
   53    * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
   54    * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
   55    * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
   56    * @author Sean C. Sullivan
   57    *
   58    * @version $Id: FileUploadBase.java 502350 2007-02-01 20:42:48Z jochen $
   59    */
   60   public abstract class FileUploadBase {
   61   
   62       // ---------------------------------------------------------- Class methods
   63   
   64   
   65       /**
   66        * <p>Utility method that determines whether the request contains multipart
   67        * content.</p>
   68        *
   69        * <p><strong>NOTE:</strong>This method will be moved to the
   70        * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
   71        * Unfortunately, since this method is static, it is not possible to
   72        * provide its replacement until this method is removed.</p>
   73        *
   74        * @param ctx The request context to be evaluated. Must be non-null.
   75        *
   76        * @return <code>true</code> if the request is multipart;
   77        *         <code>false</code> otherwise.
   78        */
   79       public static final boolean isMultipartContent(RequestContext ctx) {
   80           String contentType = ctx.getContentType();
   81           if (contentType == null) {
   82               return false;
   83           }
   84           if (contentType.toLowerCase().startsWith(MULTIPART)) {
   85               return true;
   86           }
   87           return false;
   88       }
   89   
   90   
   91       /**
   92        * Utility method that determines whether the request contains multipart
   93        * content.
   94        *
   95        * @param req The servlet request to be evaluated. Must be non-null.
   96        *
   97        * @return <code>true</code> if the request is multipart;
   98        *         <code>false</code> otherwise.
   99        *
  100        * @deprecated Use the method on <code>ServletFileUpload</code> instead.
  101        */
  102       public static boolean isMultipartContent(HttpServletRequest req) {
  103           return ServletFileUpload.isMultipartContent(req);
  104       }
  105   
  106   
  107       // ----------------------------------------------------- Manifest constants
  108   
  109   
  110       /**
  111        * HTTP content type header name.
  112        */
  113       public static final String CONTENT_TYPE = "Content-type";
  114   
  115   
  116       /**
  117        * HTTP content disposition header name.
  118        */
  119       public static final String CONTENT_DISPOSITION = "Content-disposition";
  120   
  121   
  122       /**
  123        * Content-disposition value for form data.
  124        */
  125       public static final String FORM_DATA = "form-data";
  126   
  127   
  128       /**
  129        * Content-disposition value for file attachment.
  130        */
  131       public static final String ATTACHMENT = "attachment";
  132   
  133   
  134       /**
  135        * Part of HTTP content type header.
  136        */
  137       public static final String MULTIPART = "multipart/";
  138   
  139   
  140       /**
  141        * HTTP content type header for multipart forms.
  142        */
  143       public static final String MULTIPART_FORM_DATA = "multipart/form-data";
  144   
  145   
  146       /**
  147        * HTTP content type header for multiple uploads.
  148        */
  149       public static final String MULTIPART_MIXED = "multipart/mixed";
  150   
  151   
  152       /**
  153        * The maximum length of a single header line that will be parsed
  154        * (1024 bytes).
  155        * @deprecated This constant is no longer used. As of commons-fileupload
  156        *   1.2, the only applicable limit is the total size of a parts headers,
  157        *   {@link MultipartStream#HEADER_PART_SIZE_MAX}.
  158        */
  159       public static final int MAX_HEADER_SIZE = 1024;
  160   
  161   
  162       // ----------------------------------------------------------- Data members
  163   
  164   
  165       /**
  166        * The maximum size permitted for the complete request, as opposed to
  167        * {@link #fileSizeMax}. A value of -1 indicates no maximum.
  168        */
  169       private long sizeMax = -1;
  170   
  171       /**
  172        * The maximum size permitted for a single uploaded file, as opposed
  173        * to {@link #sizeMax}. A value of -1 indicates no maximum.
  174        */
  175       private long fileSizeMax = -1;
  176   
  177       /**
  178        * The content encoding to use when reading part headers.
  179        */
  180       private String headerEncoding;
  181   
  182       /**
  183        * The progress listener.
  184        */
  185       private ProgressListener listener;
  186   
  187       // ----------------------------------------------------- Property accessors
  188   
  189   
  190       /**
  191        * Returns the factory class used when creating file items.
  192        *
  193        * @return The factory class for new file items.
  194        */
  195       public abstract FileItemFactory getFileItemFactory();
  196   
  197   
  198       /**
  199        * Sets the factory class to use when creating file items.
  200        *
  201        * @param factory The factory class for new file items.
  202        */
  203       public abstract void setFileItemFactory(FileItemFactory factory);
  204   
  205   
  206       /**
  207        * Returns the maximum allowed size of a complete request, as opposed
  208        * to {@link #getFileSizeMax()}.
  209        *
  210        * @return The maximum allowed size, in bytes. The default value of
  211        *   -1 indicates, that there is no limit.
  212        *
  213        * @see #setSizeMax(long)
  214        *
  215        */
  216       public long getSizeMax() {
  217           return sizeMax;
  218       }
  219   
  220   
  221       /**
  222        * Sets the maximum allowed size of a complete request, as opposed
  223        * to {@link #setFileSizeMax(long)}.
  224        *
  225        * @param sizeMax The maximum allowed size, in bytes. The default value of
  226        *   -1 indicates, that there is no limit.
  227        *
  228        * @see #getSizeMax()
  229        *
  230        */
  231       public void setSizeMax(long sizeMax) {
  232           this.sizeMax = sizeMax;
  233       }
  234   
  235       /**
  236        * Returns the maximum allowed size of a single uploaded file,
  237        * as opposed to {@link #getSizeMax()}.
  238        *
  239        * @see #setFileSizeMax(long)
  240        * @return Maximum size of a single uploaded file.
  241        */
  242       public long getFileSizeMax() {
  243           return fileSizeMax;
  244       }
  245   
  246       /**
  247        * Sets the maximum allowed size of a single uploaded file,
  248        * as opposed to {@link #getSizeMax()}.
  249        *
  250        * @see #getFileSizeMax()
  251        * @param fileSizeMax Maximum size of a single uploaded file.
  252        */
  253       public void setFileSizeMax(long fileSizeMax) {
  254           this.fileSizeMax = fileSizeMax;
  255       }
  256   
  257       /**
  258        * Retrieves the character encoding used when reading the headers of an
  259        * individual part. When not specified, or <code>null</code>, the request
  260        * encoding is used. If that is also not specified, or <code>null</code>,
  261        * the platform default encoding is used.
  262        *
  263        * @return The encoding used to read part headers.
  264        */
  265       public String getHeaderEncoding() {
  266           return headerEncoding;
  267       }
  268   
  269   
  270       /**
  271        * Specifies the character encoding to be used when reading the headers of
  272        * individual part. When not specified, or <code>null</code>, the request
  273        * encoding is used. If that is also not specified, or <code>null</code>,
  274        * the platform default encoding is used.
  275        *
  276        * @param encoding The encoding used to read part headers.
  277        */
  278       public void setHeaderEncoding(String encoding) {
  279           headerEncoding = encoding;
  280       }
  281   
  282   
  283       // --------------------------------------------------------- Public methods
  284   
  285   
  286       /**
  287        * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
  288        * compliant <code>multipart/form-data</code> stream.
  289        *
  290        * @param req The servlet request to be parsed.
  291        *
  292        * @return A list of <code>FileItem</code> instances parsed from the
  293        *         request, in the order that they were transmitted.
  294        *
  295        * @throws FileUploadException if there are problems reading/parsing
  296        *                             the request or storing files.
  297        *
  298        * @deprecated Use the method in <code>ServletFileUpload</code> instead.
  299        */
  300       public List /* FileItem */ parseRequest(HttpServletRequest req)
  301       throws FileUploadException {
  302           return parseRequest(new ServletRequestContext(req));
  303       }
  304   
  305       /**
  306        * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
  307        * compliant <code>multipart/form-data</code> stream.
  308        *
  309        * @param ctx The context for the request to be parsed.
  310        *
  311        * @return An iterator to instances of <code>FileItemStream</code>
  312        *         parsed from the request, in the order that they were
  313        *         transmitted.
  314        *
  315        * @throws FileUploadException if there are problems reading/parsing
  316        *                             the request or storing files.
  317        * @throws IOException An I/O error occurred. This may be a network
  318        *   error while communicating with the client or a problem while
  319        *   storing the uploaded content.
  320        */
  321       public FileItemIterator getItemIterator(RequestContext ctx)
  322       throws FileUploadException, IOException {
  323           return new FileItemIteratorImpl(ctx);
  324       }
  325   
  326       /**
  327        * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
  328        * compliant <code>multipart/form-data</code> stream.
  329        *
  330        * @param ctx The context for the request to be parsed.
  331        *
  332        * @return A list of <code>FileItem</code> instances parsed from the
  333        *         request, in the order that they were transmitted.
  334        *
  335        * @throws FileUploadException if there are problems reading/parsing
  336        *                             the request or storing files.
  337        */
  338       public List /* FileItem */ parseRequest(RequestContext ctx)
  339               throws FileUploadException {
  340           try {
  341               FileItemIterator iter = getItemIterator(ctx);
  342               List items = new ArrayList();
  343               FileItemFactory fac = getFileItemFactory();
  344               if (fac == null) {
  345                   throw new NullPointerException(
  346                       "No FileItemFactory has been set.");
  347               }
  348               while (iter.hasNext()) {
  349                   FileItemStream item = iter.next();
  350                   FileItem fileItem = fac.createItem(item.getFieldName(),
  351                           item.getContentType(), item.isFormField(),
  352                           item.getName());
  353                   try {
  354                       Streams.copy(item.openStream(), fileItem.getOutputStream(),
  355                               true);
  356                   } catch (FileUploadIOException e) {
  357                       throw (FileUploadException) e.getCause();
  358                   } catch (IOException e) {
  359                       throw new IOFileUploadException(
  360                               "Processing of " + MULTIPART_FORM_DATA
  361                               + " request failed. " + e.getMessage(), e);
  362                   }
  363                   items.add(fileItem);
  364               }
  365               return items;
  366           } catch (FileUploadIOException e) {
  367               throw (FileUploadException) e.getCause();
  368           } catch (IOException e) {
  369               throw new FileUploadException(e.getMessage(), e);
  370           }
  371       }
  372   
  373   
  374       // ------------------------------------------------------ Protected methods
  375   
  376   
  377       /**
  378        * Retrieves the boundary from the <code>Content-type</code> header.
  379        *
  380        * @param contentType The value of the content type header from which to
  381        *                    extract the boundary value.
  382        *
  383        * @return The boundary, as a byte array.
  384        */
  385       protected byte[] getBoundary(String contentType) {
  386           ParameterParser parser = new ParameterParser();
  387           parser.setLowerCaseNames(true);
  388           // Parameter parser can handle null input
  389           Map params = parser.parse(contentType, ';');
  390           String boundaryStr = (String) params.get("boundary");
  391   
  392           if (boundaryStr == null) {
  393               return null;
  394           }
  395           byte[] boundary;
  396           try {
  397               boundary = boundaryStr.getBytes("ISO-8859-1");
  398           } catch (UnsupportedEncodingException e) {
  399               boundary = boundaryStr.getBytes();
  400           }
  401           return boundary;
  402       }
  403   
  404   
  405       /**
  406        * Retrieves the file name from the <code>Content-disposition</code>
  407        * header.
  408        *
  409        * @param headers A <code>Map</code> containing the HTTP request headers.
  410        *
  411        * @return The file name for the current <code>encapsulation</code>.
  412        */
  413       protected String getFileName(Map /* String, String */ headers) {
  414           String fileName = null;
  415           String cd = getHeader(headers, CONTENT_DISPOSITION);
  416           if (cd != null) {
  417               String cdl = cd.toLowerCase();
  418               if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
  419                   ParameterParser parser = new ParameterParser();
  420                   parser.setLowerCaseNames(true);
  421                   // Parameter parser can handle null input
  422                   Map params = parser.parse(cd, ';');
  423                   if (params.containsKey("filename")) {
  424                       fileName = (String) params.get("filename");
  425                       if (fileName != null) {
  426                           fileName = fileName.trim();
  427                       } else {
  428                           // Even if there is no value, the parameter is present,
  429                           // so we return an empty file name rather than no file
  430                           // name.
  431                           fileName = "";
  432                       }
  433                   }
  434               }
  435           }
  436           return fileName;
  437       }
  438   
  439   
  440       /**
  441        * Retrieves the field name from the <code>Content-disposition</code>
  442        * header.
  443        *
  444        * @param headers A <code>Map</code> containing the HTTP request headers.
  445        *
  446        * @return The field name for the current <code>encapsulation</code>.
  447        */
  448       protected String getFieldName(Map /* String, String */ headers) {
  449           String fieldName = null;
  450           String cd = getHeader(headers, CONTENT_DISPOSITION);
  451           if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) {
  452   
  453               ParameterParser parser = new ParameterParser();
  454               parser.setLowerCaseNames(true);
  455               // Parameter parser can handle null input
  456               Map params = parser.parse(cd, ';');
  457               fieldName = (String) params.get("name");
  458               if (fieldName != null) {
  459                   fieldName = fieldName.trim();
  460               }
  461           }
  462           return fieldName;
  463       }
  464   
  465   
  466       /**
  467        * Creates a new {@link FileItem} instance.
  468        *
  469        * @param headers       A <code>Map</code> containing the HTTP request
  470        *                      headers.
  471        * @param isFormField   Whether or not this item is a form field, as
  472        *                      opposed to a file.
  473        *
  474        * @return A newly created <code>FileItem</code> instance.
  475        *
  476        * @throws FileUploadException if an error occurs.
  477        * @deprecated This method is no longer used in favour of
  478        *   internally created instances of {@link FileItem}.
  479        */
  480       protected FileItem createItem(Map /* String, String */ headers,
  481                                     boolean isFormField)
  482           throws FileUploadException {
  483           return getFileItemFactory().createItem(getFieldName(headers),
  484                   getHeader(headers, CONTENT_TYPE),
  485                   isFormField,
  486                   getFileName(headers));
  487       }
  488   
  489   
  490       /**
  491        * <p> Parses the <code>header-part</code> and returns as key/value
  492        * pairs.
  493        *
  494        * <p> If there are multiple headers of the same names, the name
  495        * will map to a comma-separated list containing the values.
  496        *
  497        * @param headerPart The <code>header-part</code> of the current
  498        *                   <code>encapsulation</code>.
  499        *
  500        * @return A <code>Map</code> containing the parsed HTTP request headers.
  501        */
  502       protected Map /* String, String */ parseHeaders(String headerPart) {
  503           final int len = headerPart.length();
  504           Map headers = new HashMap();
  505           int start = 0;
  506           for (;;) {
  507               int end = parseEndOfLine(headerPart, start);
  508               if (start == end) {
  509                   break;
  510               }
  511               String header = headerPart.substring(start, end);
  512               start = end + 2;
  513               while (start < len) {
  514                   int nonWs = start;
  515                   while (nonWs < len) {
  516                       char c = headerPart.charAt(nonWs);
  517                       if (c != ' '  &&  c != '\t') {
  518                           break;
  519                       }
  520                       ++nonWs;
  521                   }
  522                   if (nonWs == start) {
  523                       break;
  524                   }
  525                   // Continuation line found
  526                   end = parseEndOfLine(headerPart, nonWs);
  527                   header += " " + headerPart.substring(nonWs, end);
  528                   start = end + 2;
  529               }
  530               parseHeaderLine(headers, header);
  531           }
  532           return headers;
  533       }
  534   
  535       /**
  536        * Skips bytes until the end of the current line.
  537        * @param headerPart The headers, which are being parsed.
  538        * @param end Index of the last byte, which has yet been
  539        *   processed.
  540        * @return Index of the \r\n sequence, which indicates
  541        *   end of line.
  542        */
  543       private int parseEndOfLine(String headerPart, int end) {
  544           int index = end;
  545           for (;;) {
  546               int offset = headerPart.indexOf('\r', index);
  547               if (offset == -1  ||  offset + 1 >= headerPart.length()) {
  548                   throw new IllegalStateException(
  549                       "Expected headers to be terminated by an empty line.");
  550               }
  551               if (headerPart.charAt(offset + 1) == '\n') {
  552                   return offset;
  553               }
  554               index = offset + 1;
  555           }
  556       }
  557   
  558       /**
  559        * Reads the next header line.
  560        * @param headers String with all headers.
  561        * @param header Map where to store the current header.
  562        */
  563       private void parseHeaderLine(Map headers, String header) {
  564           final int colonOffset = header.indexOf(':');
  565           if (colonOffset == -1) {
  566               // This header line is malformed, skip it.
  567               return;
  568           }
  569           String headerName = header.substring(0, colonOffset)
  570               .trim().toLowerCase();
  571           String headerValue =
  572               header.substring(header.indexOf(':') + 1).trim();
  573           if (getHeader(headers, headerName) != null) {
  574               // More that one heder of that name exists,
  575               // append to the list.
  576               headers.put(headerName,
  577                       getHeader(headers, headerName) + ','
  578                       + headerValue);
  579           } else {
  580               headers.put(headerName, headerValue);
  581           }
  582       }
  583   
  584       /**
  585        * Returns the header with the specified name from the supplied map. The
  586        * header lookup is case-insensitive.
  587        *
  588        * @param headers A <code>Map</code> containing the HTTP request headers.
  589        * @param name    The name of the header to return.
  590        *
  591        * @return The value of specified header, or a comma-separated list if
  592        *         there were multiple headers of that name.
  593        */
  594       protected final String getHeader(Map /* String, String */ headers,
  595               String name) {
  596           return (String) headers.get(name.toLowerCase());
  597       }
  598   
  599       /**
  600        * The iterator, which is returned by
  601        * {@link FileUploadBase#getItemIterator(RequestContext)}.
  602        */
  603       private class FileItemIteratorImpl implements FileItemIterator {
  604           /**
  605            * Default implementation of {@link FileItemStream}.
  606            */
  607           private class FileItemStreamImpl implements FileItemStream {
  608               /** The file items content type.
  609                */
  610               private final String contentType;
  611               /** The file items field name.
  612                */
  613               private final String fieldName;
  614               /** The file items file name.
  615                */
  616               private final String name;
  617               /** Whether the file item is a form field.
  618                */
  619               private final boolean formField;
  620               /** The file items input stream.
  621                */
  622               private final InputStream stream;
  623               /** Whether the file item was already opened.
  624                */
  625               private boolean opened;
  626   
  627               /**
  628                * CReates a new instance.
  629                * @param pName The items file name, or null.
  630                * @param pFieldName The items field name.
  631                * @param pContentType The items content type, or null.
  632                * @param pFormField Whether the item is a form field.
  633                */
  634               FileItemStreamImpl(String pName, String pFieldName,
  635                       String pContentType, boolean pFormField) {
  636                   name = pName;
  637                   fieldName = pFieldName;
  638                   contentType = pContentType;
  639                   formField = pFormField;
  640                   InputStream istream = multi.newInputStream();
  641                   if (fileSizeMax != -1) {
  642                       istream = new LimitedInputStream(istream, fileSizeMax) {
  643                           protected void raiseError(long pSizeMax, long pCount)
  644                                   throws IOException {
  645                               FileUploadException e =
  646                                   new FileSizeLimitExceededException(
  647                                       "The field " + fieldName
  648                                       + " exceeds its maximum permitted "
  649                                       + " size of " + pSizeMax
  650                                       + " characters.",
  651                                       pCount, pSizeMax);
  652                               throw new FileUploadIOException(e);
  653                           }
  654                       };
  655                   }
  656                   stream = istream;
  657               }
  658   
  659               /**
  660                * Returns the items content type, or null.
  661                * @return Content type, if known, or null.
  662                */
  663               public String getContentType() {
  664                   return contentType;
  665               }
  666   
  667               /**
  668                * Returns the items field name.
  669                * @return Field name.
  670                */
  671               public String getFieldName() {
  672                   return fieldName;
  673               }
  674   
  675               /**
  676                * Returns the items file name.
  677                * @return File name, if known, or null.
  678                */
  679               public String getName() {
  680                   return name;
  681               }
  682   
  683               /**
  684                * Returns, whether this is a form field.
  685                * @return True, if the item is a form field,
  686                *   otherwise false.
  687                */
  688               public boolean isFormField() {
  689                   return formField;
  690               }
  691   
  692               /**
  693                * Returns an input stream, which may be used to
  694                * read the items contents.
  695                * @return Opened input stream.
  696                * @throws IOException An I/O error occurred.
  697                */
  698               public InputStream openStream() throws IOException {
  699                   if (opened) {
  700                       throw new IllegalStateException(
  701                               "The stream was already opened.");
  702                   }
  703                   if (((Closeable) stream).isClosed()) {
  704                       throw new FileItemStream.ItemSkippedException();
  705                   }
  706                   return stream;
  707               }
  708   
  709               /**
  710                * Closes the file item.
  711                * @throws IOException An I/O error occurred.
  712                */
  713               void close() throws IOException {
  714                   stream.close();
  715               }
  716           }
  717   
  718           /**
  719            * The multi part stream to process.
  720            */
  721           private final MultipartStream multi;
  722           /**
  723            * The notifier, which used for triggering the
  724            * {@link ProgressListener}.
  725            */
  726           private final MultipartStream.ProgressNotifier notifier;
  727           /**
  728            * The boundary, which separates the various parts.
  729            */
  730           private final byte[] boundary;
  731           /**
  732            * The item, which we currently process.
  733            */
  734           private FileItemStreamImpl currentItem;
  735           /**
  736            * The current items field name.
  737            */
  738           private String currentFieldName;
  739           /**
  740            * Whether we are currently skipping the preamble.
  741            */
  742           private boolean skipPreamble;
  743           /**
  744            * Whether the current item may still be read.
  745            */
  746           private boolean itemValid;
  747           /**
  748            * Whether we have seen the end of the file.
  749            */
  750           private boolean eof;
  751   
  752           /**
  753            * Creates a new instance.
  754            * @param ctx The request context.
  755            * @throws FileUploadException An error occurred while
  756            *   parsing the request.
  757            * @throws IOException An I/O error occurred.
  758            */
  759           FileItemIteratorImpl(RequestContext ctx)
  760                   throws FileUploadException, IOException {
  761               if (ctx == null) {
  762                   throw new NullPointerException("ctx parameter");
  763               }
  764   
  765               String contentType = ctx.getContentType();
  766               if ((null == contentType)
  767                       || (!contentType.toLowerCase().startsWith(MULTIPART))) {
  768                   throw new InvalidContentTypeException(
  769                           "the request doesn't contain a "
  770                           + MULTIPART_FORM_DATA
  771                           + " or "
  772                           + MULTIPART_MIXED
  773                           + " stream, content type header is "
  774                           + contentType);
  775               }
  776   
  777               InputStream input = ctx.getInputStream();
  778   
  779               if (sizeMax >= 0) {
  780                   int requestSize = ctx.getContentLength();
  781                   if (requestSize == -1) {
  782                       input = new LimitedInputStream(input, sizeMax) {
  783                           protected void raiseError(long pSizeMax, long pCount)
  784                                   throws IOException {
  785                               FileUploadException ex =
  786                                   new SizeLimitExceededException(
  787                                       "the request was rejected because"
  788                                       + " its size (" + pCount
  789                                       + ") exceeds the configured maximum"
  790                                       + " (" + pSizeMax + ")",
  791                                       pCount, pSizeMax);
  792                               throw new FileUploadIOException(ex);
  793                           }
  794                       };
  795                   } else {
  796                       if (sizeMax >= 0 && requestSize > sizeMax) {
  797                           throw new SizeLimitExceededException(
  798                                   "the request was rejected because its size ("
  799                                   + requestSize
  800                                   + ") exceeds the configured maximum ("
  801                                   + sizeMax + ")",
  802                                   requestSize, sizeMax);
  803                       }
  804                   }
  805               }
  806   
  807               String charEncoding = headerEncoding;
  808               if (charEncoding == null) {
  809                   charEncoding = ctx.getCharacterEncoding();
  810               }
  811   
  812               boundary = getBoundary(contentType);
  813               if (boundary == null) {
  814                   throw new FileUploadException(
  815                           "the request was rejected because "
  816                           + "no multipart boundary was found");
  817               }
  818   
  819               notifier = new MultipartStream.ProgressNotifier(listener,
  820                       ctx.getContentLength());
  821               multi = new MultipartStream(input, boundary, notifier);
  822               multi.setHeaderEncoding(charEncoding);
  823   
  824               skipPreamble = true;
  825               findNextItem();
  826           }
  827   
  828           /**
  829            * Called for finding the nex item, if any.
  830            * @return True, if an next item was found, otherwise false.
  831            * @throws IOException An I/O error occurred.
  832            */
  833           private boolean findNextItem() throws IOException {
  834               if (eof) {
  835                   return false;
  836               }
  837               if (currentItem != null) {
  838                   currentItem.close();
  839                   currentItem = null;
  840               }
  841               for (;;) {
  842                   boolean nextPart;
  843                   if (skipPreamble) {
  844                       nextPart = multi.skipPreamble();
  845                   } else {
  846                       nextPart = multi.readBoundary();
  847                   }
  848                   if (!nextPart) {
  849                       if (currentFieldName == null) {
  850                           // Outer multipart terminated -> No more data
  851                           eof = true;
  852                           return false;
  853                       }
  854                       // Inner multipart terminated -> Return to parsing the outer
  855                       multi.setBoundary(boundary);
  856                       currentFieldName = null;
  857                       continue;
  858                   }
  859                   Map headers = parseHeaders(multi.readHeaders());
  860                   if (currentFieldName == null) {
  861                       // We're parsing the outer multipart
  862                       String fieldName = getFieldName(headers);
  863                       if (fieldName != null) {
  864                           String subContentType
  865                               = getHeader(headers, CONTENT_TYPE);
  866                           if (subContentType != null
  867                                   &&  subContentType.toLowerCase()
  868                                           .startsWith(MULTIPART_MIXED)) {
  869                               currentFieldName = fieldName;
  870                               // Multiple files associated with this field name
  871                               byte[] subBoundary = getBoundary(subContentType);
  872                               multi.setBoundary(subBoundary);
  873                               skipPreamble = true;
  874                               continue;
  875                           }
  876                           String fileName = getFileName(headers);
  877                           currentItem = new FileItemStreamImpl(fileName,
  878                                   fieldName, getHeader(headers, CONTENT_TYPE),
  879                                   fileName == null);
  880                           notifier.noteItem();
  881                           itemValid = true;
  882                           return true;
  883                       }
  884                   } else {
  885                       String fileName = getFileName(headers);
  886                       if (fileName != null) {
  887                           currentItem = new FileItemStreamImpl(fileName,
  888                                   currentFieldName,
  889                                   getHeader(headers, CONTENT_TYPE),
  890                                   false);
  891                           notifier.noteItem();
  892                           itemValid = true;
  893                           return true;
  894                       }
  895                   }
  896                   multi.discardBodyData();
  897               }
  898           }
  899   
  900           /**
  901            * Returns, whether another instance of {@link FileItemStream}
  902            * is available.
  903            * @throws FileUploadException Parsing or processing the
  904            *   file item failed.
  905            * @throws IOException Reading the file item failed.
  906            * @return True, if one or more additional file items
  907            *   are available, otherwise false.
  908            */
  909           public boolean hasNext() throws FileUploadException, IOException {
  910               if (eof) {
  911                   return false;
  912               }
  913               if (itemValid) {
  914                   return true;
  915               }
  916               return findNextItem();
  917           }
  918   
  919           /**
  920            * Returns the next available {@link FileItemStream}.
  921            * @throws java.util.NoSuchElementException No more items are
  922            *   available. Use {@link #hasNext()} to prevent this exception.
  923            * @throws FileUploadException Parsing or processing the
  924            *   file item failed.
  925            * @throws IOException Reading the file item failed.
  926            * @return FileItemStream instance, which provides
  927            *   access to the next file item.
  928            */
  929           public FileItemStream next() throws FileUploadException, IOException {
  930               if (eof  ||  (!itemValid && !hasNext())) {
  931                   throw new NoSuchElementException();
  932               }
  933               itemValid = false;
  934               return currentItem;
  935           }
  936       }
  937   
  938       /**
  939        * This exception is thrown for hiding an inner
  940        * {@link FileUploadException} in an {@link IOException}.
  941        */
  942       public static class FileUploadIOException extends IOException {
  943           /** The exceptions UID, for serializing an instance.
  944            */
  945           private static final long serialVersionUID = -7047616958165584154L;
  946           /** The exceptions cause; we overwrite the parent
  947            * classes field, which is available since Java
  948            * 1.4 only.
  949            */
  950           private final FileUploadException cause;
  951   
  952           /**
  953            * Creates a <code>FileUploadIOException</code> with the
  954            * given cause.
  955            * @param pCause The exceptions cause, if any, or null.
  956            */
  957           public FileUploadIOException(FileUploadException pCause) {
  958               // We're not doing super(pCause) cause of 1.3 compatibility.
  959               cause = pCause;
  960           }
  961   
  962           /**
  963            * Returns the exceptions cause.
  964            * @return The exceptions cause, if any, or null.
  965            */
  966           public Throwable getCause() {
  967               return cause;
  968           }
  969       }
  970   
  971       /**
  972        * Thrown to indicate that the request is not a multipart request.
  973        */
  974       public static class InvalidContentTypeException
  975               extends FileUploadException {
  976           /** The exceptions UID, for serializing an instance.
  977            */
  978           private static final long serialVersionUID = -9073026332015646668L;
  979   
  980           /**
  981            * Constructs a <code>InvalidContentTypeException</code> with no
  982            * detail message.
  983            */
  984           public InvalidContentTypeException() {
  985               // Nothing to do.
  986           }
  987   
  988           /**
  989            * Constructs an <code>InvalidContentTypeException</code> with
  990            * the specified detail message.
  991            *
  992            * @param message The detail message.
  993            */
  994           public InvalidContentTypeException(String message) {
  995               super(message);
  996           }
  997       }
  998   
  999       /**
 1000        * Thrown to indicate an IOException.
 1001        */
 1002       public static class IOFileUploadException extends FileUploadException {
 1003           /** The exceptions UID, for serializing an instance.
 1004            */
 1005           private static final long serialVersionUID = 1749796615868477269L;
 1006           /** The exceptions cause; we overwrite the parent
 1007            * classes field, which is available since Java
 1008            * 1.4 only.
 1009            */
 1010           private final IOException cause;
 1011   
 1012           /**
 1013            * Creates a new instance with the given cause.
 1014            * @param pMsg The detail message.
 1015            * @param pException The exceptions cause.
 1016            */
 1017           public IOFileUploadException(String pMsg, IOException pException) {
 1018               super(pMsg);
 1019               cause = pException;
 1020           }
 1021   
 1022           /**
 1023            * Returns the exceptions cause.
 1024            * @return The exceptions cause, if any, or null.
 1025            */
 1026           public Throwable getCause() {
 1027               return cause;
 1028           }
 1029       }
 1030   
 1031       /** This exception is thrown, if a requests permitted size
 1032        * is exceeded.
 1033        */
 1034       protected abstract static class SizeException extends FileUploadException {
 1035           /**
 1036            * The actual size of the request.
 1037            */
 1038           private final long actual;
 1039   
 1040           /**
 1041            * The maximum permitted size of the request.
 1042            */
 1043           private final long permitted;
 1044   
 1045           /**
 1046            * Creates a new instance.
 1047            * @param message The detail message.
 1048            * @param actual The actual number of bytes in the request.
 1049            * @param permitted The requests size limit, in bytes.
 1050            */
 1051           protected SizeException(String message, long actual, long permitted) {
 1052               super(message);
 1053               this.actual = actual;
 1054               this.permitted = permitted;
 1055           }
 1056   
 1057           /**
 1058            * Retrieves the actual size of the request.
 1059            *
 1060            * @return The actual size of the request.
 1061            */
 1062           public long getActualSize() {
 1063               return actual;
 1064           }
 1065   
 1066           /**
 1067            * Retrieves the permitted size of the request.
 1068            *
 1069            * @return The permitted size of the request.
 1070            */
 1071           public long getPermittedSize() {
 1072               return permitted;
 1073           }
 1074       }
 1075   
 1076       /**
 1077        * Thrown to indicate that the request size is not specified. In other
 1078        * words, it is thrown, if the content-length header is missing or
 1079        * contains the value -1.
 1080        * @deprecated As of commons-fileupload 1.2, the presence of a
 1081        *   content-length header is no longer required.
 1082        */
 1083       public static class UnknownSizeException
 1084           extends FileUploadException {
 1085           /** The exceptions UID, for serializing an instance.
 1086            */
 1087           private static final long serialVersionUID = 7062279004812015273L;
 1088   
 1089           /**
 1090            * Constructs a <code>UnknownSizeException</code> with no
 1091            * detail message.
 1092            */
 1093           public UnknownSizeException() {
 1094               super();
 1095           }
 1096   
 1097           /**
 1098            * Constructs an <code>UnknownSizeException</code> with
 1099            * the specified detail message.
 1100            *
 1101            * @param message The detail message.
 1102            */
 1103           public UnknownSizeException(String message) {
 1104               super(message);
 1105           }
 1106       }
 1107   
 1108       /**
 1109        * Thrown to indicate that the request size exceeds the configured maximum.
 1110        */
 1111       public static class SizeLimitExceededException
 1112               extends SizeException {
 1113           /** The exceptions UID, for serializing an instance.
 1114            */
 1115           private static final long serialVersionUID = -2474893167098052828L;
 1116   
 1117           /**
 1118            * @deprecated Replaced by
 1119            * {@link #SizeLimitExceededException(String, long, long)}
 1120            */
 1121           public SizeLimitExceededException() {
 1122               this(null, 0, 0);
 1123           }
 1124   
 1125           /**
 1126            * @deprecated Replaced by
 1127            * {@link #SizeLimitExceededException(String, long, long)}
 1128            * @param message The exceptions detail message.
 1129            */
 1130           public SizeLimitExceededException(String message) {
 1131               this(message, 0, 0);
 1132           }
 1133   
 1134           /**
 1135            * Constructs a <code>SizeExceededException</code> with
 1136            * the specified detail message, and actual and permitted sizes.
 1137            *
 1138            * @param message   The detail message.
 1139            * @param actual    The actual request size.
 1140            * @param permitted The maximum permitted request size.
 1141            */
 1142           public SizeLimitExceededException(String message, long actual,
 1143                   long permitted) {
 1144               super(message, actual, permitted);
 1145           }
 1146       }
 1147   
 1148       /**
 1149        * Thrown to indicate that A files size exceeds the configured maximum.
 1150        */
 1151       public static class FileSizeLimitExceededException
 1152               extends SizeException {
 1153           /** The exceptions UID, for serializing an instance.
 1154            */
 1155           private static final long serialVersionUID = 8150776562029630058L;
 1156   
 1157           /**
 1158            * Constructs a <code>SizeExceededException</code> with
 1159            * the specified detail message, and actual and permitted sizes.
 1160            *
 1161            * @param message   The detail message.
 1162            * @param actual    The actual request size.
 1163            * @param permitted The maximum permitted request size.
 1164            */
 1165           public FileSizeLimitExceededException(String message, long actual,
 1166                   long permitted) {
 1167               super(message, actual, permitted);
 1168           }
 1169       }
 1170   
 1171       /**
 1172        * Returns the progress listener.
 1173        * @return The progress listener, if any, or null.
 1174        */
 1175       public ProgressListener getProgressListener() {
 1176           return listener;
 1177       }
 1178   
 1179       /**
 1180        * Sets the progress listener.
 1181        * @param pListener The progress listener, if any. Defaults to null.
 1182        */
 1183       public void setProgressListener(ProgressListener pListener) {
 1184           listener = pListener;
 1185       }
 1186   }

Save This Page
Home » commons-fileupload-1.2-src » org.apache.commons » fileupload » [javadoc | source]