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