View Javadoc

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