1
2
3
4
5
6
7 package org.mule.transport.http.multipart;
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.mule.model.streaming.DeleteOnCloseFileInputStream;
23
24 import java.io.BufferedInputStream;
25 import java.io.BufferedOutputStream;
26 import java.io.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
28 import java.io.File;
29 import java.io.FileNotFoundException;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.util.Collection;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.StringTokenizer;
38
39 import javax.servlet.ServletException;
40
41 import org.apache.commons.io.input.AutoCloseInputStream;
42
43
44
45
46
47
48 public class MultiPartInputStream
49 {
50 public static final MultipartConfiguration __DEFAULT_MULTIPART_CONFIG = new MultipartConfiguration(System.getProperty("java.io.tmpdir"));
51 protected InputStream _in;
52 protected MultipartConfiguration _config;
53 protected String _contentType;
54 protected MultiMap _map;
55 protected Map<String, Part> _parts;
56 protected File _tmpDir;
57
58
59
60
61 public class MultiPart implements Part
62 {
63 protected String _name;
64 protected String _filename;
65 protected File _file;
66 protected OutputStream _out;
67 protected String _contentType;
68 protected MultiMap<String> _headers;
69 protected long _size = 0;
70
71 public MultiPart (String name, String filename)
72 throws IOException
73 {
74 _name = name;
75 _filename = filename;
76 }
77
78 protected void setContentType (String contentType)
79 {
80 _contentType = contentType;
81 }
82
83
84 protected void open()
85 throws FileNotFoundException, IOException
86 {
87
88
89
90 if (_filename != null && _filename.trim().length() > 0)
91 {
92 createFile();
93 }
94 else
95 {
96
97
98 _out = new ByteArrayOutputStream();
99 }
100 }
101
102 protected void close()
103 throws IOException
104 {
105 _out.close();
106 }
107
108
109 protected void write (int b)
110 throws IOException
111 {
112 if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStream.this._config.getMaxFileSize())
113 throw new IOException ("Multipart Mime part "+_name+" exceeds max filesize");
114
115 if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
116 createFile();
117 _out.write(b);
118 _size ++;
119 }
120
121 protected void write (byte[] bytes, int offset, int length)
122 throws IOException
123 {
124 if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStream.this._config.getMaxFileSize())
125 throw new IOException ("Multipart Mime part "+_name+" exceeds max filesize");
126
127 if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
128 createFile();
129
130 _out.write(bytes, offset, length);
131 _size += length;
132 }
133
134 protected void createFile ()
135 throws IOException
136 {
137 _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
138 FileOutputStream fos = new FileOutputStream(_file);
139 BufferedOutputStream bos = new BufferedOutputStream(fos);
140
141 if (_size > 0 && _out != null)
142 {
143
144 _out.flush();
145 ((ByteArrayOutputStream)_out).writeTo(bos);
146 _out.close();
147 }
148 _out = bos;
149 }
150
151
152
153 protected void setHeaders(MultiMap<String> headers)
154 {
155 _headers = headers;
156 }
157
158
159
160
161 public String getContentType()
162 {
163 return _contentType;
164 }
165
166
167
168
169 public String getHeader(String name)
170 {
171 return (String)_headers.getValue(name, 0);
172 }
173
174
175
176
177 public Collection<String> getHeaderNames()
178 {
179 return _headers.keySet();
180 }
181
182
183
184
185 public Collection<String> getHeaders(String name)
186 {
187 return _headers.getValues(name);
188 }
189
190
191
192
193 public InputStream getInputStream() throws IOException
194 {
195 if (_file != null)
196 {
197
198 return new BufferedInputStream(new AutoCloseInputStream(new DeleteOnCloseFileInputStream(_file)));
199 }
200 else
201 {
202
203 return new ByteArrayInputStream(((ByteArrayOutputStream)_out).toByteArray());
204 }
205 }
206
207
208
209
210 public String getName()
211 {
212 return _name;
213 }
214
215
216
217
218 public long getSize()
219 {
220 return _size;
221 }
222
223
224
225
226 public void write(String fileName) throws IOException
227 {
228 if (_file == null)
229 {
230
231 _file = new File (_tmpDir, fileName);
232 BufferedOutputStream bos = null;
233 try
234 {
235 bos = new BufferedOutputStream(new FileOutputStream(_file));
236 ((ByteArrayOutputStream)_out).writeTo(bos);
237 bos.flush();
238 }
239 finally
240 {
241 if (bos != null)
242 bos.close();
243 }
244 }
245 else
246 {
247
248 _file.renameTo(new File(_tmpDir, fileName));
249 }
250 }
251
252
253
254
255 public void delete() throws IOException
256 {
257 if (_file != null)
258 _file.delete();
259 }
260
261
262
263
264
265
266 public File getFile ()
267 {
268 return _file;
269 }
270
271
272
273
274
275
276 public String getContentDispositionFilename ()
277 {
278 return _filename;
279 }
280 }
281
282
283
284
285
286
287
288
289
290 public MultiPartInputStream(InputStream in, String contentType, MultipartConfiguration config)
291 {
292 _in = new BufferedInputStream(in);
293 _contentType = contentType;
294 _config = config;
295 if (_config == null)
296 _config = __DEFAULT_MULTIPART_CONFIG;
297 }
298
299
300
301 public Collection<Part> getParts()
302 throws IOException
303 {
304 parse();
305 return _parts.values();
306 }
307
308
309 public Part getPart(String name)
310 throws IOException, ServletException
311 {
312 parse();
313 return _parts.get(name);
314 }
315
316
317 public MultiMap getMap ()
318 throws IOException, ServletException
319 {
320 parse();
321 return _map;
322 }
323
324
325 protected void parse ()
326 throws IOException
327 {
328
329 if (_parts != null)
330 return;
331
332
333 long total = 0;
334 _parts = new HashMap<String, Part>();
335
336
337 if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
338 return;
339
340
341 String location = __DEFAULT_MULTIPART_CONFIG.getLocation();
342 location = ("".equals(_config.getLocation())? location : _config.getLocation());
343
344 _tmpDir = new File(location);
345 if (!_tmpDir.exists())
346 _tmpDir.mkdirs();
347
348
349 String boundary="--"+value(_contentType.substring(_contentType.indexOf("boundary=")));
350 byte[] byteBoundary=(boundary+"--").getBytes("ISO-8859-1");
351
352
353 byte[] bytes=readLine(_in);
354 String line=bytes==null?null:new String(bytes,"UTF-8");
355 if(line==null || !line.equals(boundary))
356 {
357 throw new IOException("Missing initial multi part boundary");
358 }
359
360
361 boolean lastPart=false;
362 String contentDisposition=null;
363 String contentType=null;
364 outer:while(!lastPart)
365 {
366 MultiMap<String> headers = new MultiMap<String>();
367 while(true)
368 {
369 bytes=readLine(_in);
370 if(bytes==null)
371 break outer;
372
373
374 if(bytes.length==0)
375 break;
376
377 total += bytes.length;
378 if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
379 throw new IOException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
380
381 line=new String(bytes,"UTF-8");
382
383
384 int c=line.indexOf(':',0);
385 if(c>0)
386 {
387 String key=line.substring(0,c).trim().toLowerCase();
388 String value=line.substring(c+1,line.length()).trim();
389 headers.put(key, value);
390 if (key.equalsIgnoreCase("content-disposition"))
391 contentDisposition=value;
392 if (key.equalsIgnoreCase("content-type"))
393 contentType = value;
394 }
395 }
396
397
398 boolean form_data=false;
399 if(contentDisposition==null)
400 {
401 throw new IOException("Missing content-disposition");
402 }
403
404 StringTokenizer tok=new StringTokenizer(contentDisposition,";");
405 String name=null;
406 String filename=null;
407 while(tok.hasMoreTokens())
408 {
409 String t=tok.nextToken().trim();
410 String tl=t.toLowerCase();
411 if(t.startsWith("form-data"))
412 form_data=true;
413 else if(tl.startsWith("name="))
414 name=value(t);
415 else if(tl.startsWith("filename="))
416 filename=value(t);
417 }
418
419
420 if(!form_data)
421 {
422 continue;
423 }
424
425
426
427
428
429 if(name==null)
430 {
431 continue;
432 }
433
434
435 MultiPart part = new MultiPart(name, filename);
436 part.setHeaders(headers);
437 part.setContentType(contentType);
438 _parts.put(name, part);
439
440 part.open();
441
442 try
443 {
444 int state=-2;
445 int c;
446 boolean cr=false;
447 boolean lf=false;
448
449
450 while(true)
451 {
452 int b=0;
453 while((c=(state!=-2)?state:_in.read())!=-1)
454 {
455 total ++;
456 if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
457 throw new IOException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
458
459 state=-2;
460
461 if(c==13||c==10)
462 {
463 if(c==13)
464 state=_in.read();
465 break;
466 }
467
468 if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
469 b++;
470 else
471 {
472
473 if(cr)
474 part.write(13);
475
476 if(lf)
477 part.write(10);
478
479 cr=lf=false;
480 if(b>0)
481 part.write(byteBoundary,0,b);
482
483 b=-1;
484 part.write(c);
485 }
486 }
487
488 if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
489 {
490 if(cr)
491 part.write(13);
492
493 if(lf)
494 part.write(10);
495
496 cr=lf=false;
497 part.write(byteBoundary,0,b);
498 b=-1;
499 }
500
501 if(b>0||c==-1)
502 {
503 if(b==byteBoundary.length)
504 lastPart=true;
505 if(state==10)
506 state=-2;
507 break;
508 }
509
510 if(cr)
511 part.write(13);
512
513 if(lf)
514 part.write(10);
515
516 cr=(c==13);
517 lf=(c==10||state==10);
518 if(state==10)
519 state=-2;
520 }
521 }
522 finally
523 {
524
525 part.close();
526 }
527 }
528 }
529
530
531
532 private String value(String nameEqualsValue)
533 {
534 String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
535 int i=value.indexOf(';');
536 if(i>0)
537 value=value.substring(0,i);
538 if(value.startsWith("\""))
539 {
540 value=value.substring(1,value.indexOf('"',1));
541 }
542 else
543 {
544 i=value.indexOf(' ');
545 if(i>0)
546 value=value.substring(0,i);
547 }
548 return value;
549 }
550
551 public static int CR = '\015';
552 public static int LF = '\012';
553
554 private byte[] readLine(InputStream in) throws IOException
555 {
556 byte[] buf = new byte[256];
557
558 int i=0;
559 int loops=0;
560 int ch=0;
561
562 while (true)
563 {
564 ch=in.read();
565 if (ch<0)
566 break;
567 loops++;
568
569
570 if (loops==1 && ch==LF)
571 continue;
572
573 if (ch==CR || ch==LF)
574 break;
575
576 if (i>=buf.length)
577 {
578 byte[] old_buf=buf;
579 buf=new byte[old_buf.length+256];
580 System.arraycopy(old_buf, 0, buf, 0, old_buf.length);
581 }
582 buf[i++]=(byte)ch;
583 }
584
585 if (ch==-1 && i==0)
586 return null;
587
588
589 if (ch==CR && in.available()>=1 && in.markSupported())
590 {
591 in.mark(1);
592 ch=in.read();
593 if (ch!=LF)
594 in.reset();
595 }
596
597 byte[] old_buf=buf;
598 buf=new byte[i];
599 System.arraycopy(old_buf, 0, buf, 0, i);
600
601 return buf;
602 }
603
604 }