1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jetty.servlets;
14
15 import java.io.BufferedInputStream;
16 import java.io.BufferedOutputStream;
17 import java.io.BufferedReader;
18 import java.io.ByteArrayOutputStream;
19 import java.io.File;
20 import java.io.FileOutputStream;
21 import java.io.FilterInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.OutputStream;
26 import java.io.UnsupportedEncodingException;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.Enumeration;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33
34 import javax.servlet.Filter;
35 import javax.servlet.FilterChain;
36 import javax.servlet.FilterConfig;
37 import javax.servlet.ServletContext;
38 import javax.servlet.ServletException;
39 import javax.servlet.ServletRequest;
40 import javax.servlet.ServletResponse;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletRequestWrapper;
43
44 import org.eclipse.jetty.util.B64Code;
45 import org.eclipse.jetty.util.LazyList;
46 import org.eclipse.jetty.util.MultiMap;
47 import org.eclipse.jetty.util.QuotedStringTokenizer;
48 import org.eclipse.jetty.util.StringUtil;
49 import org.eclipse.jetty.util.TypeUtil;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public class MultiPartFilter implements Filter
65 {
66 public final static String CONTENT_TYPE_SUFFIX=".org.eclipse.jetty.servlet.contentType";
67 private final static String FILES ="org.eclipse.jetty.servlet.MultiPartFilter.files";
68 private File tempdir;
69 private boolean _deleteFiles;
70 private ServletContext _context;
71 private int _fileOutputBuffer = 0;
72
73
74
75
76
77 public void init(FilterConfig filterConfig) throws ServletException
78 {
79 tempdir=(File)filterConfig.getServletContext().getAttribute("javax.servlet.context.tempdir");
80 _deleteFiles="true".equals(filterConfig.getInitParameter("deleteFiles"));
81 String fileOutputBuffer = filterConfig.getInitParameter("fileOutputBuffer");
82 if(fileOutputBuffer!=null)
83 _fileOutputBuffer = Integer.parseInt(fileOutputBuffer);
84 _context=filterConfig.getServletContext();
85 }
86
87
88
89
90
91
92 public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
93 throws IOException, ServletException
94 {
95 HttpServletRequest srequest=(HttpServletRequest)request;
96 if(srequest.getContentType()==null||!srequest.getContentType().startsWith("multipart/form-data"))
97 {
98 chain.doFilter(request,response);
99 return;
100 }
101
102 InputStream in = new BufferedInputStream(request.getInputStream());
103 String content_type=srequest.getContentType();
104
105
106 String boundary="--"+QuotedStringTokenizer.unquote(value(content_type.substring(content_type.indexOf("boundary="))).trim());
107
108 byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1);
109
110 MultiMap params = new MultiMap();
111 for (Iterator i = request.getParameterMap().entrySet().iterator();i.hasNext();)
112 {
113 Map.Entry entry=(Map.Entry)i.next();
114 Object value=entry.getValue();
115 if (value instanceof String[])
116 params.addValues(entry.getKey(),(String[])value);
117 else
118 params.add(entry.getKey(),value);
119 }
120
121 try
122 {
123
124 byte[] bytes=TypeUtil.readLine(in);
125 String line=bytes==null?null:new String(bytes,"UTF-8");
126 if(line==null || !line.equals(boundary))
127 {
128 throw new IOException("Missing initial multi part boundary");
129 }
130
131
132 boolean lastPart=false;
133 String content_disposition=null;
134 String content_transfer_encoding=null;
135
136
137 outer:while(!lastPart)
138 {
139 String type_content=null;
140
141 while(true)
142 {
143
144 bytes=TypeUtil.readLine(in);
145 if (bytes==null)
146 break outer;
147
148
149 if(bytes.length==0)
150 break;
151 line=new String(bytes,"UTF-8");
152
153
154 int c=line.indexOf(':',0);
155 if(c>0)
156 {
157 String key=line.substring(0,c).trim().toLowerCase();
158 String value=line.substring(c+1,line.length()).trim();
159 if(key.equals("content-disposition"))
160 content_disposition=value;
161 else if(key.equals("content-transfer-encoding"))
162 content_transfer_encoding=value;
163 else if (key.equals("content-type"))
164 type_content = value;
165 }
166 }
167
168 boolean form_data=false;
169 if(content_disposition==null)
170 {
171 throw new IOException("Missing content-disposition");
172 }
173
174 QuotedStringTokenizer tok=new QuotedStringTokenizer(content_disposition,";");
175 String name=null;
176 String filename=null;
177 while(tok.hasMoreTokens())
178 {
179 String t=tok.nextToken().trim();
180 String tl=t.toLowerCase();
181 if(t.startsWith("form-data"))
182 form_data=true;
183 else if(tl.startsWith("name="))
184 name=value(t);
185 else if(tl.startsWith("filename="))
186 filename=value(t);
187 }
188
189
190 if(!form_data)
191 {
192 continue;
193 }
194
195
196
197
198
199 if(name==null)
200 {
201 continue;
202 }
203
204 OutputStream out=null;
205 File file=null;
206 try
207 {
208 if (filename!=null && filename.length()>0)
209 {
210 file = File.createTempFile("MultiPart", "", tempdir);
211 out = new FileOutputStream(file);
212 if(_fileOutputBuffer>0)
213 out = new BufferedOutputStream(out, _fileOutputBuffer);
214 request.setAttribute(name,file);
215 params.add(name, filename);
216 if (type_content != null)
217 params.add(name+CONTENT_TYPE_SUFFIX, type_content);
218
219 if (_deleteFiles)
220 {
221 file.deleteOnExit();
222 ArrayList files = (ArrayList)request.getAttribute(FILES);
223 if (files==null)
224 {
225 files=new ArrayList();
226 request.setAttribute(FILES,files);
227 }
228 files.add(file);
229 }
230 }
231 else
232 {
233 out=new ByteArrayOutputStream();
234 }
235
236
237 if ("base64".equalsIgnoreCase(content_transfer_encoding))
238 {
239 in = new Base64InputStream(in);
240 }
241 else if ("quoted-printable".equalsIgnoreCase(content_transfer_encoding))
242 {
243 in = new FilterInputStream(in)
244 {
245 @Override
246 public int read() throws IOException
247 {
248 int c = in.read();
249 if (c >= 0 && c == '=')
250 {
251 int hi = in.read();
252 int lo = in.read();
253 if (hi < 0 || lo < 0)
254 {
255 throw new IOException("Unexpected end to quoted-printable byte");
256 }
257 char[] chars = new char[] { (char)hi, (char)lo };
258 c = Integer.parseInt(new String(chars),16);
259 }
260 return c;
261 }
262 };
263 }
264
265 int state=-2;
266 int c;
267 boolean cr=false;
268 boolean lf=false;
269
270
271 while(true)
272 {
273 int b=0;
274 while((c=(state!=-2)?state:in.read())!=-1)
275 {
276 state=-2;
277
278 if(c==13||c==10)
279 {
280 if(c==13)
281 state=in.read();
282 break;
283 }
284
285 if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
286 b++;
287 else
288 {
289
290 if(cr)
291 out.write(13);
292 if(lf)
293 out.write(10);
294 cr=lf=false;
295 if(b>0)
296 out.write(byteBoundary,0,b);
297 b=-1;
298 out.write(c);
299 }
300 }
301
302 if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
303 {
304 if(cr)
305 out.write(13);
306 if(lf)
307 out.write(10);
308 cr=lf=false;
309 out.write(byteBoundary,0,b);
310 b=-1;
311 }
312
313 if(b>0||c==-1)
314 {
315 if(b==byteBoundary.length)
316 lastPart=true;
317 if(state==10)
318 state=-2;
319 break;
320 }
321
322 if(cr)
323 out.write(13);
324 if(lf)
325 out.write(10);
326 cr=(c==13);
327 lf=(c==10||state==10);
328 if(state==10)
329 state=-2;
330 }
331 }
332 finally
333 {
334 out.close();
335 }
336
337 if (file==null)
338 {
339 bytes = ((ByteArrayOutputStream)out).toByteArray();
340 params.add(name,bytes);
341 if (type_content != null)
342 params.add(name+CONTENT_TYPE_SUFFIX, type_content);
343 }
344 }
345
346
347 chain.doFilter(new Wrapper(srequest,params),response);
348 }
349 finally
350 {
351 deleteFiles(request);
352 }
353 }
354
355 private void deleteFiles(ServletRequest request)
356 {
357 ArrayList files = (ArrayList)request.getAttribute(FILES);
358 if (files!=null)
359 {
360 Iterator iter = files.iterator();
361 while (iter.hasNext())
362 {
363 File file=(File)iter.next();
364 try
365 {
366 file.delete();
367 }
368 catch(Exception e)
369 {
370 _context.log("failed to delete "+file,e);
371 }
372 }
373 }
374 }
375
376
377 private String value(String nameEqualsValue)
378 {
379 return nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
380 }
381
382
383
384
385
386 public void destroy()
387 {
388 }
389
390
391
392 private static class Wrapper extends HttpServletRequestWrapper
393 {
394 String _encoding=StringUtil.__UTF8;
395 MultiMap _params;
396
397
398
399
400
401 public Wrapper(HttpServletRequest request, MultiMap map)
402 {
403 super(request);
404 this._params=map;
405 }
406
407
408
409
410
411 @Override
412 public int getContentLength()
413 {
414 return 0;
415 }
416
417
418
419
420
421 @Override
422 public String getParameter(String name)
423 {
424 Object o=_params.get(name);
425 if (!(o instanceof byte[]) && LazyList.size(o)>0)
426 o=LazyList.get(o,0);
427
428 if (o instanceof byte[])
429 {
430 try
431 {
432 String s=new String((byte[])o,_encoding);
433 return s;
434 }
435 catch(Exception e)
436 {
437 e.printStackTrace();
438 }
439 }
440 else if (o!=null)
441 return String.valueOf(o);
442 return null;
443 }
444
445
446
447
448
449 @Override
450 public Map getParameterMap()
451 {
452 return Collections.unmodifiableMap(_params.toStringArrayMap());
453 }
454
455
456
457
458
459 @Override
460 public Enumeration getParameterNames()
461 {
462 return Collections.enumeration(_params.keySet());
463 }
464
465
466
467
468
469 @Override
470 public String[] getParameterValues(String name)
471 {
472 List l=_params.getValues(name);
473 if (l==null || l.size()==0)
474 return new String[0];
475 String[] v = new String[l.size()];
476 for (int i=0;i<l.size();i++)
477 {
478 Object o=l.get(i);
479 if (o instanceof byte[])
480 {
481 try
482 {
483 v[i]=new String((byte[])o,_encoding);
484 }
485 catch(Exception e)
486 {
487 e.printStackTrace();
488 }
489 }
490 else if (o instanceof String)
491 v[i]=(String)o;
492 }
493 return v;
494 }
495
496
497
498
499
500 @Override
501 public void setCharacterEncoding(String enc)
502 throws UnsupportedEncodingException
503 {
504 _encoding=enc;
505 }
506 }
507
508 private static class Base64InputStream extends InputStream
509 {
510 BufferedReader _in;
511 String _line;
512 byte[] _buffer;
513 int _pos;
514
515 public Base64InputStream (InputStream in)
516 {
517 _in = new BufferedReader(new InputStreamReader(in));
518 }
519
520 @Override
521 public int read() throws IOException
522 {
523 if (_buffer==null || _pos>= _buffer.length)
524 {
525 _line = _in.readLine();
526 if (_line==null)
527 return -1;
528 if (_line.startsWith("--"))
529 _buffer=(_line+"\r\n").getBytes();
530 else if (_line.length()==0)
531 _buffer="\r\n".getBytes();
532 else
533 _buffer=B64Code.decode(_line);
534
535 _pos=0;
536 }
537 return _buffer[_pos++];
538 }
539 }
540 }