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