View Javadoc

1   // ========================================================================
2   // Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  package org.eclipse.jetty.servlets;
14  
15  import java.io.BufferedInputStream;
16  import java.io.BufferedOutputStream;
17  import java.io.ByteArrayOutputStream;
18  import java.io.File;
19  import java.io.FileOutputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.UnsupportedEncodingException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Enumeration;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.servlet.Filter;
34  import javax.servlet.FilterChain;
35  import javax.servlet.FilterConfig;
36  import javax.servlet.MultipartConfigElement;
37  import javax.servlet.ServletContext;
38  import javax.servlet.ServletException;
39  import javax.servlet.ServletRequest;
40  import javax.servlet.ServletResponse;
41  import javax.servlet.http.HttpServletRequest;
42  import javax.servlet.http.HttpServletRequestWrapper;
43  import javax.servlet.http.Part;
44  
45  
46  import org.eclipse.jetty.util.IO;
47  import org.eclipse.jetty.util.LazyList;
48  import org.eclipse.jetty.util.MultiMap;
49  import org.eclipse.jetty.util.MultiPartInputStream;
50  import org.eclipse.jetty.util.QuotedStringTokenizer;
51  import org.eclipse.jetty.util.StringUtil;
52  import org.eclipse.jetty.util.TypeUtil;
53  
54  /* ------------------------------------------------------------ */
55  /**
56   * Multipart Form Data Filter.
57   * <p>
58   * This class decodes the multipart/form-data stream sent by a HTML form that uses a file input
59   * item.  Any files sent are stored to a temporary file and a File object added to the request 
60   * as an attribute.  All other values are made available via the normal getParameter API and
61   * the setCharacterEncoding mechanism is respected when converting bytes to Strings.
62   * <p>
63   * If the init parameter "delete" is set to "true", any files created will be deleted when the
64   * current request returns.
65   * <p>
66   * The init parameter maxFormKeys sets the maximum number of keys that may be present in a 
67   * form (default set by system property org.eclipse.jetty.server.Request.maxFormKeys or 1000) to protect 
68   * against DOS attacks by bad hash keys. 
69   * <p>
70   * The init parameter deleteFiles controls if uploaded files are automatically deleted after the request
71   * completes.
72   * 
73   * Use init parameter "maxFileSize" to set the max size file that can be uploaded.
74   * 
75   * Use init parameter "maxRequestSize" to limit the size of the multipart request.
76   * 
77   */
78  public class MultiPartFilter implements Filter
79  {
80      public final static String CONTENT_TYPE_SUFFIX=".org.eclipse.jetty.servlet.contentType";
81      private final static String FILES ="org.eclipse.jetty.servlet.MultiPartFilter.files";
82      private File tempdir;
83      private boolean _deleteFiles;
84      private ServletContext _context;
85      private int _fileOutputBuffer = 0;
86      private long _maxFileSize = -1L;
87      private long _maxRequestSize = -1L;
88      private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue();
89  
90      /* ------------------------------------------------------------------------------- */
91      /**
92       * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
93       */
94      public void init(FilterConfig filterConfig) throws ServletException
95      {
96          tempdir=(File)filterConfig.getServletContext().getAttribute("javax.servlet.context.tempdir");
97          _deleteFiles="true".equals(filterConfig.getInitParameter("deleteFiles"));
98          String fileOutputBuffer = filterConfig.getInitParameter("fileOutputBuffer");
99          if(fileOutputBuffer!=null)
100             _fileOutputBuffer = Integer.parseInt(fileOutputBuffer);
101         String maxFileSize = filterConfig.getInitParameter("maxFileSize");
102         if (maxFileSize != null)
103             _maxFileSize = Long.parseLong(maxFileSize.trim());
104         String maxRequestSize = filterConfig.getInitParameter("maxRequestSize");
105         if (maxRequestSize != null)
106             _maxRequestSize = Long.parseLong(maxRequestSize.trim());
107         
108         _context=filterConfig.getServletContext();
109         String mfks = filterConfig.getInitParameter("maxFormKeys");
110         if (mfks!=null)
111             _maxFormKeys=Integer.parseInt(mfks);
112     }
113 
114     /* ------------------------------------------------------------------------------- */
115     /**
116      * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
117      *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
118      */
119     public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) 
120         throws IOException, ServletException
121     {
122         HttpServletRequest srequest=(HttpServletRequest)request;
123         if(srequest.getContentType()==null||!srequest.getContentType().startsWith("multipart/form-data"))
124         {
125             chain.doFilter(request,response);
126             return;
127         }
128         
129         InputStream in = new BufferedInputStream(request.getInputStream());
130         String content_type=srequest.getContentType();
131         
132         //Get current parameters so we can merge into them
133         MultiMap<String> params = new MultiMap<String>();
134         for (Iterator<Map.Entry<String,String[]>> i = request.getParameterMap().entrySet().iterator();i.hasNext();)
135         {
136             Map.Entry<String,String[]> entry=i.next();
137             Object value=entry.getValue();
138             if (value instanceof String[])
139                 params.addValues(entry.getKey(),(String[])value);
140             else
141                 params.add(entry.getKey(),value);
142         }
143         
144         MultipartConfigElement config = new MultipartConfigElement(tempdir.getCanonicalPath(), _maxFileSize, _maxRequestSize, _fileOutputBuffer);
145         MultiPartInputStream mpis = new MultiPartInputStream(in, content_type, config, tempdir);
146         
147 
148         try
149         {
150             Collection<Part> parts = mpis.getParts();
151             if (parts != null)
152             {
153                 Iterator<Part> itor = parts.iterator();
154                 while (itor.hasNext() && params.size() < _maxFormKeys)
155                 {
156                     Part p = itor.next();
157                     MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p;
158                     if (mp.getFile() != null)
159                     {
160                         request.setAttribute(mp.getName(),mp.getFile());
161                         if (mp.getContentDispositionFilename() != null)
162                         {
163                             params.add(mp.getName(), mp.getContentDispositionFilename());
164                             if (mp.getContentType() != null)
165                                 params.add(mp.getName()+CONTENT_TYPE_SUFFIX, mp.getContentType());
166                         }
167                         if (_deleteFiles)
168                         {
169                             mp.getFile().deleteOnExit();
170 
171                             ArrayList files = (ArrayList)request.getAttribute(FILES);
172                             if (files==null)
173                             {
174                                 files=new ArrayList();
175                                 request.setAttribute(FILES,files);
176                             }
177                             files.add(mp.getFile());
178                         }
179                     }
180                     else
181                     {
182                         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
183                         IO.copy(p.getInputStream(), bytes);
184                         params.add(p.getName(), bytes.toByteArray());
185                         if (p.getContentType() != null)
186                             params.add(p.getName()+CONTENT_TYPE_SUFFIX, p.getContentType());
187                     }
188                 }
189             }
190 
191             // handle request
192             chain.doFilter(new Wrapper(srequest,params),response);
193         }
194         finally
195         {
196             deleteFiles(request);
197         }
198     }
199 
200     private void deleteFiles(ServletRequest request)
201     {
202         ArrayList files = (ArrayList)request.getAttribute(FILES);
203         if (files!=null)
204         {
205             Iterator iter = files.iterator();
206             while (iter.hasNext())
207             {
208                 File file=(File)iter.next();
209                 try
210                 {
211                     file.delete();
212                 }
213                 catch(Exception e)
214                 {
215                     _context.log("failed to delete "+file,e);
216                 }
217             }
218         }
219     }
220     
221     /* ------------------------------------------------------------ */
222     private String value(String nameEqualsValue)
223     {
224         return nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
225     }
226 
227     /* ------------------------------------------------------------------------------- */
228     /**
229      * @see javax.servlet.Filter#destroy()
230      */
231     public void destroy()
232     {
233     }
234 
235     /* ------------------------------------------------------------------------------- */
236     /* ------------------------------------------------------------------------------- */
237     private static class Wrapper extends HttpServletRequestWrapper
238     {
239         String _encoding=StringUtil.__UTF8;
240         MultiMap _params;
241         
242         /* ------------------------------------------------------------------------------- */
243         /** Constructor.
244          * @param request
245          */
246         public Wrapper(HttpServletRequest request, MultiMap map)
247         {
248             super(request);
249             this._params=map;
250         }
251         
252         /* ------------------------------------------------------------------------------- */
253         /**
254          * @see javax.servlet.ServletRequest#getContentLength()
255          */
256         @Override
257         public int getContentLength()
258         {
259             return 0;
260         }
261         
262         /* ------------------------------------------------------------------------------- */
263         /**
264          * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
265          */
266         @Override
267         public String getParameter(String name)
268         {
269             Object o=_params.get(name);
270             if (!(o instanceof byte[]) && LazyList.size(o)>0)
271                 o=LazyList.get(o,0);
272             
273             if (o instanceof byte[])
274             {
275                 try
276                 {
277                     String s=new String((byte[])o,_encoding);
278                     return s;
279                 }
280                 catch(Exception e)
281                 {
282                     e.printStackTrace();
283                 }
284             }
285             else if (o!=null)
286                 return String.valueOf(o);
287             return null;
288         }
289         
290         /* ------------------------------------------------------------------------------- */
291         /**
292          * @see javax.servlet.ServletRequest#getParameterMap()
293          */
294         @Override
295         public Map getParameterMap()
296         {
297             Map<String, String> cmap = new HashMap<String,String>();
298             
299             for ( Object key : _params.keySet() )
300             {
301                 cmap.put((String)key,getParameter((String)key));
302             }
303             
304             return Collections.unmodifiableMap(cmap);
305         }
306         
307         /* ------------------------------------------------------------------------------- */
308         /**
309          * @see javax.servlet.ServletRequest#getParameterNames()
310          */
311         @Override
312         public Enumeration getParameterNames()
313         {
314             return Collections.enumeration(_params.keySet());
315         }
316         
317         /* ------------------------------------------------------------------------------- */
318         /**
319          * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
320          */
321         @Override
322         public String[] getParameterValues(String name)
323         {
324             List l=_params.getValues(name);
325             if (l==null || l.size()==0)
326                 return new String[0];
327             String[] v = new String[l.size()];
328             for (int i=0;i<l.size();i++)
329             {
330                 Object o=l.get(i);
331                 if (o instanceof byte[])
332                 {
333                     try
334                     {
335                         v[i]=new String((byte[])o,_encoding);
336                     }
337                     catch(Exception e)
338                     {
339                         throw new RuntimeException(e);
340                     }
341                 }
342                 else if (o instanceof String)
343                     v[i]=(String)o;
344             }
345             return v;
346         }
347         
348         /* ------------------------------------------------------------------------------- */
349         /**
350          * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
351          */
352         @Override
353         public void setCharacterEncoding(String enc) 
354             throws UnsupportedEncodingException
355         {
356             _encoding=enc;
357         }
358     }
359 }