Home » apache-tomcat-6.0.26-src » org.apache » tomcat » util » http » 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   
   18   
   19   package org.apache.tomcat.util.http.fileupload;
   20   
   21   
   22   import java.io.IOException;
   23   import java.io.InputStream;
   24   import java.io.OutputStream;
   25   import java.util.ArrayList;
   26   import java.util.HashMap;
   27   import java.util.List;
   28   import java.util.Map;
   29   import javax.servlet.http.HttpServletRequest;
   30   
   31   
   32   /**
   33    * <p>High level API for processing file uploads.</p>
   34    *
   35    * <p>This class handles multiple files per single HTML widget, sent using
   36    * <code>multipart/mixed</code> encoding type, as specified by
   37    * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
   38    * #parseRequest(HttpServletRequest)} to acquire a list of {@link
   39    * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
   40    * widget.</p>
   41    *
   42    * <p>How the data for individual parts is stored is determined by the factory
   43    * used to create them; a given part may be in memory, on disk, or somewhere
   44    * else.</p>
   45    *
   46    * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
   47    * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
   48    * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
   49    * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
   50    * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
   51    * @author Sean C. Sullivan
   52    *
   53    * @version $Id: FileUploadBase.java 467222 2006-10-24 03:17:11Z markt $
   54    */
   55   public abstract class FileUploadBase
   56   {
   57   
   58       // ---------------------------------------------------------- Class methods
   59   
   60   
   61       /**
   62        * Utility method that determines whether the request contains multipart
   63        * content.
   64        *
   65        * @param req The servlet request to be evaluated. Must be non-null.
   66        *
   67        * @return <code>true</code> if the request is multipart;
   68        *         <code>false</code> otherwise.
   69        */
   70       public static final boolean isMultipartContent(HttpServletRequest req)
   71       {
   72           String contentType = req.getHeader(CONTENT_TYPE);
   73           if (contentType == null)
   74           {
   75               return false;
   76           }
   77           if (contentType.startsWith(MULTIPART))
   78           {
   79               return true;
   80           }
   81           return false;
   82       }
   83   
   84   
   85       // ----------------------------------------------------- Manifest constants
   86   
   87   
   88       /**
   89        * HTTP content type header name.
   90        */
   91       public static final String CONTENT_TYPE = "Content-type";
   92   
   93   
   94       /**
   95        * HTTP content disposition header name.
   96        */
   97       public static final String CONTENT_DISPOSITION = "Content-disposition";
   98   
   99   
  100       /**
  101        * Content-disposition value for form data.
  102        */
  103       public static final String FORM_DATA = "form-data";
  104   
  105   
  106       /**
  107        * Content-disposition value for file attachment.
  108        */
  109       public static final String ATTACHMENT = "attachment";
  110   
  111   
  112       /**
  113        * Part of HTTP content type header.
  114        */
  115       public static final String MULTIPART = "multipart/";
  116   
  117   
  118       /**
  119        * HTTP content type header for multipart forms.
  120        */
  121       public static final String MULTIPART_FORM_DATA = "multipart/form-data";
  122   
  123   
  124       /**
  125        * HTTP content type header for multiple uploads.
  126        */
  127       public static final String MULTIPART_MIXED = "multipart/mixed";
  128   
  129   
  130       /**
  131        * The maximum length of a single header line that will be parsed
  132        * (1024 bytes).
  133        */
  134       public static final int MAX_HEADER_SIZE = 1024;
  135   
  136   
  137       // ----------------------------------------------------------- Data members
  138   
  139   
  140       /**
  141        * The maximum size permitted for an uploaded file. A value of -1 indicates
  142        * no maximum.
  143        */
  144       private long sizeMax = -1;
  145   
  146   
  147       /**
  148        * The content encoding to use when reading part headers.
  149        */
  150       private String headerEncoding;
  151   
  152   
  153       // ----------------------------------------------------- Property accessors
  154   
  155   
  156       /**
  157        * Returns the factory class used when creating file items.
  158        *
  159        * @return The factory class for new file items.
  160        */
  161       public abstract FileItemFactory getFileItemFactory();
  162   
  163   
  164       /**
  165        * Sets the factory class to use when creating file items.
  166        *
  167        * @param factory The factory class for new file items.
  168        */
  169       public abstract void setFileItemFactory(FileItemFactory factory);
  170   
  171   
  172       /**
  173        * Returns the maximum allowed upload size.
  174        *
  175        * @return The maximum allowed size, in bytes.
  176        *
  177        * @see #setSizeMax(long)
  178        *
  179        */
  180       public long getSizeMax()
  181       {
  182           return sizeMax;
  183       }
  184   
  185   
  186       /**
  187        * Sets the maximum allowed upload size. If negative, there is no maximum.
  188        *
  189        * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
  190        *
  191        * @see #getSizeMax()
  192        *
  193        */
  194       public void setSizeMax(long sizeMax)
  195       {
  196           this.sizeMax = sizeMax;
  197       }
  198   
  199   
  200       /**
  201        * Retrieves the character encoding used when reading the headers of an
  202        * individual part. When not specified, or <code>null</code>, the platform
  203        * default encoding is used.
  204        *
  205        * @return The encoding used to read part headers.
  206        */
  207       public String getHeaderEncoding()
  208       {
  209           return headerEncoding;
  210       }
  211   
  212   
  213       /**
  214        * Specifies the character encoding to be used when reading the headers of
  215        * individual parts. When not specified, or <code>null</code>, the platform
  216        * default encoding is used.
  217        *
  218        * @param encoding The encoding used to read part headers.
  219        */
  220       public void setHeaderEncoding(String encoding)
  221       {
  222           headerEncoding = encoding;
  223       }
  224   
  225   
  226       // --------------------------------------------------------- Public methods
  227   
  228   
  229       /**
  230        * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
  231        * compliant <code>multipart/form-data</code> stream. If files are stored
  232        * on disk, the path is given by <code>getRepository()</code>.
  233        *
  234        * @param req The servlet request to be parsed.
  235        *
  236        * @return A list of <code>FileItem</code> instances parsed from the
  237        *         request, in the order that they were transmitted.
  238        *
  239        * @exception FileUploadException if there are problems reading/parsing
  240        *                                the request or storing files.
  241        */
  242       public List /* FileItem */ parseRequest(HttpServletRequest req)
  243           throws FileUploadException
  244       {
  245           if (null == req)
  246           {
  247               throw new NullPointerException("req parameter");
  248           }
  249   
  250           ArrayList items = new ArrayList();
  251           String contentType = req.getHeader(CONTENT_TYPE);
  252   
  253           if ((null == contentType) || (!contentType.startsWith(MULTIPART)))
  254           {
  255               throw new InvalidContentTypeException(
  256                   "the request doesn't contain a "
  257                   + MULTIPART_FORM_DATA
  258                   + " or "
  259                   + MULTIPART_MIXED
  260                   + " stream, content type header is "
  261                   + contentType);
  262           }
  263           int requestSize = req.getContentLength();
  264   
  265           if (requestSize == -1)
  266           {
  267               throw new UnknownSizeException(
  268                   "the request was rejected because it's size is unknown");
  269           }
  270   
  271           if (sizeMax >= 0 && requestSize > sizeMax)
  272           {
  273               throw new SizeLimitExceededException(
  274                   "the request was rejected because "
  275                   + "it's size exceeds allowed range");
  276           }
  277   
  278           try
  279           {
  280               int boundaryIndex = contentType.indexOf("boundary=");
  281               if (boundaryIndex < 0)
  282               {
  283                   throw new FileUploadException(
  284                           "the request was rejected because "
  285                           + "no multipart boundary was found");
  286               }
  287               byte[] boundary = contentType.substring(
  288                       boundaryIndex + 9).getBytes();
  289   
  290               InputStream input = req.getInputStream();
  291   
  292               MultipartStream multi = new MultipartStream(input, boundary);
  293               multi.setHeaderEncoding(headerEncoding);
  294   
  295               boolean nextPart = multi.skipPreamble();
  296               while (nextPart)
  297               {
  298                   Map headers = parseHeaders(multi.readHeaders());
  299                   String fieldName = getFieldName(headers);
  300                   if (fieldName != null)
  301                   {
  302                       String subContentType = getHeader(headers, CONTENT_TYPE);
  303                       if (subContentType != null && subContentType
  304                                                   .startsWith(MULTIPART_MIXED))
  305                       {
  306                           // Multiple files.
  307                           byte[] subBoundary =
  308                               subContentType.substring(
  309                                   subContentType
  310                                   .indexOf("boundary=") + 9).getBytes();
  311                           multi.setBoundary(subBoundary);
  312                           boolean nextSubPart = multi.skipPreamble();
  313                           while (nextSubPart)
  314                           {
  315                               headers = parseHeaders(multi.readHeaders());
  316                               if (getFileName(headers) != null)
  317                               {
  318                                   FileItem item =
  319                                           createItem(headers, false);
  320                                   OutputStream os = item.getOutputStream();
  321                                   try
  322                                   {
  323                                       multi.readBodyData(os);
  324                                   }
  325                                   finally
  326                                   {
  327                                       os.close();
  328                                   }
  329                                   items.add(item);
  330                               }
  331                               else
  332                               {
  333                                   // Ignore anything but files inside
  334                                   // multipart/mixed.
  335                                   multi.discardBodyData();
  336                               }
  337                               nextSubPart = multi.readBoundary();
  338                           }
  339                           multi.setBoundary(boundary);
  340                       }
  341                       else
  342                       {
  343                           if (getFileName(headers) != null)
  344                           {
  345                               // A single file.
  346                               FileItem item = createItem(headers, false);
  347                               OutputStream os = item.getOutputStream();
  348                               try
  349                               {
  350                                   multi.readBodyData(os);
  351                               }
  352                               finally
  353                               {
  354                                   os.close();
  355                               }
  356                               items.add(item);
  357                           }
  358                           else
  359                           {
  360                               // A form field.
  361                               FileItem item = createItem(headers, true);
  362                               OutputStream os = item.getOutputStream();
  363                               try
  364                               {
  365                                   multi.readBodyData(os);
  366                               }
  367                               finally
  368                               {
  369                                   os.close();
  370                               }
  371                               items.add(item);
  372                           }
  373                       }
  374                   }
  375                   else
  376                   {
  377                       // Skip this part.
  378                       multi.discardBodyData();
  379                   }
  380                   nextPart = multi.readBoundary();
  381               }
  382           }
  383           catch (IOException e)
  384           {
  385               throw new FileUploadException(
  386                   "Processing of " + MULTIPART_FORM_DATA
  387                       + " request failed. " + e.getMessage());
  388           }
  389   
  390           return items;
  391       }
  392   
  393   
  394       // ------------------------------------------------------ Protected methods
  395   
  396   
  397       /**
  398        * Retrieves the file name from the <code>Content-disposition</code>
  399        * header.
  400        *
  401        * @param headers A <code>Map</code> containing the HTTP request headers.
  402        *
  403        * @return The file name for the current <code>encapsulation</code>.
  404        */
  405       protected String getFileName(Map /* String, String */ headers)
  406       {
  407           String fileName = null;
  408           String cd = getHeader(headers, CONTENT_DISPOSITION);
  409           if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))
  410           {
  411               int start = cd.indexOf("filename=\"");
  412               int end = cd.indexOf('"', start + 10);
  413               if (start != -1 && end != -1)
  414               {
  415                   fileName = cd.substring(start + 10, end).trim();
  416               }
  417           }
  418           return fileName;
  419       }
  420   
  421   
  422       /**
  423        * Retrieves the field name from the <code>Content-disposition</code>
  424        * header.
  425        *
  426        * @param headers A <code>Map</code> containing the HTTP request headers.
  427        *
  428        * @return The field name for the current <code>encapsulation</code>.
  429        */
  430       protected String getFieldName(Map /* String, String */ headers)
  431       {
  432           String fieldName = null;
  433           String cd = getHeader(headers, CONTENT_DISPOSITION);
  434           if (cd != null && cd.startsWith(FORM_DATA))
  435           {
  436               int start = cd.indexOf("name=\"");
  437               int end = cd.indexOf('"', start + 6);
  438               if (start != -1 && end != -1)
  439               {
  440                   fieldName = cd.substring(start + 6, end);
  441               }
  442           }
  443           return fieldName;
  444       }
  445   
  446   
  447       /**
  448        * Creates a new {@link FileItem} instance.
  449        *
  450        * @param headers       A <code>Map</code> containing the HTTP request
  451        *                      headers.
  452        * @param isFormField   Whether or not this item is a form field, as
  453        *                      opposed to a file.
  454        *
  455        * @return A newly created <code>FileItem</code> instance.
  456        *
  457        * @exception FileUploadException if an error occurs.
  458        */
  459       protected FileItem createItem(Map /* String, String */ headers,
  460                                     boolean isFormField)
  461           throws FileUploadException
  462       {
  463           return getFileItemFactory().createItem(getFieldName(headers),
  464                   getHeader(headers, CONTENT_TYPE),
  465                   isFormField,
  466                   getFileName(headers));
  467       }
  468   
  469   
  470       /**
  471        * <p> Parses the <code>header-part</code> and returns as key/value
  472        * pairs.
  473        *
  474        * <p> If there are multiple headers of the same names, the name
  475        * will map to a comma-separated list containing the values.
  476        *
  477        * @param headerPart The <code>header-part</code> of the current
  478        *                   <code>encapsulation</code>.
  479        *
  480        * @return A <code>Map</code> containing the parsed HTTP request headers.
  481        */
  482       protected Map /* String, String */ parseHeaders(String headerPart)
  483       {
  484           Map headers = new HashMap();
  485           char buffer[] = new char[MAX_HEADER_SIZE];
  486           boolean done = false;
  487           int j = 0;
  488           int i;
  489           String header, headerName, headerValue;
  490           try
  491           {
  492               while (!done)
  493               {
  494                   i = 0;
  495                   // Copy a single line of characters into the buffer,
  496                   // omitting trailing CRLF.
  497                   while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n')
  498                   {
  499                       buffer[i++] = headerPart.charAt(j++);
  500                   }
  501                   header = new String(buffer, 0, i - 2);
  502                   if (header.equals(""))
  503                   {
  504                       done = true;
  505                   }
  506                   else
  507                   {
  508                       if (header.indexOf(':') == -1)
  509                       {
  510                           // This header line is malformed, skip it.
  511                           continue;
  512                       }
  513                       headerName = header.substring(0, header.indexOf(':'))
  514                           .trim().toLowerCase();
  515                       headerValue =
  516                           header.substring(header.indexOf(':') + 1).trim();
  517                       if (getHeader(headers, headerName) != null)
  518                       {
  519                           // More that one heder of that name exists,
  520                           // append to the list.
  521                           headers.put(headerName,
  522                                       getHeader(headers, headerName) + ','
  523                                           + headerValue);
  524                       }
  525                       else
  526                       {
  527                           headers.put(headerName, headerValue);
  528                       }
  529                   }
  530               }
  531           }
  532           catch (IndexOutOfBoundsException e)
  533           {
  534               // Headers were malformed. continue with all that was
  535               // parsed.
  536           }
  537           return headers;
  538       }
  539   
  540   
  541       /**
  542        * Returns the header with the specified name from the supplied map. The
  543        * header lookup is case-insensitive.
  544        *
  545        * @param headers A <code>Map</code> containing the HTTP request headers.
  546        * @param name    The name of the header to return.
  547        *
  548        * @return The value of specified header, or a comma-separated list if
  549        *         there were multiple headers of that name.
  550        */
  551       protected final String getHeader(Map /* String, String */ headers,
  552                                        String name)
  553       {
  554           return (String) headers.get(name.toLowerCase());
  555       }
  556   
  557   
  558       /**
  559        * Thrown to indicate that the request is not a multipart request.
  560        */
  561       public static class InvalidContentTypeException
  562           extends FileUploadException
  563       {
  564           /**
  565            * Constructs a <code>InvalidContentTypeException</code> with no
  566            * detail message.
  567            */
  568           public InvalidContentTypeException()
  569           {
  570               super();
  571           }
  572   
  573           /**
  574            * Constructs an <code>InvalidContentTypeException</code> with
  575            * the specified detail message.
  576            *
  577            * @param message The detail message.
  578            */
  579           public InvalidContentTypeException(String message)
  580           {
  581               super(message);
  582           }
  583       }
  584   
  585   
  586       /**
  587        * Thrown to indicate that the request size is not specified.
  588        */
  589       public static class UnknownSizeException
  590           extends FileUploadException
  591       {
  592           /**
  593            * Constructs a <code>UnknownSizeException</code> with no
  594            * detail message.
  595            */
  596           public UnknownSizeException()
  597           {
  598               super();
  599           }
  600   
  601           /**
  602            * Constructs an <code>UnknownSizeException</code> with
  603            * the specified detail message.
  604            *
  605            * @param message The detail message.
  606            */
  607           public UnknownSizeException(String message)
  608           {
  609               super(message);
  610           }
  611       }
  612   
  613   
  614       /**
  615        * Thrown to indicate that the request size exceeds the configured maximum.
  616        */
  617       public static class SizeLimitExceededException
  618           extends FileUploadException
  619       {
  620           /**
  621            * Constructs a <code>SizeExceededException</code> with no
  622            * detail message.
  623            */
  624           public SizeLimitExceededException()
  625           {
  626               super();
  627           }
  628   
  629           /**
  630            * Constructs an <code>SizeExceededException</code> with
  631            * the specified detail message.
  632            *
  633            * @param message The detail message.
  634            */
  635           public SizeLimitExceededException(String message)
  636           {
  637               super(message);
  638           }
  639       }
  640   
  641   }

Home » apache-tomcat-6.0.26-src » org.apache » tomcat » util » http » fileupload » [javadoc | source]