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 }