View Javadoc

    1   // ========================================================================
    2   // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14  package org.mortbay.servlet;
    15
    16  import java.io.BufferedInputStream;
    17  import java.io.BufferedOutputStream;
    18  import java.io.ByteArrayOutputStream;
    19  import java.io.File;
    20  import java.io.FileNotFoundException;
    21  import java.io.FileOutputStream;
    22  import java.io.IOException;
    23  import java.io.OutputStream;
    24  import java.io.UnsupportedEncodingException;
    25  import java.util.ArrayList;
    26  import java.util.Collections;
    27  import java.util.Enumeration;
    28  import java.util.Iterator;
    29  import java.util.List;
    30  import java.util.Map;
    31  import java.util.StringTokenizer;
    32
    33  import javax.servlet.Filter;
    34  import javax.servlet.FilterChain;
    35  import javax.servlet.FilterConfig;
    36  import javax.servlet.ServletContext;
    37  import javax.servlet.ServletException;
    38  import javax.servlet.ServletRequest;
    39  import javax.servlet.ServletResponse;
    40  import javax.servlet.http.HttpServletRequest;
    41  import javax.servlet.http.HttpServletRequestWrapper;
    42
    43  import org.mortbay.util.MultiMap;
    44  import org.mortbay.util.StringUtil;
    45  import org.mortbay.util.TypeUtil;
    46
    47  /* ------------------------------------------------------------ */
    48  /**
    49   * Multipart Form Data Filter.
    50   * <p>
    51   * This class decodes the multipart/form-data stream sent by a HTML form that uses a file input
    52   * item.  Any files sent are stored to a tempary file and a File object added to the request
    53   * as an attribute.  All other values are made available via the normal getParameter API and
    54   * the setCharacterEncoding mechanism is respected when converting bytes to Strings.
    55   *
    56   * If the init paramter "delete" is set to "true", any files created will be deleted when the
    57   * current request returns.
    58   *
    59   * @author Greg Wilkins
    60   * @author Jim Crossley
    61   */
    62  public class MultiPartFilter implements Filter
    63  {
    64      private final static String FILES ="org.mortbay.servlet.MultiPartFilter.files";
    65      private File tempdir;
    66      private boolean _deleteFiles;
    67      private ServletContext _context;
    68      private int _fileOutputBuffer = 0;
    69
    70      /* ------------------------------------------------------------------------------- */
    71      /**
    72       * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
    73       */
    74      public void init(FilterConfig filterConfig) throws ServletException
    75      {
    76          tempdir=(File)filterConfig.getServletContext().getAttribute("javax.servlet.context.tempdir");
    77          _deleteFiles="true".equals(filterConfig.getInitParameter("deleteFiles"));
    78          String fileOutputBuffer = filterConfig.getInitParameter("fileOutputBuffer");
    79          if(fileOutputBuffer!=null)
    80              _fileOutputBuffer = Integer.parseInt(fileOutputBuffer);
    81          _context=filterConfig.getServletContext();
    82      }
    83
    84      /* ------------------------------------------------------------------------------- */
    85      /**
    86       * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
    87       *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
    88       */
    89      public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
    90          throws IOException, ServletException
    91      {
    92          HttpServletRequest srequest=(HttpServletRequest)request;
    93          if(srequest.getContentType()==null||!srequest.getContentType().startsWith("multipart/form-data"))
    94          {
    95              chain.doFilter(request,response);
    96              return;
    97          }
    98
    99          BufferedInputStream in = new BufferedInputStream(request.getInputStream());
    100         String content_type=srequest.getContentType();
    101
    102         // TODO - handle encodings
    103
    104         String boundary="--"+value(content_type.substring(content_type.indexOf("boundary=")));
    105         byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1);
    106         MultiMap params = new MultiMap();
    107
    108         try
    109         {
    110             // Get first boundary
    111             byte[] bytes=TypeUtil.readLine(in);
    112             String line=bytes==null?null:new String(bytes,"UTF-8");
    113             if(line==null || !line.equals(boundary))
    114             {
    115                 throw new IOException("Missing initial multi part boundary");
    116             }
    117
    118             // Read each part
    119             boolean lastPart=false;
    120             String content_disposition=null;
    121             while(!lastPart)
    122             {
    123                 while(true)
    124                 {
    125                     bytes=TypeUtil.readLine(in);
    126                     // If blank line, end of part headers
    127                     if(bytes==null || bytes.length==0)
    128                         break;
    129                     line=new String(bytes,"UTF-8");
    130
    131                     // place part header key and value in map
    132                     int c=line.indexOf(':',0);
    133                     if(c>0)
    134                     {
    135                         String key=line.substring(0,c).trim().toLowerCase();
    136                         String value=line.substring(c+1,line.length()).trim();
    137                         if(key.equals("content-disposition"))
    138                             content_disposition=value;
    139                     }
    140                 }
    141                 // Extract content-disposition
    142                 boolean form_data=false;
    143                 if(content_disposition==null)
    144                 {
    145                     throw new IOException("Missing content-disposition");
    146                 }
    147
    148                 StringTokenizer tok=new StringTokenizer(content_disposition,";");
    149                 String name=null;
    150                 String filename=null;
    151                 while(tok.hasMoreTokens())
    152                 {
    153                     String t=tok.nextToken().trim();
    154                     String tl=t.toLowerCase();
    155                     if(t.startsWith("form-data"))
    156                         form_data=true;
    157                     else if(tl.startsWith("name="))
    158                         name=value(t);
    159                     else if(tl.startsWith("filename="))
    160                         filename=value(t);
    161                 }
    162
    163                 // Check disposition
    164                 if(!form_data)
    165                 {
    166                     continue;
    167                 }
    168                 if(name==null||name.length()==0)
    169                 {
    170                     continue;
    171                 }
    172
    173                 OutputStream out=null;
    174                 File file=null;
    175                 try
    176                 {
    177                     if (filename!=null && filename.length()>0)
    178                     {
    179                         file = File.createTempFile("MultiPart", "", tempdir);
    180                         out = new FileOutputStream(file);
    181                         if(_fileOutputBuffer>0)
    182                             out = new BufferedOutputStream(out, _fileOutputBuffer);
    183                         request.setAttribute(name,file);
    184                         params.put(name, filename);
    185
    186                         if (_deleteFiles)
    187                         {
    188                             file.deleteOnExit();
    189                             ArrayList files = (ArrayList)request.getAttribute(FILES);
    190                             if (files==null)
    191                             {
    192                                 files=new ArrayList();
    193                                 request.setAttribute(FILES,files);
    194                             }
    195                             files.add(file);
    196                         }
    197
    198                     }
    199                     else
    200                         out=new ByteArrayOutputStream();
    201
    202                     int state=-2;
    203                     int c;
    204                     boolean cr=false;
    205                     boolean lf=false;
    206
    207                     // loop for all lines`
    208                     while(true)
    209                     {
    210                         int b=0;
    211                         while((c=(state!=-2)?state:in.read())!=-1)
    212                         {
    213                             state=-2;
    214                             // look for CR and/or LF
    215                             if(c==13||c==10)
    216                             {
    217                                 if(c==13)
    218                                     state=in.read();
    219                                 break;
    220                             }
    221                             // look for boundary
    222                             if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
    223                                 b++;
    224                             else
    225                             {
    226                                 // this is not a boundary
    227                                 if(cr)
    228                                     out.write(13);
    229                                 if(lf)
    230                                     out.write(10);
    231                                 cr=lf=false;
    232                                 if(b>0)
    233                                     out.write(byteBoundary,0,b);
    234                                 b=-1;
    235                                 out.write(c);
    236                             }
    237                         }
    238                         // check partial boundary
    239                         if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
    240                         {
    241                             if(cr)
    242                                 out.write(13);
    243                             if(lf)
    244                                 out.write(10);
    245                             cr=lf=false;
    246                             out.write(byteBoundary,0,b);
    247                             b=-1;
    248                         }
    249                         // boundary match
    250                         if(b>0||c==-1)
    251                         {
    252                             if(b==byteBoundary.length)
    253                                 lastPart=true;
    254                             if(state==10)
    255                                 state=-2;
    256                             break;
    257                         }
    258                         // handle CR LF
    259                         if(cr)
    260                             out.write(13);
    261                         if(lf)
    262                             out.write(10);
    263                         cr=(c==13);
    264                         lf=(c==10||state==10);
    265                         if(state==10)
    266                             state=-2;
    267                     }
    268                 }
    269                 finally
    270                 {
    271                     out.close();
    272                 }
    273
    274                 if (file==null)
    275                 {
    276                     bytes = ((ByteArrayOutputStream)out).toByteArray();
    277                     params.add(name,bytes);
    278                 }
    279             }
    280
    281             // handle request
    282             chain.doFilter(new Wrapper(srequest,params),response);
    283         }
    284         finally
    285         {
    286             deleteFiles(request);
    287         }
    288     }
    289
    290     private void deleteFiles(ServletRequest request)
    291     {
    292         ArrayList files = (ArrayList)request.getAttribute(FILES);
    293         if (files!=null)
    294         {
    295             Iterator iter = files.iterator();
    296             while (iter.hasNext())
    297             {
    298                 File file=(File)iter.next();
    299                 try
    300                 {
    301                     file.delete();
    302                 }
    303                 catch(Exception e)
    304                 {
    305                     _context.log("failed to delete "+file,e);
    306                 }
    307             }
    308         }
    309     }
    310     /* ------------------------------------------------------------ */
    311     private String value(String nameEqualsValue)
    312     {
    313         String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
    314         int i=value.indexOf(';');
    315         if(i>0)
    316             value=value.substring(0,i);
    317         if(value.startsWith("\""))
    318         {
    319             value=value.substring(1,value.indexOf('"',1));
    320         }
    321         else
    322         {
    323             i=value.indexOf(' ');
    324             if(i>0)
    325                 value=value.substring(0,i);
    326         }
    327         return value;
    328     }
    329
    330     /* ------------------------------------------------------------------------------- */
    331     /**
    332      * @see javax.servlet.Filter#destroy()
    333      */
    334     public void destroy()
    335     {
    336     }
    337
    338     private static class Wrapper extends HttpServletRequestWrapper
    339     {
    340         String encoding="UTF-8";
    341         MultiMap map;
    342
    343         /* ------------------------------------------------------------------------------- */
    344         /** Constructor.
    345          * @param request
    346          */
    347         public Wrapper(HttpServletRequest request, MultiMap map)
    348         {
    349             super(request);
    350             this.map=map;
    351         }
    352
    353         /* ------------------------------------------------------------------------------- */
    354         /**
    355          * @see javax.servlet.ServletRequest#getContentLength()
    356          */
    357         public int getContentLength()
    358         {
    359             return 0;
    360         }
    361
    362         /* ------------------------------------------------------------------------------- */
    363         /**
    364          * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
    365          */
    366         public String getParameter(String name)
    367         {
    368             Object o=map.get(name);
    369             if (o instanceof byte[])
    370             {
    371                 try
    372                 {
    373                     String s=new String((byte[])o,encoding);
    374                     return s;
    375                 }
    376                 catch(Exception e)
    377                 {
    378                     e.printStackTrace();
    379                 }
    380             }
    381             else if (o instanceof String)
    382                 return (String)o;
    383             return null;
    384         }
    385
    386         /* ------------------------------------------------------------------------------- */
    387         /**
    388          * @see javax.servlet.ServletRequest#getParameterMap()
    389          */
    390         public Map getParameterMap()
    391         {
    392             return map;
    393         }
    394
    395         /* ------------------------------------------------------------------------------- */
    396         /**
    397          * @see javax.servlet.ServletRequest#getParameterNames()
    398          */
    399         public Enumeration getParameterNames()
    400         {
    401             return Collections.enumeration(map.keySet());
    402         }
    403
    404         /* ------------------------------------------------------------------------------- */
    405         /**
    406          * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
    407          */
    408         public String[] getParameterValues(String name)
    409         {
    410             List l=map.getValues(name);
    411             if (l==null || l.size()==0)
    412                 return new String[0];
    413             String[] v = new String[l.size()];
    414             for (int i=0;i<l.size();i++)
    415             {
    416                 Object o=l.get(i);
    417                 if (o instanceof byte[])
    418                 {
    419                     try
    420                     {
    421                         v[i]=new String((byte[])o,encoding);
    422                     }
    423                     catch(Exception e)
    424                     {
    425                         e.printStackTrace();
    426                     }
    427                 }
    428                 else if (o instanceof String)
    429                     v[i]=(String)o;
    430             }
    431             return v;
    432         }
    433
    434         /* ------------------------------------------------------------------------------- */
    435         /**
    436          * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
    437          */
    438         public void setCharacterEncoding(String enc)
    439             throws UnsupportedEncodingException
    440         {
    441             encoding=enc;
    442         }
    443     }
    444 }