View Javadoc

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