comparison src/org/eclipse/jetty/util/MultiPartInputStream.java @ 825:7fb7c1915788

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