View Javadoc
1 package org.apache.turbine.util.upload; 2 3 /* ==================================================================== 4 * The Apache Software License, Version 1.1 5 * 6 * Copyright (c) 2001 The Apache Software Foundation. All rights 7 * reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in 18 * the documentation and/or other materials provided with the 19 * distribution. 20 * 21 * 3. The end-user documentation included with the redistribution, 22 * if any, must include the following acknowledgment: 23 * "This product includes software developed by the 24 * Apache Software Foundation (http://www.apache.org/)." 25 * Alternately, this acknowledgment may appear in the software itself, 26 * if and wherever such third-party acknowledgments normally appear. 27 * 28 * 4. The names "Apache" and "Apache Software Foundation" and 29 * "Apache Turbine" must not be used to endorse or promote products 30 * derived from this software without prior written permission. For 31 * written permission, please contact apache@apache.org. 32 * 33 * 5. Products derived from this software may not be called "Apache", 34 * "Apache Turbine", nor may "Apache" appear in their name, without 35 * prior written permission of the Apache Software Foundation. 36 * 37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 48 * SUCH DAMAGE. 49 * ==================================================================== 50 * 51 * This software consists of voluntary contributions made by many 52 * individuals on behalf of the Apache Software Foundation. For more 53 * information on the Apache Software Foundation, please see 54 * <http://www.apache.org/>;. 55 */ 56 57 import java.io.IOException; 58 import java.io.InputStream; 59 import java.io.OutputStream; 60 61 /*** 62 * This class can be used to process data streams conforming to MIME 63 * 'multipart' format as defined in <a 64 * href="http://rf.cs/rfc1521.html">RFC 1251</a>. Arbitrary 65 * large amouns of data in the stream can be processed under constant 66 * memory usage. 67 * 68 * <p>The format of the stream is defined in the following way:<br> 69 * 70 * <code> 71 * multipart-body := preamble 1*encapsulation close-delimiter epilogue<br> 72 * encapsulation := delimiter body CRLF<br> 73 * delimiter := "--" boundary CRLF<br> 74 * close-delimiter := "--" boudary "--"<br> 75 * preamble := <ignore><br> 76 * epilogue := <ignore><br> 77 * body := header-part CRLF body-part<br> 78 * header-part := 1*header CRLF<br> 79 * header := header-name ":" header-value<br> 80 * header-name := <printable ascii characters except ":"><br> 81 * header-value := <any ascii characters except CR & LF><br> 82 * body-data := <arbitrary data><br> 83 * </code> 84 * 85 * <p>Note that body-data can contain another mulipart entity. There 86 * is limited support for single pass processing of such nested 87 * streams. The nested stream is <strong>required</strong> to have a 88 * boundary token of the same length as the parent stream (see {@link 89 * #setBoundary(byte[])}). 90 * 91 * <p>Here is an exaple of usage of this class.<br> 92 * 93 * <pre> 94 * try { 95 * MultipartStream multipartStream = new MultipartStream(input, 96 * boundary); 97 * boolean nextPart = malitPartStream.skipPreamble(); 98 * OutputStream output; 99 * while(nextPart) { 100 * header = chunks.readHeader(); 101 * // process headers 102 * // create some output stream 103 * multipartStream.readBodyPart(output); 104 * nextPart = multipartStream.readBoundary(); 105 * } 106 * } catch(MultipartStream.MalformedStreamException e) { 107 * // the stream failed to follow required syntax 108 * } catch(IOException) { 109 * // a read or write error occurred 110 * } 111 * 112 * </pre> 113 * 114 * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a> 115 * @version $Id: MultipartStream.java,v 1.1.1.1 2001/08/16 05:10:03 jvanzyl Exp $ 116 */ 117 public class MultipartStream 118 { 119 /*** 120 * The maximum lenght of <code>header-part</code> that will be 121 * processed (10 kilobytes = 10240 bytes. 122 )*/ 123 public static final int HEADER_PART_SIZE_MAX = 10240; 124 125 /*** The stream were data is read from. */ 126 protected InputStream input; 127 128 /*** 129 * The lenght of boundary token plus leading <code>CRLF--</code>. 130 */ 131 protected int boundaryLength; 132 133 /*** 134 * The amount of data that must be kept in the buffer in order to 135 * detect delimiters reliably. 136 */ 137 protected int keepRegion; 138 139 /*** A byte sequence that partitions the stream. */ 140 protected byte[] boundary; 141 142 /*** The lenght of the buffer used for processing. */ 143 protected int bufSize; 144 145 /*** The default lenght of the buffer used for processing. */ 146 protected static final int DEFAULT_BUFSIZE = 4096; 147 148 /*** The buffer used for processing. */ 149 protected byte[] buffer; 150 151 /*** 152 * The index of first valid character in the buffer. 153 * 154 * 0 <= head < bufSize 155 */ 156 protected int head; 157 158 /*** 159 * The index of last valid characer in the buffer + 1. 160 * 161 * 0 <= tail <= bufSize 162 */ 163 protected int tail; 164 165 /*** 166 * A byte sequence that marks the end of <code>header-part</code> 167 * (<code>CRLFCRLF</code>). 168 */ 169 protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A}; 170 171 /*** 172 * A byte sequence that that follows a delimiter that will be 173 * followed by an encapsulation (<code>CRLF</code>). 174 */ 175 protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A }; 176 177 /*** 178 * A byte sequence that that follows a delimiter of the last 179 * encapsulation in the stream (<code>--</code>). 180 */ 181 protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D }; 182 183 184 /*** 185 * Constructs a MultipartStream with a custom size buffer. 186 * 187 * <p>Note that the buffer must be at least big enough to contain 188 * the boundary string, plus 4 characters for CR/LF and double 189 * dash, plus at least one byte of data. Too small buffer size 190 * setting will degrade performance. 191 * 192 * @param input The <code>InputStream</code> to serve as a data 193 * source. 194 * @param boundary The token used for dividing the stream into 195 * <code>encapsulations</code>. 196 * @param bufSize The size of the buffer to be used in bytes. 197 * @exception MalformedStreamException. 198 * @exception IOException. 199 */ 200 public MultipartStream( InputStream input, 201 byte[] boundary, 202 int bufSize ) 203 throws MalformedStreamException, 204 IOException 205 { 206 this.input = input; 207 this.bufSize = bufSize; 208 this.buffer = new byte[bufSize]; 209 210 // We prepend CR/LF to the boundary to chop trailng CR/LF from 211 // body-data tokens. 212 this.boundary = new byte[boundary.length+4]; 213 this.boundaryLength = boundary.length+4; 214 this.keepRegion = boundary.length+3; 215 this.boundary[0] = 0x0D; 216 this.boundary[1] = 0x0A; 217 this.boundary[2] = 0x2D; 218 this.boundary[3] = 0x2D; 219 System.arraycopy(boundary, 0, this.boundary, 4, boundary.length); 220 221 head = 0; 222 tail = 0; 223 } 224 225 /*** 226 * Constructs a MultipartStream with a defalut size buffer. 227 * 228 * @param input The <code>InputStream</code> to serve as a data 229 * source. 230 * @param boundary The token used for dividing the stream into 231 * <code>encapsulations</code>. 232 * @exception IOException. 233 */ 234 public MultipartStream( InputStream input, 235 byte[] boundary ) 236 throws IOException 237 { 238 this(input, boundary, DEFAULT_BUFSIZE); 239 } 240 241 /*** 242 * Reads a byte from the <code>buffer</code>, and refills it as 243 * neccessary. 244 * 245 * @return Next byte from the input stream. 246 * @exception IOException, if there isn't any more data available. 247 */ 248 public byte readByte() 249 throws IOException 250 { 251 // Buffer depleted ? 252 if(head == tail) 253 { 254 head = 0; 255 // Refill. 256 tail = input.read(buffer, head, bufSize); 257 if(tail == -1) 258 { 259 // No more data available. 260 throw new IOException("No more data is available"); 261 } 262 } 263 return buffer[head++]; 264 } 265 266 /*** 267 * Skips a <code>boundary</code> token, and checks wether more 268 * <code>encapsulations</code> are contained in the stream. 269 * 270 * @return <code>True</code> if there are more encapsulations in 271 * this stream. 272 * @exception MalformedStreamException if the stream ends 273 * unexpecetedly or fails to follow required syntax. 274 */ 275 public boolean readBoundary() 276 throws MalformedStreamException 277 { 278 byte[] marker = new byte[2]; 279 boolean nextChunk = false; 280 281 head += boundaryLength; 282 try 283 { 284 marker[0] = readByte(); 285 marker[1] = readByte(); 286 if (arrayequals(marker, STREAM_TERMINATOR, 2)) 287 { 288 nextChunk = false; 289 } 290 else if(arrayequals(marker, FIELD_SEPARATOR, 2)) 291 { 292 nextChunk = true; 293 } 294 else 295 { 296 throw new MalformedStreamException("Unexpected characters follow a boundary"); 297 } 298 } 299 catch(IOException e) 300 { 301 throw new MalformedStreamException("Stream ended unexpectedly"); 302 } 303 return nextChunk; 304 } 305 306 /*** 307 * Changes the boundary token used for partitioning the stream. 308 * 309 * <p>This method allows single pass processing of nested 310 * multipart streams. 311 * 312 * <p>The boundary token of the nested stream is 313 * <code>required</code> to be of the same length as the boundary 314 * token in parent stream. 315 * 316 * <p>Restoring parent stream boundary token after processing of a 317 * nested stream is left ot the application. <br> 318 * 319 * @param boundary A boundary to be used for parsing of the nested 320 * stream. 321 * @exception IllegalBoundaryException, if <code>boundary</code> 322 * has diffrent lenght than the one being currently in use. 323 */ 324 public void setBoundary( byte[] boundary ) 325 throws IllegalBoundaryException 326 { 327 if (boundary.length != boundaryLength-4) 328 { 329 throw new IllegalBoundaryException("The length of a boundary token can not be changed"); 330 } 331 System.arraycopy(boundary, 0, this.boundary, 4, boundary.length); 332 } 333 334 /*** 335 * <p>Reads <code>header-part</code> of the current 336 * <code>encapsulation</code> 337 * 338 * <p>Headers are returned verbatim to the input stream, including 339 * traling <code>CRLF</code> marker. Parsing is left to the 340 * application. 341 * 342 * <p><strong>TODO</strong> allow limiting maximum header size to 343 * protect against abuse.<br> 344 * 345 * @return <code>header-part</code> of the current encapsulation. 346 * @exception MalformedStreamException, if the stream ends 347 * unexpecetedly. 348 */ 349 public String readHeaders() 350 throws MalformedStreamException 351 { 352 int i = 0; 353 byte b[] = new byte[1]; 354 StringBuffer buf = new StringBuffer(); 355 int sizeMax = HEADER_PART_SIZE_MAX; 356 int size = 0; 357 while(i<4) 358 { 359 try 360 { 361 b[0] = readByte(); 362 } 363 catch(IOException e) 364 { 365 throw new MalformedStreamException("Stream ended unexpectedly"); 366 } 367 size++; 368 if(b[0] == HEADER_SEPARATOR[i]) 369 { 370 i++; 371 } 372 else 373 { 374 i = 0; 375 } 376 if(size <= sizeMax) 377 { 378 buf.append(new String(b)); 379 } 380 } 381 return buf.toString(); 382 } 383 384 /*** 385 * Reads <code>body-data</code> from the current 386 * <code>encapsulation</code> and writes its contents into the 387 * output <code>Stream</code>. 388 * 389 * <p>Arbitrary large amouts of data can be processed by this 390 * method using a constant size buffer. (see {@link 391 * #MultipartStream(InputStream,byte[],int) constructor}). 392 * 393 * @param output The <code>Stream</code> to write data into. 394 * @return the amount of data written. 395 * @exception MalformedStreamException 396 * @exception IOException 397 */ 398 public int readBodyData( OutputStream output ) 399 throws MalformedStreamException, 400 IOException 401 { 402 boolean done = false; 403 int pad; 404 int pos; 405 int bytesRead; 406 int total = 0; 407 while(!done) 408 { 409 // Is boundary token present somewere in the buffer? 410 pos = findSeparator(); 411 if(pos != -1) 412 { 413 // Write the rest of the data before the boundary. 414 output.write(buffer, head, pos-head); 415 total += pos-head; 416 head = pos; 417 done = true; 418 } 419 else 420 { 421 // Determine how much data should be kept in the 422 // buffer. 423 if(tail-head>keepRegion) 424 { 425 pad = keepRegion; 426 } 427 else 428 { 429 pad = tail-head; 430 } 431 // Write out the data belonging to the body-data. 432 output.write(buffer, head, tail-head-pad); 433 434 // Move the data to the beging of the buffer. 435 total += tail-head-pad; 436 System.arraycopy(buffer, tail-pad, buffer, 0, pad); 437 438 // Refill buffer with new data. 439 head = 0; 440 bytesRead = input.read(buffer, pad, bufSize-pad); 441 442 // [pprrrrrrr] 443 if(bytesRead != -1) 444 { 445 tail = pad+bytesRead; 446 } 447 else 448 { 449 // The last pad amount is left in the buffer. 450 // Boundary can't be in there so write out the 451 // data you have and signal an error condition. 452 output.write(buffer,0,pad); 453 output.flush(); 454 total += pad; 455 throw new MalformedStreamException("Stream ended unexpectedly"); 456 } 457 } 458 } 459 output.flush(); 460 return total; 461 } 462 463 /*** 464 * Reads <code>body-data</code> from the current 465 * <code>encapsulation</code> and discards it. 466 * 467 * <p>Use this method to skip encapsulations you don't need or 468 * don't understand. 469 * 470 * @return The amount of data discarded. 471 * @exception MalformedStreamException 472 * @exception IOException 473 */ 474 public int discardBodyData() 475 throws MalformedStreamException, 476 IOException 477 { 478 boolean done = false; 479 int pad; 480 int pos; 481 int bytesRead; 482 int total = 0; 483 while(!done) 484 { 485 // Is boundary token present somewere in the buffer? 486 pos = findSeparator(); 487 if(pos != -1) 488 { 489 // Write the rest of the data before the boundary. 490 total += pos-head; 491 head = pos; 492 done = true; 493 } 494 else 495 { 496 // Determine how much data should be kept in the 497 // buffer. 498 if(tail-head>keepRegion) 499 { 500 pad = keepRegion; 501 } 502 else 503 { 504 pad = tail-head; 505 } 506 total += tail-head-pad; 507 508 // Move the data to the beging of the buffer. 509 System.arraycopy(buffer, tail-pad, buffer, 0, pad); 510 511 // Refill buffer with new data. 512 head = 0; 513 bytesRead = input.read(buffer, pad, bufSize-pad); 514 515 // [pprrrrrrr] 516 if(bytesRead != -1) 517 { 518 tail = pad+bytesRead; 519 } 520 else 521 { 522 // The last pad amount is left in the buffer. 523 // Boundary can't be in there so signal an error 524 // condition. 525 total += pad; 526 throw new MalformedStreamException("Stream ended unexpectedly"); 527 } 528 } 529 } 530 return total; 531 } 532 533 /*** 534 * Finds the beginning of the first <code>encapsulation</code>. 535 * 536 * @return <code>True</code> if an <code>encapsulation</code> was 537 * found in the stream. 538 * @exception IOException 539 */ 540 public boolean skipPreamble() 541 throws IOException 542 { 543 // First delimiter may be not preceeded with a CRLF. 544 System.arraycopy(boundary, 2, boundary, 0, boundary.length-2); 545 boundaryLength = boundary.length-2; 546 try 547 { 548 // Discard all data up to the delimiter. 549 discardBodyData(); 550 551 // Read boundary - if succeded, the stream contains an 552 // encapsulation. 553 return readBoundary(); 554 } 555 catch(MalformedStreamException e) 556 { 557 return false; 558 } 559 finally 560 { 561 // Restore delimiter. 562 System.arraycopy(boundary,0, boundary, 2, boundary.length-2); 563 boundaryLength = boundary.length; 564 boundary[0] = 0x0D; 565 boundary[1] = 0x0A; 566 } 567 } 568 569 /*** 570 * Compares <code>count</code> first bytes in the arrays 571 * <code>a</code> and <code>b</code>. 572 * 573 * @param a The first array to compare. 574 * @param b The second array to compare. 575 * @param count How many bytes should be compared. 576 * @return <code>true</code> if <code>count</code> first bytes in 577 * arrays <code>a</code> and <code>b</code> are equal. 578 */ 579 public static boolean arrayequals( byte[] a, 580 byte[] b, 581 int count ) 582 { 583 for(int i=0; i<count; i++) 584 { 585 if(a[i] != b[i]) 586 { 587 return false; 588 } 589 } 590 return true; 591 } 592 593 /*** 594 * Searches a byte of specified value in the <code>buffer</code> 595 * starting at specified <code>position</code>. 596 * 597 * @param value the value to find. 598 * @param pos The starting position for searching. 599 * @return The position of byte found, counting from beginning of 600 * the <code>buffer</code>, or <code>-1</code> if not found. 601 */ 602 protected int findByte(byte value, 603 int pos) 604 { 605 for (int i = pos; i < tail; i++) 606 if(buffer[i] == value) 607 return i; 608 609 return -1; 610 } 611 612 /*** 613 * Searches the <code>boundary</code> in <code>buffer</code> 614 * region delimited by <code>head</code> and <code>tail</code>. 615 * 616 * @return The position of the boundary found, counting from 617 * beginning of the <code>buffer</code>, or <code>-1</code> if not 618 * found. 619 */ 620 protected int findSeparator() 621 { 622 int first; 623 int match = 0; 624 int maxpos = tail - boundaryLength; 625 for(first = head; 626 (first <= maxpos) && (match != boundaryLength); 627 first++) 628 { 629 first = findByte(boundary[0], first); 630 if(first == -1 || (first > maxpos)) 631 return -1; 632 for(match = 1; match<boundaryLength; match++) 633 { 634 if(buffer[first+match] != boundary[match]) 635 break; 636 } 637 } 638 if(match == boundaryLength) 639 { 640 return first-1; 641 } 642 return -1; 643 } 644 645 646 /*** 647 * Thrown to indicate that the input stream fails to follow the 648 * required syntax. 649 */ 650 public class MalformedStreamException 651 extends IOException 652 { 653 /*** 654 * Constructs a <code>MalformedStreamException</code> with no 655 * detail message. 656 */ 657 public MalformedStreamException() 658 { 659 super(); 660 } 661 662 /*** 663 * Constructs an <code>MalformedStreamException</code> with 664 * the specified detail message. 665 * 666 * @param message The detail message. 667 */ 668 public MalformedStreamException(String message) 669 { 670 super(message); 671 } 672 } 673 674 /*** 675 * Thrown upon attempt of setting an invalid boundary token. 676 */ 677 public class IllegalBoundaryException 678 extends IOException 679 { 680 /*** 681 * Constructs an <code>IllegalBoundaryException</code> with no 682 * detail message. 683 */ 684 public IllegalBoundaryException() 685 { 686 super(); 687 } 688 689 /*** 690 * Constructs an <code>IllegalBoundaryException</code> with 691 * the specified detail message. 692 * 693 * @param message The detail message. 694 */ 695 public IllegalBoundaryException(String message) 696 { 697 super(message); 698 } 699 } 700 701 /*------------------------------------------------------------- 702 703 // These are the methods that were used to debug this stuff. 704 705 // Dump data. 706 protected void dump() 707 { 708 System.out.println("01234567890"); 709 byte[] temp = new byte[buffer.length]; 710 for(int i=0; i<buffer.length; i++) 711 { 712 if(buffer[i] == 0x0D || buffer[i] == 0x0A) 713 { 714 temp[i] = 0x21; 715 } 716 else 717 { 718 temp[i] = buffer[i]; 719 } 720 } 721 System.out.println(new String(temp)); 722 int i; 723 for(i=0; i<head; i++) 724 System.out.print(" "); 725 System.out.println("h"); 726 for(i=0; i<tail; i++) 727 System.out.print(" "); 728 System.out.println("t"); 729 System.out.flush(); 730 } 731 732 // Main routine, for testing purposes only. 733 // 734 // @param args A String[] with the command line arguments. 735 // @exception Exception, a generic exception. 736 public static void main( String[] args ) 737 throws Exception 738 { 739 File boundaryFile = new File("boundary.dat"); 740 int boundarySize = (int)boundaryFile.length(); 741 byte[] boundary = new byte[boundarySize]; 742 FileInputStream input = new FileInputStream(boundaryFile); 743 input.read(boundary,0,boundarySize); 744 745 input = new FileInputStream("multipart.dat"); 746 MultipartStream chunks = new MultipartStream(input, boundary); 747 748 int i = 0; 749 String header; 750 OutputStream output; 751 boolean nextChunk = chunks.skipPreamble(); 752 while(nextChunk) 753 { 754 header = chunks.readHeaders(); 755 System.out.println("!"+header+"!"); 756 System.out.println("wrote part"+i+".dat"); 757 output = new FileOutputStream("part"+(i++)+".dat"); 758 chunks.readBodyData(output); 759 nextChunk = chunks.readBoundary(); 760 } 761 } 762 763 */ 764 }

This page was automatically generated by Maven