1 18 19 package org.apache.struts.upload; 20 21 import java.io.InputStream ; 22 import java.io.IOException ; 23 import java.io.File ; 24 25 33 public class MultipartBoundaryInputStream extends InputStream 34 { 35 private static final byte NEWLINE_BYTE = ((byte) '\n'); 36 37 private static final byte CARRIAGE_RETURN = ((byte) '\r'); 38 39 private static final byte[] CRLF = new byte[] {CARRIAGE_RETURN, NEWLINE_BYTE}; 40 41 private static final String DOUBLE_DASH_STRING = "--"; 42 43 private static final int DEFAULT_LINE_SIZE = 4096; 44 45 private static final String TOKEN_EQUALS = "="; 46 47 private static final char TOKEN_QUOTE = '\"'; 48 49 private static final char TOKEN_COLON = ':'; 50 51 private static final char TOKEN_SEMI_COLON = ';'; 52 53 private static final char TOKEN_SPACE = ' '; 54 55 private static final String DEFAULT_CONTENT_DISPOSITION = "form-data"; 56 57 private static final String PARAMETER_NAME = "name"; 58 59 private static final String PARAMETER_FILENAME = "filename"; 60 61 private static final String PARAMETER_CHARSET = "charset"; 62 63 private static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain"; 64 65 private static final String CONTENT_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream"; 66 67 private static final String MESSAGE_INVALID_START = "Multipart data doesn't start with boundary"; 68 69 72 protected InputStream inputStream; 73 74 77 protected String boundary; 78 79 82 protected boolean boundaryEncountered; 83 84 87 protected boolean finalBoundaryEncountered; 88 89 92 protected boolean endOfStream; 93 94 97 protected String elementContentDisposition; 98 99 102 protected String elementName; 103 104 107 protected String elementContentType; 108 109 113 protected String elementFileName; 114 115 118 protected String elementCharset; 119 120 123 protected long maxLength; 124 125 128 protected boolean maxLengthMet; 129 130 133 protected long bytesRead; 134 135 private byte[] boundaryBytes; 136 137 private byte[] finalBoundaryBytes; 138 139 private byte[] line; 140 141 private int lineSize; 142 143 private int lineLength; 144 145 private boolean lineHasNewline; 146 147 private boolean lineHasCarriage; 148 149 private int lineIndex; 150 151 public MultipartBoundaryInputStream() 152 { 153 this.lineSize = DEFAULT_LINE_SIZE; 154 this.maxLength = -1; 155 resetStream(); 156 } 157 158 161 public void setBoundary(String boundary) 162 { 163 this.boundary = DOUBLE_DASH_STRING + boundary; 164 this.boundaryBytes = this.boundary.getBytes(); 165 this.finalBoundaryBytes = (this.boundary + DOUBLE_DASH_STRING).getBytes(); 166 } 167 168 171 public void resetForNextBoundary() throws IOException 172 { 173 if (!this.finalBoundaryEncountered) 174 { 175 this.boundaryEncountered = false; 176 resetCrlf(); 177 fillLine(); 178 readElementHeaders(); 179 } 180 } 181 182 188 public void setInputStream(InputStream stream) throws IOException 189 { 190 this.inputStream = stream; 191 resetStream(); 192 readFirstElement(); 193 } 194 195 198 public int read() throws IOException 199 { 200 if (!this.maxLengthMet) 201 { 202 if (!this.boundaryEncountered) 203 { 204 return readFromLine(); 205 } 206 } 207 return -1; 208 } 209 210 public int read(byte[] buffer) throws IOException 211 { 212 return read(buffer, 0, buffer.length); 213 } 214 215 public int read(byte[] buffer, int offset, int length) throws IOException 216 { 217 if (length > 0) 218 { 219 int read = read(); 220 if ((read == -1) && (this.endOfStream || this.boundaryEncountered)) 221 { 222 return -1; 223 } 224 int bytesRead = 1; 225 buffer[offset++] = (byte) read; 226 227 while ((bytesRead < length) && (((read = read())!= -1) || ((read == -1) && 228 (!this.boundaryEncountered))) && !this.maxLengthMet) 229 { 230 buffer[offset++] = (byte) read; 231 bytesRead++; 232 } 233 return bytesRead; 234 } 235 return -1; 236 } 237 238 241 public synchronized void mark(int i) 242 { 243 this.inputStream.mark(i); 244 } 245 246 249 public synchronized void reset() throws IOException 250 { 251 this.inputStream.reset(); 252 } 253 254 257 public void setMaxLength(long maxLength) 258 { 259 this.maxLength = maxLength; 260 } 261 262 public long getMaxLength() 263 { 264 return maxLength; 265 } 266 267 270 public boolean isMaxLengthMet() 271 { 272 return maxLengthMet; 273 } 274 275 279 public String getElementContentDisposition() 280 { 281 return this.elementContentDisposition; 282 } 283 284 288 public String getElementName() 289 { 290 return this.elementName; 291 } 292 293 297 public String getElementCharset() 298 { 299 return this.elementCharset; 300 } 301 302 307 public String getElementContentType() 308 { 309 return this.elementContentType; 310 } 311 312 316 public String getElementFileName() 317 { 318 return this.elementFileName; 319 } 320 321 324 public boolean isElementFile() 325 { 326 return (this.elementFileName != null); 327 } 328 329 332 public boolean isBoundaryEncountered() 333 { 334 return this.boundaryEncountered; 335 } 336 337 340 public boolean isFinalBoundaryEncountered() 341 { 342 return this.finalBoundaryEncountered; 343 } 344 345 348 public boolean isEndOfStream() 349 { 350 return this.endOfStream; 351 } 352 353 public void setLineSize(int size) 354 { 355 this.lineSize = size; 356 } 357 358 public long getBytesRead() 359 { 360 return this.bytesRead; 361 } 362 363 private final void readFirstElement() throws IOException 364 { 365 fillLine(); 366 if (!this.boundaryEncountered) 367 { 368 throw new IOException (MESSAGE_INVALID_START); 369 } 370 fillLine(); 371 readElementHeaders(); 372 } 373 374 private final void readElementHeaders() throws IOException 375 { 376 readContentDisposition(); 377 resetCrlf(); 378 boolean hadContentType = readContentType(); 379 resetCrlf(); 380 if (hadContentType) 381 { 382 skipCurrentLineIfBlank(); 383 } 384 } 385 386 private final void readContentDisposition() throws IOException 387 { 388 String line = readLine(); 389 if (line != null) 390 { 391 int colonIndex = line.indexOf(TOKEN_COLON); 392 if (colonIndex != -1) 393 { 394 int firstSemiColonIndex = line.indexOf(TOKEN_SEMI_COLON); 395 if (firstSemiColonIndex != -1) 396 { 397 this.elementContentDisposition = line.substring(colonIndex+1, firstSemiColonIndex).trim(); 398 } 399 } 400 else 401 { 402 this.elementContentDisposition = DEFAULT_CONTENT_DISPOSITION; 403 } 404 this.elementName = parseForParameter(PARAMETER_NAME, line); 405 this.elementFileName = parseForParameter(PARAMETER_FILENAME, line); 406 if (this.elementFileName != null) 408 { 409 this.elementFileName = checkAndFixFilename(this.elementFileName); 410 } 411 } 412 } 413 414 private final String checkAndFixFilename(String filename) 415 { 416 filename = new File (filename).getName(); 417 418 int colonIndex = filename.indexOf(":"); 422 if (colonIndex == -1) { 423 colonIndex = filename.indexOf("\\\\"); 425 } 426 int slashIndex = filename.lastIndexOf("\\"); 427 428 if ((colonIndex > -1) && (slashIndex > -1)) { 429 filename = filename.substring(slashIndex+1, filename.length()); 433 } 434 return filename; 435 } 436 437 private final String parseForParameter(String parameter, String parseString) 438 { 439 int nameIndex = parseString.indexOf(parameter + TOKEN_EQUALS); 440 if (nameIndex != -1) 441 { 442 nameIndex += parameter.length() + 1; 443 int startIndex = -1; 444 int endIndex = -1; 445 if (parseString.charAt(nameIndex) == TOKEN_QUOTE) 446 { 447 startIndex = nameIndex + 1; 448 int endQuoteIndex = parseString.indexOf(TOKEN_QUOTE, startIndex); 449 if (endQuoteIndex != -1) 450 { 451 endIndex = endQuoteIndex; 452 } 453 } 454 else 455 { 456 startIndex = nameIndex; 457 int spaceIndex = parseString.indexOf(TOKEN_SPACE, startIndex); 458 if (spaceIndex != -1) 459 { 460 endIndex = spaceIndex; 461 } 462 else 463 { 464 int carriageIndex = parseString.indexOf(CARRIAGE_RETURN, startIndex); 465 if (carriageIndex != -1) 466 { 467 endIndex = carriageIndex; 468 } 469 else 470 { 471 endIndex = parseString.length(); 472 } 473 } 474 } 475 if ((startIndex != -1) && (endIndex != -1)) 476 { 477 return parseString.substring(startIndex, endIndex); 478 } 479 } 480 return null; 481 } 482 483 private final boolean readContentType() throws IOException 484 { 485 String line = readLine(); 486 if (line != null) 487 { 488 if (line.length() > 2) 490 { 491 this.elementContentType = parseHeaderValue(line); 492 if (this.elementContentType == null) 493 { 494 this.elementContentType = CONTENT_TYPE_APPLICATION_OCTET_STREAM; 495 } 496 this.elementCharset = parseForParameter(PARAMETER_CHARSET, line); 497 return true; 498 } 499 this.elementContentType = CONTENT_TYPE_TEXT_PLAIN; 501 } 502 return false; 503 } 504 505 private final String parseHeaderValue(String headerLine) 506 { 507 int colonIndex = headerLine.indexOf(TOKEN_COLON); 509 if (colonIndex != -1) 510 { 511 int endLineIndex; 512 int semiColonIndex = headerLine.indexOf(TOKEN_SEMI_COLON, colonIndex); 514 if (semiColonIndex != -1) 515 { 516 endLineIndex = semiColonIndex; 517 } 518 else 519 { 520 endLineIndex = headerLine.indexOf(CARRIAGE_RETURN, colonIndex); 522 } 523 if (endLineIndex == -1) 524 { 525 endLineIndex = headerLine.length(); 527 } 528 return headerLine.substring(colonIndex+1, endLineIndex).trim(); 530 } 531 return null; 532 } 533 534 private final void skipCurrentLineIfBlank() throws IOException 535 { 536 boolean fill = false; 537 if (this.lineLength == 1) 538 { 539 if (this.line[0] == NEWLINE_BYTE) 540 { 541 fill = true; 542 } 543 } 544 else if (this.lineLength == 2) 545 { 546 if (equals(this.line, 0, 2, CRLF)) 547 { 548 fill = true; 549 } 550 } 551 if (fill && !this.endOfStream) 552 { 553 fillLine(); 554 } 555 } 556 557 private final void resetCrlf() 558 { 559 this.lineHasCarriage = false; 560 this.lineHasNewline = false; 561 } 562 563 private final void resetStream() 564 { 565 this.line = new byte[this.lineSize]; 566 this.lineIndex = 0; 567 this.lineLength = 0; 568 this.lineHasCarriage = false; 569 this.lineHasNewline = false; 570 this.boundaryEncountered = false; 571 this.finalBoundaryEncountered = false; 572 this.endOfStream = false; 573 this.maxLengthMet = false; 574 this.bytesRead = 0; 575 } 576 577 private final String readLine() throws IOException 578 { 579 String line = null; 580 if (availableInLine() > 0) 581 { 582 line = new String (this.line, 0, this.lineLength); 583 if (!this.endOfStream) 584 { 585 fillLine(); 586 } 587 } 588 else 589 { 590 if (!this.endOfStream) 591 { 592 fillLine(); 593 line = readLine(); 594 } 595 } 596 return line; 597 } 598 599 private final int readFromLine() throws IOException 600 { 601 if (!this.boundaryEncountered) 602 { 603 if (availableInLine() > 0) 604 { 605 return this.line[this.lineIndex++]; 606 } 607 else 608 { 609 if (!this.endOfStream) 610 { 611 fillLine(); 612 return readFromLine(); 613 } 614 } 615 } 616 return -1; 617 } 618 619 private final int availableInLine() 620 { 621 return (this.lineLength - this.lineIndex); 622 } 623 624 private final void fillLine() throws IOException 625 { 626 resetLine(); 627 if (!this.finalBoundaryEncountered && !this.endOfStream) 628 { 629 fillLineBuffer(); 630 checkForBoundary(); 631 } 632 } 633 634 private final void resetLine() 635 { 636 this.lineIndex = 0; 637 } 638 639 private final void fillLineBuffer() throws IOException 640 { 641 int read = 0; 642 int index = 0; 643 644 if (this.lineHasCarriage) 646 { 647 this.line[index++] = CARRIAGE_RETURN; 648 this.lineHasCarriage = false; 649 } 650 if (this.lineHasNewline) 651 { 652 this.line[index++] = NEWLINE_BYTE; 653 this.lineHasNewline = false; 654 } 655 while ((index < this.line.length) && (!this.maxLengthMet)) 656 { 657 read = this.inputStream.read(); 658 byteRead(); 659 if ((read != -1) || ((read == -1) && (this.inputStream.available() > 0)) && !this.maxLengthMet) 660 { 661 this.line[index++] = (byte) read; 662 if (read == NEWLINE_BYTE) 663 { 664 this.lineHasNewline= true; 666 index--; 667 if (index > 0) 668 { 669 if (this.line[index - 1] == CARRIAGE_RETURN) 671 { 672 this.lineHasCarriage = true; 673 index--; 674 } 675 } 676 break; 677 } 678 } 679 else 680 { 681 this.endOfStream = true; 682 break; 683 } 684 } 685 this.lineLength = index; 686 } 687 688 private final void byteRead() 689 { 690 this.bytesRead++; 691 if (this.maxLength > -1) 692 { 693 if (this.bytesRead >= this.maxLength) 694 { 695 this.maxLengthMet = true; 696 this.endOfStream = true; 697 } 698 } 699 } 700 701 private final void checkForBoundary() 702 { 703 this.boundaryEncountered = false; 704 int actualLength = this.lineLength; 705 int startIndex; 706 if ((this.line[0] == CARRIAGE_RETURN) || (this.line[0] == NEWLINE_BYTE)) 707 { 708 actualLength--; 709 } 710 if (this.line[1] == NEWLINE_BYTE) 711 { 712 actualLength--; 713 } 714 startIndex = (this.lineLength - actualLength); 715 if (actualLength == this.boundaryBytes.length) 716 { 717 if (equals(this.line, startIndex, this.boundaryBytes.length, this.boundaryBytes)) 718 { 719 this.boundaryEncountered = true; 720 } 721 } 722 else if (actualLength == (this.boundaryBytes.length + 2)) 723 { 724 if (equals(this.line, startIndex, this.finalBoundaryBytes.length, this.finalBoundaryBytes)) 725 { 726 this.boundaryEncountered = true; 727 this.finalBoundaryEncountered = true; 728 this.endOfStream = true; 729 } 730 } 731 } 732 733 742 private final boolean equals(byte[] comp, int offset, int length, byte[] source) 743 { 744 if ((length != source.length) || (comp.length - offset < length)) 745 { 746 return false; 747 } 748 for (int i = 0; i < length; i++) 749 { 750 if (comp[offset+i] != source[i]) 751 { 752 return false; 753 } 754 } 755 return true; 756 } 757 758 759 760 761 762 763 764 765 766 } 767 | Popular Tags |