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