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 boolean _writeFilesWithFilenames = false;
108     private long _maxFileSize = -1L;
109     private long _maxRequestSize = -1L;
110     private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys", 1000);
111 
112     /* ------------------------------------------------------------------------------- */
113     /**
114      * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
115      */
116     public void init(FilterConfig filterConfig) throws ServletException
117     {
118         tempdir=(File)filterConfig.getServletContext().getAttribute("javax.servlet.context.tempdir");
119         _deleteFiles="true".equals(filterConfig.getInitParameter("deleteFiles"));
120         String fileOutputBuffer = filterConfig.getInitParameter("fileOutputBuffer");
121         if(fileOutputBuffer!=null)
122             _fileOutputBuffer = Integer.parseInt(fileOutputBuffer);
123         String maxFileSize = filterConfig.getInitParameter("maxFileSize");
124         if (maxFileSize != null)
125             _maxFileSize = Long.parseLong(maxFileSize.trim());
126         String maxRequestSize = filterConfig.getInitParameter("maxRequestSize");
127         if (maxRequestSize != null)
128             _maxRequestSize = Long.parseLong(maxRequestSize.trim());
129 
130         _context=filterConfig.getServletContext();
131         String mfks = filterConfig.getInitParameter("maxFormKeys");
132         if (mfks!=null)
133             _maxFormKeys=Integer.parseInt(mfks);
134         _writeFilesWithFilenames = "true".equalsIgnoreCase(filterConfig.getInitParameter("writeFilesWithFilenames"));
135     }
136 
137     /* ------------------------------------------------------------------------------- */
138     /**
139      * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
140      *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
141      */
142     public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
143         throws IOException, ServletException
144     {
145         HttpServletRequest srequest=(HttpServletRequest)request;
146         if(srequest.getContentType()==null||!srequest.getContentType().startsWith("multipart/form-data"))
147         {
148             chain.doFilter(request,response);
149             return;
150         }
151 
152         InputStream in = new BufferedInputStream(request.getInputStream());
153         String content_type=srequest.getContentType();
154 
155         //Get current parameters so we can merge into them
156         MultiMap params = new MultiMap();
157         for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet())
158         {
159             Object value = entry.getValue();
160             if (value instanceof String[])
161                 params.addValues(entry.getKey(), (String[])value);
162             else
163                 params.add(entry.getKey(), value);
164         }
165 
166         MultipartConfigElement config = new MultipartConfigElement(tempdir.getCanonicalPath(), _maxFileSize, _maxRequestSize, _fileOutputBuffer);
167         MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(in, content_type, config, tempdir);
168         mpis.setDeleteOnExit(_deleteFiles);
169         mpis.setWriteFilesWithFilenames(_writeFilesWithFilenames);
170         request.setAttribute(MULTIPART, mpis);
171         try
172         {
173             Collection<Part> parts = mpis.getParts();
174             if (parts != null)
175             {
176                 Iterator<Part> itor = parts.iterator();
177                 while (itor.hasNext() && params.size() < _maxFormKeys)
178                 {
179                     Part p = itor.next();
180                     if (LOG.isDebugEnabled())
181                         LOG.debug("{}",p);
182                     MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
183                     if (mp.getFile() != null)
184                     {
185                         request.setAttribute(mp.getName(),mp.getFile());
186                         if (mp.getContentDispositionFilename() != null)
187                         {
188                             params.add(mp.getName(), mp.getContentDispositionFilename());
189                             if (mp.getContentType() != null)
190                                 params.add(mp.getName()+CONTENT_TYPE_SUFFIX, mp.getContentType());
191                         }
192                     }
193                     else
194                     {
195                         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
196                         IO.copy(p.getInputStream(), bytes);
197                         params.add(p.getName(), bytes.toByteArray());
198                         if (p.getContentType() != null)
199                             params.add(p.getName()+CONTENT_TYPE_SUFFIX, p.getContentType());
200                     }
201                 }
202             }
203 
204             // handle request
205             chain.doFilter(new Wrapper(srequest,params),response);
206         }
207         finally
208         {
209             deleteFiles(request);
210         }
211     }
212     
213     
214     /* ------------------------------------------------------------ */
215     private void deleteFiles(ServletRequest request)
216     {
217         if (!_deleteFiles)
218             return;
219         
220         MultiPartInputStreamParser mpis = (MultiPartInputStreamParser)request.getAttribute(MULTIPART);
221         if (mpis != null)
222         {
223             try
224             {
225                 mpis.deleteParts();
226             }
227             catch (Exception e)
228             {
229                 _context.log("Error deleting multipart tmp files", e);
230             }
231         }
232         request.removeAttribute(MULTIPART);
233     }
234 
235     /* ------------------------------------------------------------------------------- */
236     /**
237      * @see javax.servlet.Filter#destroy()
238      */
239     public void destroy()
240     {
241     }
242 
243     /* ------------------------------------------------------------------------------- */
244     /* ------------------------------------------------------------------------------- */
245     private static class Wrapper extends HttpServletRequestWrapper
246     {
247         Charset _encoding=StandardCharsets.UTF_8;
248         MultiMap<Object> _params;
249 
250         /* ------------------------------------------------------------------------------- */
251         public Wrapper(HttpServletRequest request, MultiMap map)
252         {
253             super(request);
254             this._params=map;
255         }
256 
257         /* ------------------------------------------------------------------------------- */
258         /**
259          * @see javax.servlet.ServletRequest#getContentLength()
260          */
261         @Override
262         public int getContentLength()
263         {
264             return 0;
265         }
266 
267         /* ------------------------------------------------------------------------------- */
268         /**
269          * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
270          */
271         @Override
272         public String getParameter(String name)
273         {
274             Object o=_params.get(name);
275             if (!(o instanceof byte[]) && LazyList.size(o)>0)
276                 o=LazyList.get(o,0);
277 
278             if (o instanceof byte[])
279             {
280                 try
281                 {
282                     return getParameterBytesAsString(name, (byte[])o);
283                 }
284                 catch(Exception e)
285                 {
286                     LOG.warn(e);
287                 }
288             }
289             else if (o!=null)
290                 return String.valueOf(o);
291             return null;
292         }
293 
294         /* ------------------------------------------------------------------------------- */
295         /**
296          * @see javax.servlet.ServletRequest#getParameterMap()
297          */
298         @Override
299         public Map<String, String[]> getParameterMap()
300         {
301             Map<String, String[]> cmap = new HashMap<String,String[]>();
302             
303             for ( Object key : _params.keySet() )
304             {
305                 cmap.put((String)key,getParameterValues((String)key));
306             }
307 
308             return Collections.unmodifiableMap(cmap);
309         }
310 
311         /* ------------------------------------------------------------------------------- */
312         /**
313          * @see javax.servlet.ServletRequest#getParameterNames()
314          */
315         @Override
316         public Enumeration<String> getParameterNames()
317         {
318             return Collections.enumeration(_params.keySet());
319         }
320 
321         /* ------------------------------------------------------------------------------- */
322         /**
323          * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
324          */
325         @Override
326         public String[] getParameterValues(String name)
327         {
328             List l=_params.getValues(name);
329             if (l==null || l.size()==0)
330                 return new String[0];
331             String[] v = new String[l.size()];
332             for (int i=0;i<l.size();i++)
333             {
334                 Object o=l.get(i);
335                 if (o instanceof byte[])
336                 {
337                     try
338                     {
339                         v[i]=getParameterBytesAsString(name, (byte[])o);
340                     }
341                     catch(Exception e)
342                     {
343                         throw new RuntimeException(e);
344                     }
345                 }
346                 else if (o instanceof String)
347                     v[i]=(String)o;
348             }
349             return v;
350         }
351 
352         /* ------------------------------------------------------------------------------- */
353         /**
354          * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
355          */
356         @Override
357         public void setCharacterEncoding(String enc)
358             throws UnsupportedEncodingException
359         {
360             try
361             {
362                 _encoding=Charset.forName(enc);
363             }
364             catch (UnsupportedCharsetException e)
365             {
366                 throw new UnsupportedEncodingException(e.getMessage());
367             }
368         }
369         
370         
371         /* ------------------------------------------------------------------------------- */
372         private String getParameterBytesAsString (String name, byte[] bytes) 
373         throws UnsupportedEncodingException
374         {
375             //check if there is a specific encoding for the parameter
376             Object ct = _params.getValue(name+CONTENT_TYPE_SUFFIX,0);
377             //use default if not
378             Charset contentType = _encoding;
379             if (ct != null)
380             {
381                 String tmp = MimeTypes.getCharsetFromContentType((String)ct);
382                 try
383                 {
384                     contentType = (tmp == null?_encoding:Charset.forName(tmp));
385                 }
386                 catch (UnsupportedCharsetException e)
387                 {
388                     throw new UnsupportedEncodingException(e.getMessage());
389                 }
390             }
391             
392             return new String(bytes,contentType);
393         }
394     }
395 }