View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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   * Multipart Form Data Filter.
61   * <p>
62   * This class is ONLY needed if you cannot use the Servlet 3.0 APIs for 
63   * configuring and handling multipart requests. See javax.servlet.http.HttpServletRequest.getParts(). If
64   * you use the new servlet apis then you should REMOVE this filter from your webapp.
65   * <p>
66   * This class decodes the <code>multipart/form-data</code> 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   * Init Parameters:
72   * <dl>
73   * <dt>delete</dt>
74   * <dd>(boolean)
75   * If set to "true", any files created will be deleted when the current request returns.</dd>
76   * <dt>maxFormKeys</dt>
77   * <dd>(number)
78   * Sets the maximum number of keys that may be present in a
79   * form (default set by system property <code>org.eclipse.jetty.server.Request.maxFormKeys</code> or 1000) to protect
80   * against DOS attacks by bad hash keys.
81   * </dd>
82   * <dt>deleteFiles</dt>
83   * <dd>(boolean)
84   * Controls if uploaded files are automatically deleted after the request completes.
85   * </dd>
86   * <dt>maxFileSize</dt>
87   * <dd>(size in bytes)
88   * Set the max size file that can be uploaded.
89   * </dd>
90   * <dt>maxRequestSize</dt>
91   * <dd>(size in bytes)
92   * To limit the size of the multipart request.
93   * </dd>
94   * </dl>
95   * @deprecated See servlet 3.0 apis like javax.servlet.http.HttpServletRequest.getParts()
96   */
97  @Deprecated
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         public Wrapper(HttpServletRequest request, MultiMap map)
247         {
248             super(request);
249             this._params=map;
250         }
251 
252         /* ------------------------------------------------------------------------------- */
253         /**
254          * @see javax.servlet.ServletRequest#getContentLength()
255          */
256         @Override
257         public int getContentLength()
258         {
259             return 0;
260         }
261 
262         /* ------------------------------------------------------------------------------- */
263         /**
264          * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
265          */
266         @Override
267         public String getParameter(String name)
268         {
269             Object o=_params.get(name);
270             if (!(o instanceof byte[]) && LazyList.size(o)>0)
271                 o=LazyList.get(o,0);
272 
273             if (o instanceof byte[])
274             {
275                 try
276                 {
277                     return getParameterBytesAsString(name, (byte[])o);
278                 }
279                 catch(Exception e)
280                 {
281                     LOG.warn(e);
282                 }
283             }
284             else if (o!=null)
285                 return String.valueOf(o);
286             return null;
287         }
288 
289         /* ------------------------------------------------------------------------------- */
290         /**
291          * @see javax.servlet.ServletRequest#getParameterMap()
292          */
293         @Override
294         public Map<String, String[]> getParameterMap()
295         {
296             Map<String, String[]> cmap = new HashMap<String,String[]>();
297             
298             for ( Object key : _params.keySet() )
299             {
300                 cmap.put((String)key,getParameterValues((String)key));
301             }
302 
303             return Collections.unmodifiableMap(cmap);
304         }
305 
306         /* ------------------------------------------------------------------------------- */
307         /**
308          * @see javax.servlet.ServletRequest#getParameterNames()
309          */
310         @Override
311         public Enumeration<String> getParameterNames()
312         {
313             return Collections.enumeration(_params.keySet());
314         }
315 
316         /* ------------------------------------------------------------------------------- */
317         /**
318          * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
319          */
320         @Override
321         public String[] getParameterValues(String name)
322         {
323             List l=_params.getValues(name);
324             if (l==null || l.size()==0)
325                 return new String[0];
326             String[] v = new String[l.size()];
327             for (int i=0;i<l.size();i++)
328             {
329                 Object o=l.get(i);
330                 if (o instanceof byte[])
331                 {
332                     try
333                     {
334                         v[i]=getParameterBytesAsString(name, (byte[])o);
335                     }
336                     catch(Exception e)
337                     {
338                         throw new RuntimeException(e);
339                     }
340                 }
341                 else if (o instanceof String)
342                     v[i]=(String)o;
343             }
344             return v;
345         }
346 
347         /* ------------------------------------------------------------------------------- */
348         /**
349          * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
350          */
351         @Override
352         public void setCharacterEncoding(String enc)
353             throws UnsupportedEncodingException
354         {
355             try
356             {
357                 _encoding=Charset.forName(enc);
358             }
359             catch (UnsupportedCharsetException e)
360             {
361                 throw new UnsupportedEncodingException(e.getMessage());
362             }
363         }
364         
365         
366         /* ------------------------------------------------------------------------------- */
367         private String getParameterBytesAsString (String name, byte[] bytes) 
368         throws UnsupportedEncodingException
369         {
370             //check if there is a specific encoding for the parameter
371             Object ct = _params.getValue(name+CONTENT_TYPE_SUFFIX,0);
372             //use default if not
373             Charset contentType = _encoding;
374             if (ct != null)
375             {
376                 String tmp = MimeTypes.getCharsetFromContentType((String)ct);
377                 try
378                 {
379                     contentType = (tmp == null?_encoding:Charset.forName(tmp));
380                 }
381                 catch (UnsupportedCharsetException e)
382                 {
383                     throw new UnsupportedEncodingException(e.getMessage());
384                 }
385             }
386             
387             return new String(bytes,contentType);
388         }
389     }
390 }