View Javadoc

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