Mercurial Hosting > luan
comparison src/org/eclipse/jetty/util/MultiPartInputStream.java @ 802:3428c60d7cfc
replace jetty jars with source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 07 Sep 2016 21:15:48 -0600 |
parents | |
children | 8e9db0bbf4f9 |
comparison
equal
deleted
inserted
replaced
801:6a21393191c1 | 802:3428c60d7cfc |
---|---|
1 // | |
2 // ======================================================================== | |
3 // Copyright (c) 1995-2014 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.util; | |
20 | |
21 import java.io.BufferedInputStream; | |
22 import java.io.BufferedOutputStream; | |
23 import java.io.BufferedReader; | |
24 import java.io.ByteArrayInputStream; | |
25 import java.io.ByteArrayOutputStream; | |
26 import java.io.File; | |
27 import java.io.FileInputStream; | |
28 import java.io.FileNotFoundException; | |
29 import java.io.FileOutputStream; | |
30 import java.io.FilterInputStream; | |
31 import java.io.IOException; | |
32 import java.io.InputStream; | |
33 import java.io.InputStreamReader; | |
34 import java.io.OutputStream; | |
35 import java.lang.reflect.Array; | |
36 import java.util.ArrayList; | |
37 import java.util.Collection; | |
38 import java.util.Collections; | |
39 import java.util.HashMap; | |
40 import java.util.List; | |
41 import java.util.Locale; | |
42 import java.util.Map; | |
43 import java.util.StringTokenizer; | |
44 | |
45 import javax.servlet.MultipartConfigElement; | |
46 import javax.servlet.ServletException; | |
47 import javax.servlet.http.Part; | |
48 | |
49 import org.eclipse.jetty.util.log.Log; | |
50 import org.eclipse.jetty.util.log.Logger; | |
51 | |
52 | |
53 | |
54 /** | |
55 * MultiPartInputStream | |
56 * | |
57 * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings. | |
58 */ | |
59 public class MultiPartInputStream | |
60 { | |
61 private static final Logger LOG = Log.getLogger(MultiPartInputStream.class); | |
62 | |
63 public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir")); | |
64 protected InputStream _in; | |
65 protected MultipartConfigElement _config; | |
66 protected String _contentType; | |
67 protected MultiMap<String> _parts; | |
68 protected File _tmpDir; | |
69 protected File _contextTmpDir; | |
70 protected boolean _deleteOnExit; | |
71 | |
72 | |
73 | |
74 public class MultiPart implements Part | |
75 { | |
76 protected String _name; | |
77 protected String _filename; | |
78 protected File _file; | |
79 protected OutputStream _out; | |
80 protected ByteArrayOutputStream2 _bout; | |
81 protected String _contentType; | |
82 protected MultiMap<String> _headers; | |
83 protected long _size = 0; | |
84 protected boolean _temporary = true; | |
85 | |
86 public MultiPart (String name, String filename) | |
87 throws IOException | |
88 { | |
89 _name = name; | |
90 _filename = filename; | |
91 } | |
92 | |
93 protected void setContentType (String contentType) | |
94 { | |
95 _contentType = contentType; | |
96 } | |
97 | |
98 | |
99 protected void open() | |
100 throws IOException | |
101 { | |
102 //We will either be writing to a file, if it has a filename on the content-disposition | |
103 //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we | |
104 //will need to change to write to a file. | |
105 if (_filename != null && _filename.trim().length() > 0) | |
106 { | |
107 createFile(); | |
108 } | |
109 else | |
110 { | |
111 //Write to a buffer in memory until we discover we've exceed the | |
112 //MultipartConfig fileSizeThreshold | |
113 _out = _bout= new ByteArrayOutputStream2(); | |
114 } | |
115 } | |
116 | |
117 protected void close() | |
118 throws IOException | |
119 { | |
120 _out.close(); | |
121 } | |
122 | |
123 | |
124 protected void write (int b) | |
125 throws IOException | |
126 { | |
127 if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStream.this._config.getMaxFileSize()) | |
128 throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize"); | |
129 | |
130 if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null) | |
131 createFile(); | |
132 _out.write(b); | |
133 _size ++; | |
134 } | |
135 | |
136 protected void write (byte[] bytes, int offset, int length) | |
137 throws IOException | |
138 { | |
139 if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStream.this._config.getMaxFileSize()) | |
140 throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize"); | |
141 | |
142 if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null) | |
143 createFile(); | |
144 | |
145 _out.write(bytes, offset, length); | |
146 _size += length; | |
147 } | |
148 | |
149 protected void createFile () | |
150 throws IOException | |
151 { | |
152 _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir); | |
153 if (_deleteOnExit) | |
154 _file.deleteOnExit(); | |
155 FileOutputStream fos = new FileOutputStream(_file); | |
156 BufferedOutputStream bos = new BufferedOutputStream(fos); | |
157 | |
158 if (_size > 0 && _out != null) | |
159 { | |
160 //already written some bytes, so need to copy them into the file | |
161 _out.flush(); | |
162 _bout.writeTo(bos); | |
163 _out.close(); | |
164 _bout = null; | |
165 } | |
166 _out = bos; | |
167 } | |
168 | |
169 | |
170 | |
171 protected void setHeaders(MultiMap<String> headers) | |
172 { | |
173 _headers = headers; | |
174 } | |
175 | |
176 /** | |
177 * @see javax.servlet.http.Part#getContentType() | |
178 */ | |
179 public String getContentType() | |
180 { | |
181 return _contentType; | |
182 } | |
183 | |
184 /** | |
185 * @see javax.servlet.http.Part#getHeader(java.lang.String) | |
186 */ | |
187 public String getHeader(String name) | |
188 { | |
189 if (name == null) | |
190 return null; | |
191 return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0); | |
192 } | |
193 | |
194 /** | |
195 * @see javax.servlet.http.Part#getHeaderNames() | |
196 */ | |
197 public Collection<String> getHeaderNames() | |
198 { | |
199 return _headers.keySet(); | |
200 } | |
201 | |
202 /** | |
203 * @see javax.servlet.http.Part#getHeaders(java.lang.String) | |
204 */ | |
205 public Collection<String> getHeaders(String name) | |
206 { | |
207 return _headers.getValues(name); | |
208 } | |
209 | |
210 /** | |
211 * @see javax.servlet.http.Part#getInputStream() | |
212 */ | |
213 public InputStream getInputStream() throws IOException | |
214 { | |
215 if (_file != null) | |
216 { | |
217 //written to a file, whether temporary or not | |
218 return new BufferedInputStream (new FileInputStream(_file)); | |
219 } | |
220 else | |
221 { | |
222 //part content is in memory | |
223 return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); | |
224 } | |
225 } | |
226 | |
227 public byte[] getBytes() | |
228 { | |
229 if (_bout!=null) | |
230 return _bout.toByteArray(); | |
231 return null; | |
232 } | |
233 | |
234 /** | |
235 * @see javax.servlet.http.Part#getName() | |
236 */ | |
237 public String getName() | |
238 { | |
239 return _name; | |
240 } | |
241 | |
242 /** | |
243 * @see javax.servlet.http.Part#getSize() | |
244 */ | |
245 public long getSize() | |
246 { | |
247 return _size; | |
248 } | |
249 | |
250 /** | |
251 * @see javax.servlet.http.Part#write(java.lang.String) | |
252 */ | |
253 public void write(String fileName) throws IOException | |
254 { | |
255 if (_file == null) | |
256 { | |
257 _temporary = false; | |
258 | |
259 //part data is only in the ByteArrayOutputStream and never been written to disk | |
260 _file = new File (_tmpDir, fileName); | |
261 | |
262 BufferedOutputStream bos = null; | |
263 try | |
264 { | |
265 bos = new BufferedOutputStream(new FileOutputStream(_file)); | |
266 _bout.writeTo(bos); | |
267 bos.flush(); | |
268 } | |
269 finally | |
270 { | |
271 if (bos != null) | |
272 bos.close(); | |
273 _bout = null; | |
274 } | |
275 } | |
276 else | |
277 { | |
278 //the part data is already written to a temporary file, just rename it | |
279 _temporary = false; | |
280 | |
281 File f = new File(_tmpDir, fileName); | |
282 if (_file.renameTo(f)) | |
283 _file = f; | |
284 } | |
285 } | |
286 | |
287 /** | |
288 * Remove the file, whether or not Part.write() was called on it | |
289 * (ie no longer temporary) | |
290 * @see javax.servlet.http.Part#delete() | |
291 */ | |
292 public void delete() throws IOException | |
293 { | |
294 if (_file != null && _file.exists()) | |
295 _file.delete(); | |
296 } | |
297 | |
298 /** | |
299 * Only remove tmp files. | |
300 * | |
301 * @throws IOException | |
302 */ | |
303 public void cleanUp() throws IOException | |
304 { | |
305 if (_temporary && _file != null && _file.exists()) | |
306 _file.delete(); | |
307 } | |
308 | |
309 | |
310 /** | |
311 * Get the file, if any, the data has been written to. | |
312 * @return | |
313 */ | |
314 public File getFile () | |
315 { | |
316 return _file; | |
317 } | |
318 | |
319 | |
320 /** | |
321 * Get the filename from the content-disposition. | |
322 * @return null or the filename | |
323 */ | |
324 public String getContentDispositionFilename () | |
325 { | |
326 return _filename; | |
327 } | |
328 } | |
329 | |
330 | |
331 | |
332 | |
333 /** | |
334 * @param in Request input stream | |
335 * @param contentType Content-Type header | |
336 * @param config MultipartConfigElement | |
337 * @param contextTmpDir javax.servlet.context.tempdir | |
338 */ | |
339 public MultiPartInputStream (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) | |
340 { | |
341 _in = new ReadLineInputStream(in); | |
342 _contentType = contentType; | |
343 _config = config; | |
344 _contextTmpDir = contextTmpDir; | |
345 if (_contextTmpDir == null) | |
346 _contextTmpDir = new File (System.getProperty("java.io.tmpdir")); | |
347 | |
348 if (_config == null) | |
349 _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); | |
350 } | |
351 | |
352 /** | |
353 * Get the already parsed parts. | |
354 * | |
355 * @return | |
356 */ | |
357 public Collection<Part> getParsedParts() | |
358 { | |
359 if (_parts == null) | |
360 return Collections.emptyList(); | |
361 | |
362 Collection<Object> values = _parts.values(); | |
363 List<Part> parts = new ArrayList<Part>(); | |
364 for (Object o: values) | |
365 { | |
366 List<Part> asList = LazyList.getList(o, false); | |
367 parts.addAll(asList); | |
368 } | |
369 return parts; | |
370 } | |
371 | |
372 /** | |
373 * Delete any tmp storage for parts, and clear out the parts list. | |
374 * | |
375 * @throws MultiException | |
376 */ | |
377 public void deleteParts () | |
378 throws MultiException | |
379 { | |
380 Collection<Part> parts = getParsedParts(); | |
381 MultiException err = new MultiException(); | |
382 for (Part p:parts) | |
383 { | |
384 try | |
385 { | |
386 ((MultiPartInputStream.MultiPart)p).cleanUp(); | |
387 } | |
388 catch(Exception e) | |
389 { | |
390 err.add(e); | |
391 } | |
392 } | |
393 _parts.clear(); | |
394 | |
395 err.ifExceptionThrowMulti(); | |
396 } | |
397 | |
398 | |
399 /** | |
400 * Parse, if necessary, the multipart data and return the list of Parts. | |
401 * | |
402 * @return | |
403 * @throws IOException | |
404 * @throws ServletException | |
405 */ | |
406 public Collection<Part> getParts() | |
407 throws IOException, ServletException | |
408 { | |
409 parse(); | |
410 Collection<Object> values = _parts.values(); | |
411 List<Part> parts = new ArrayList<Part>(); | |
412 for (Object o: values) | |
413 { | |
414 List<Part> asList = LazyList.getList(o, false); | |
415 parts.addAll(asList); | |
416 } | |
417 return parts; | |
418 } | |
419 | |
420 | |
421 /** | |
422 * Get the named Part. | |
423 * | |
424 * @param name | |
425 * @return | |
426 * @throws IOException | |
427 * @throws ServletException | |
428 */ | |
429 public Part getPart(String name) | |
430 throws IOException, ServletException | |
431 { | |
432 parse(); | |
433 return (Part)_parts.getValue(name, 0); | |
434 } | |
435 | |
436 | |
437 /** | |
438 * Parse, if necessary, the multipart stream. | |
439 * | |
440 * @throws IOException | |
441 * @throws ServletException | |
442 */ | |
443 protected void parse () | |
444 throws IOException, ServletException | |
445 { | |
446 //have we already parsed the input? | |
447 if (_parts != null) | |
448 return; | |
449 | |
450 //initialize | |
451 long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize | |
452 _parts = new MultiMap<String>(); | |
453 | |
454 //if its not a multipart request, don't parse it | |
455 if (_contentType == null || !_contentType.startsWith("multipart/form-data")) | |
456 return; | |
457 | |
458 //sort out the location to which to write the files | |
459 | |
460 if (_config.getLocation() == null) | |
461 _tmpDir = _contextTmpDir; | |
462 else if ("".equals(_config.getLocation())) | |
463 _tmpDir = _contextTmpDir; | |
464 else | |
465 { | |
466 File f = new File (_config.getLocation()); | |
467 if (f.isAbsolute()) | |
468 _tmpDir = f; | |
469 else | |
470 _tmpDir = new File (_contextTmpDir, _config.getLocation()); | |
471 } | |
472 | |
473 if (!_tmpDir.exists()) | |
474 _tmpDir.mkdirs(); | |
475 | |
476 String contentTypeBoundary = ""; | |
477 int bstart = _contentType.indexOf("boundary="); | |
478 if (bstart >= 0) | |
479 { | |
480 int bend = _contentType.indexOf(";", bstart); | |
481 bend = (bend < 0? _contentType.length(): bend); | |
482 contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend), true).trim()); | |
483 } | |
484 | |
485 String boundary="--"+contentTypeBoundary; | |
486 byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1); | |
487 | |
488 // Get first boundary | |
489 String line = null; | |
490 try | |
491 { | |
492 line=((ReadLineInputStream)_in).readLine(); | |
493 } | |
494 catch (IOException e) | |
495 { | |
496 LOG.warn("Badly formatted multipart request"); | |
497 throw e; | |
498 } | |
499 | |
500 if (line == null) | |
501 throw new IOException("Missing content for multipart request"); | |
502 | |
503 boolean badFormatLogged = false; | |
504 line=line.trim(); | |
505 while (line != null && !line.equals(boundary)) | |
506 { | |
507 if (!badFormatLogged) | |
508 { | |
509 LOG.warn("Badly formatted multipart request"); | |
510 badFormatLogged = true; | |
511 } | |
512 line=((ReadLineInputStream)_in).readLine(); | |
513 line=(line==null?line:line.trim()); | |
514 } | |
515 | |
516 if (line == null) | |
517 throw new IOException("Missing initial multi part boundary"); | |
518 | |
519 // Read each part | |
520 boolean lastPart=false; | |
521 | |
522 outer:while(!lastPart) | |
523 { | |
524 String contentDisposition=null; | |
525 String contentType=null; | |
526 String contentTransferEncoding=null; | |
527 | |
528 MultiMap<String> headers = new MultiMap<String>(); | |
529 while(true) | |
530 { | |
531 line=((ReadLineInputStream)_in).readLine(); | |
532 | |
533 //No more input | |
534 if(line==null) | |
535 break outer; | |
536 | |
537 // If blank line, end of part headers | |
538 if("".equals(line)) | |
539 break; | |
540 | |
541 total += line.length(); | |
542 if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) | |
543 throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")"); | |
544 | |
545 //get content-disposition and content-type | |
546 int c=line.indexOf(':',0); | |
547 if(c>0) | |
548 { | |
549 String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH); | |
550 String value=line.substring(c+1,line.length()).trim(); | |
551 headers.put(key, value); | |
552 if (key.equalsIgnoreCase("content-disposition")) | |
553 contentDisposition=value; | |
554 if (key.equalsIgnoreCase("content-type")) | |
555 contentType = value; | |
556 if(key.equals("content-transfer-encoding")) | |
557 contentTransferEncoding=value; | |
558 | |
559 } | |
560 } | |
561 | |
562 // Extract content-disposition | |
563 boolean form_data=false; | |
564 if(contentDisposition==null) | |
565 { | |
566 throw new IOException("Missing content-disposition"); | |
567 } | |
568 | |
569 QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true); | |
570 String name=null; | |
571 String filename=null; | |
572 while(tok.hasMoreTokens()) | |
573 { | |
574 String t=tok.nextToken().trim(); | |
575 String tl=t.toLowerCase(Locale.ENGLISH); | |
576 if(t.startsWith("form-data")) | |
577 form_data=true; | |
578 else if(tl.startsWith("name=")) | |
579 name=value(t, true); | |
580 else if(tl.startsWith("filename=")) | |
581 filename=filenameValue(t); | |
582 } | |
583 | |
584 // Check disposition | |
585 if(!form_data) | |
586 { | |
587 continue; | |
588 } | |
589 //It is valid for reset and submit buttons to have an empty name. | |
590 //If no name is supplied, the browser skips sending the info for that field. | |
591 //However, if you supply the empty string as the name, the browser sends the | |
592 //field, with name as the empty string. So, only continue this loop if we | |
593 //have not yet seen a name field. | |
594 if(name==null) | |
595 { | |
596 continue; | |
597 } | |
598 | |
599 //Have a new Part | |
600 MultiPart part = new MultiPart(name, filename); | |
601 part.setHeaders(headers); | |
602 part.setContentType(contentType); | |
603 _parts.add(name, part); | |
604 part.open(); | |
605 | |
606 InputStream partInput = null; | |
607 if ("base64".equalsIgnoreCase(contentTransferEncoding)) | |
608 { | |
609 partInput = new Base64InputStream((ReadLineInputStream)_in); | |
610 } | |
611 else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) | |
612 { | |
613 partInput = new FilterInputStream(_in) | |
614 { | |
615 @Override | |
616 public int read() throws IOException | |
617 { | |
618 int c = in.read(); | |
619 if (c >= 0 && c == '=') | |
620 { | |
621 int hi = in.read(); | |
622 int lo = in.read(); | |
623 if (hi < 0 || lo < 0) | |
624 { | |
625 throw new IOException("Unexpected end to quoted-printable byte"); | |
626 } | |
627 char[] chars = new char[] { (char)hi, (char)lo }; | |
628 c = Integer.parseInt(new String(chars),16); | |
629 } | |
630 return c; | |
631 } | |
632 }; | |
633 } | |
634 else | |
635 partInput = _in; | |
636 | |
637 try | |
638 { | |
639 int state=-2; | |
640 int c; | |
641 boolean cr=false; | |
642 boolean lf=false; | |
643 | |
644 // loop for all lines | |
645 while(true) | |
646 { | |
647 int b=0; | |
648 while((c=(state!=-2)?state:partInput.read())!=-1) | |
649 { | |
650 total ++; | |
651 if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) | |
652 throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")"); | |
653 | |
654 state=-2; | |
655 | |
656 // look for CR and/or LF | |
657 if(c==13||c==10) | |
658 { | |
659 if(c==13) | |
660 { | |
661 partInput.mark(1); | |
662 int tmp=partInput.read(); | |
663 if (tmp!=10) | |
664 partInput.reset(); | |
665 else | |
666 state=tmp; | |
667 } | |
668 break; | |
669 } | |
670 | |
671 // Look for boundary | |
672 if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b]) | |
673 { | |
674 b++; | |
675 } | |
676 else | |
677 { | |
678 // Got a character not part of the boundary, so we don't have the boundary marker. | |
679 // Write out as many chars as we matched, then the char we're looking at. | |
680 if(cr) | |
681 part.write(13); | |
682 | |
683 if(lf) | |
684 part.write(10); | |
685 | |
686 cr=lf=false; | |
687 if(b>0) | |
688 part.write(byteBoundary,0,b); | |
689 | |
690 b=-1; | |
691 part.write(c); | |
692 } | |
693 } | |
694 | |
695 // Check for incomplete boundary match, writing out the chars we matched along the way | |
696 if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1)) | |
697 { | |
698 if(cr) | |
699 part.write(13); | |
700 | |
701 if(lf) | |
702 part.write(10); | |
703 | |
704 cr=lf=false; | |
705 part.write(byteBoundary,0,b); | |
706 b=-1; | |
707 } | |
708 | |
709 // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part. | |
710 if(b>0||c==-1) | |
711 { | |
712 | |
713 if(b==byteBoundary.length) | |
714 lastPart=true; | |
715 if(state==10) | |
716 state=-2; | |
717 break; | |
718 } | |
719 | |
720 // handle CR LF | |
721 if(cr) | |
722 part.write(13); | |
723 | |
724 if(lf) | |
725 part.write(10); | |
726 | |
727 cr=(c==13); | |
728 lf=(c==10||state==10); | |
729 if(state==10) | |
730 state=-2; | |
731 } | |
732 } | |
733 finally | |
734 { | |
735 part.close(); | |
736 } | |
737 } | |
738 if (!lastPart) | |
739 throw new IOException("Incomplete parts"); | |
740 } | |
741 | |
742 public void setDeleteOnExit(boolean deleteOnExit) | |
743 { | |
744 _deleteOnExit = deleteOnExit; | |
745 } | |
746 | |
747 | |
748 public boolean isDeleteOnExit() | |
749 { | |
750 return _deleteOnExit; | |
751 } | |
752 | |
753 | |
754 /* ------------------------------------------------------------ */ | |
755 private String value(String nameEqualsValue, boolean splitAfterSpace) | |
756 { | |
757 /* | |
758 String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim(); | |
759 int i=value.indexOf(';'); | |
760 if(i>0) | |
761 value=value.substring(0,i); | |
762 if(value.startsWith("\"")) | |
763 { | |
764 value=value.substring(1,value.indexOf('"',1)); | |
765 } | |
766 else if (splitAfterSpace) | |
767 { | |
768 i=value.indexOf(' '); | |
769 if(i>0) | |
770 value=value.substring(0,i); | |
771 } | |
772 return value; | |
773 */ | |
774 int idx = nameEqualsValue.indexOf('='); | |
775 String value = nameEqualsValue.substring(idx+1).trim(); | |
776 return QuotedStringTokenizer.unquoteOnly(value); | |
777 } | |
778 | |
779 | |
780 /* ------------------------------------------------------------ */ | |
781 private String filenameValue(String nameEqualsValue) | |
782 { | |
783 int idx = nameEqualsValue.indexOf('='); | |
784 String value = nameEqualsValue.substring(idx+1).trim(); | |
785 | |
786 if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*")) | |
787 { | |
788 //incorrectly escaped IE filenames that have the whole path | |
789 //we just strip any leading & trailing quotes and leave it as is | |
790 char first=value.charAt(0); | |
791 if (first=='"' || first=='\'') | |
792 value=value.substring(1); | |
793 char last=value.charAt(value.length()-1); | |
794 if (last=='"' || last=='\'') | |
795 value = value.substring(0,value.length()-1); | |
796 | |
797 return value; | |
798 } | |
799 else | |
800 //unquote the string, but allow any backslashes that don't | |
801 //form a valid escape sequence to remain as many browsers | |
802 //even on *nix systems will not escape a filename containing | |
803 //backslashes | |
804 return QuotedStringTokenizer.unquoteOnly(value, true); | |
805 } | |
806 | |
807 private static class Base64InputStream extends InputStream | |
808 { | |
809 ReadLineInputStream _in; | |
810 String _line; | |
811 byte[] _buffer; | |
812 int _pos; | |
813 | |
814 | |
815 public Base64InputStream(ReadLineInputStream rlis) | |
816 { | |
817 _in = rlis; | |
818 } | |
819 | |
820 @Override | |
821 public int read() throws IOException | |
822 { | |
823 if (_buffer==null || _pos>= _buffer.length) | |
824 { | |
825 //Any CR and LF will be consumed by the readLine() call. | |
826 //We need to put them back into the bytes returned from this | |
827 //method because the parsing of the multipart content uses them | |
828 //as markers to determine when we've reached the end of a part. | |
829 _line = _in.readLine(); | |
830 if (_line==null) | |
831 return -1; //nothing left | |
832 if (_line.startsWith("--")) | |
833 _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part | |
834 else if (_line.length()==0) | |
835 _buffer="\r\n".getBytes(); //blank line | |
836 else | |
837 { | |
838 ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2); | |
839 B64Code.decode(_line, baos); | |
840 baos.write(13); | |
841 baos.write(10); | |
842 _buffer = baos.toByteArray(); | |
843 } | |
844 | |
845 _pos=0; | |
846 } | |
847 | |
848 return _buffer[_pos++]; | |
849 } | |
850 } | |
851 } |