1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * @(#)MimeMultipart.java 1.31 03/01/29 28 */ 29 30 31 32 package com.sun.xml.internal.messaging.saaj.packaging.mime.internet; 33 34 import java.io.*; 35 import java.util.BitSet; 36 37 import javax.activation.DataSource; 38 39 import com.sun.xml.internal.messaging.saaj.packaging.mime.*; 40 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.*; 41 42 import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream; 43 import com.sun.xml.internal.messaging.saaj.util.FinalArrayList; 44 45 /** 46 * The MimeMultipart class is an implementation of the abstract Multipart 47 * class that uses MIME conventions for the multipart data. <p> 48 * 49 * A MimeMultipart is obtained from a MimePart whose primary type 50 * is "multipart" (by invoking the part's <code>getContent()</code> method) 51 * or it can be created by a client as part of creating a new MimeMessage. <p> 52 * 53 * The default multipart subtype is "mixed". The other multipart 54 * subtypes, such as "alternative", "related", and so on, can be 55 * implemented as subclasses of MimeMultipart with additional methods 56 * to implement the additional semantics of that type of multipart 57 * content. The intent is that service providers, mail JavaBean writers 58 * and mail clients will write many such subclasses and their Command 59 * Beans, and will install them into the JavaBeans Activation 60 * Framework, so that any JavaMail implementation and its clients can 61 * transparently find and use these classes. Thus, a MIME multipart 62 * handler is treated just like any other type handler, thereby 63 * decoupling the process of providing multipart handlers from the 64 * JavaMail API. Lacking these additional MimeMultipart subclasses, 65 * all subtypes of MIME multipart data appear as MimeMultipart objects. <p> 66 * 67 * An application can directly construct a MIME multipart object of any 68 * subtype by using the <code>MimeMultipart(String subtype)</code> 69 * constructor. For example, to create a "multipart/alternative" object, 70 * use <code>new MimeMultipart("alternative")</code>. 71 * 72 */ 73 74 //TODO: cleanup the SharedInputStream handling 75 public class BMMimeMultipart extends MimeMultipart { 76 77 /* 78 * When true it indicates parsing hasnt been done at all 79 */ 80 private boolean begining = true; 81 82 int[] bcs = new int[256]; 83 int[] gss = null; 84 private static final int BUFFER_SIZE = 4096; 85 private byte[] buffer = new byte[BUFFER_SIZE]; 86 private byte[] prevBuffer = new byte[BUFFER_SIZE]; 87 private BitSet lastPartFound = new BitSet(1); 88 89 // cached inputstream which is possibly partially consumed 90 private InputStream in = null; 91 private String boundary = null; 92 // current stream position, set to -1 on EOF 93 int b = 0; 94 95 // property to indicate if lazyAttachments is ON 96 private boolean lazyAttachments = false; 97 98 /** 99 * Default constructor. An empty MimeMultipart object 100 * is created. Its content type is set to "multipart/mixed". 101 * A unique boundary string is generated and this string is 102 * setup as the "boundary" parameter for the 103 * <code>contentType</code> field. <p> 104 * 105 * MimeBodyParts may be added later. 106 */ 107 public BMMimeMultipart() { 108 super(); 109 //this("mixed"); 110 } 111 112 /** 113 * Construct a MimeMultipart object of the given subtype. 114 * A unique boundary string is generated and this string is 115 * setup as the "boundary" parameter for the 116 * <code>contentType</code> field. <p> 117 * 118 * MimeBodyParts may be added later. 119 */ 120 public BMMimeMultipart(String subtype) { 121 super(subtype); 122 /* 123 * Compute a boundary string. 124 String boundary = UniqueValue.getUniqueBoundaryValue(); 125 ContentType cType = new ContentType("multipart", subtype, null); 126 contentType.setParameter("boundary", boundary); 127 */ 128 } 129 130 /** 131 * Constructs a MimeMultipart object and its bodyparts from the 132 * given DataSource. <p> 133 * 134 * This constructor handles as a special case the situation where the 135 * given DataSource is a MultipartDataSource object. In this case, this 136 * method just invokes the superclass (i.e., Multipart) constructor 137 * that takes a MultipartDataSource object. <p> 138 * 139 * Otherwise, the DataSource is assumed to provide a MIME multipart 140 * byte stream. The <code>parsed</code> flag is set to false. When 141 * the data for the body parts are needed, the parser extracts the 142 * "boundary" parameter from the content type of this DataSource, 143 * skips the 'preamble' and reads bytes till the terminating 144 * boundary and creates MimeBodyParts for each part of the stream. 145 * 146 * @param ds DataSource, can be a MultipartDataSource 147 */ 148 public BMMimeMultipart(DataSource ds, ContentType ct) 149 throws MessagingException { 150 super(ds,ct); 151 boundary = ct.getParameter("boundary"); 152 /* 153 if (ds instanceof MultipartDataSource) { 154 // ask super to do this for us. 155 setMultipartDataSource((MultipartDataSource)ds); 156 return; 157 } 158 159 // 'ds' was not a MultipartDataSource, we have 160 // to parse this ourself. 161 parsed = false; 162 this.ds = ds; 163 if (ct==null) 164 contentType = new ContentType(ds.getContentType()); 165 else 166 contentType = ct; 167 */ 168 169 } 170 171 public InputStream initStream() throws MessagingException { 172 173 if (in == null) { 174 try { 175 in = ds.getInputStream(); 176 if (!(in instanceof ByteArrayInputStream) && 177 !(in instanceof BufferedInputStream) && 178 !(in instanceof SharedInputStream)) 179 in = new BufferedInputStream(in); 180 } catch (Exception ex) { 181 throw new MessagingException("No inputstream from datasource"); 182 } 183 184 if (!in.markSupported()) { 185 throw new MessagingException( 186 "InputStream does not support Marking"); 187 } 188 } 189 return in; 190 } 191 192 /** 193 * Parse the InputStream from our DataSource, constructing the 194 * appropriate MimeBodyParts. The <code>parsed</code> flag is 195 * set to true, and if true on entry nothing is done. This 196 * method is called by all other methods that need data for 197 * the body parts, to make sure the data has been parsed. 198 * 199 * @since JavaMail 1.2 200 */ 201 protected void parse() throws MessagingException { 202 if (parsed) 203 return; 204 205 initStream(); 206 207 SharedInputStream sin = null; 208 if (in instanceof SharedInputStream) { 209 sin = (SharedInputStream)in; 210 } 211 212 String bnd = "--" + boundary; 213 byte[] bndbytes = ASCIIUtility.getBytes(bnd); 214 try { 215 parse(in, bndbytes, sin); 216 } catch (IOException ioex) { 217 throw new MessagingException("IO Error", ioex); 218 } catch (Exception ex) { 219 throw new MessagingException("Error", ex); 220 } 221 222 parsed = true; 223 } 224 225 public boolean lastBodyPartFound() { 226 return lastPartFound.get(0); 227 } 228 229 public MimeBodyPart getNextPart( 230 InputStream stream, byte[] pattern, SharedInputStream sin) 231 throws Exception { 232 233 if (!stream.markSupported()) { 234 throw new Exception("InputStream does not support Marking"); 235 } 236 237 if (begining) { 238 compile(pattern); 239 if (!skipPreamble(stream, pattern, sin)) { 240 throw new Exception( 241 "Missing Start Boundary, or boundary does not start on a new line"); 242 } 243 begining = false; 244 } 245 246 if (lastBodyPartFound()) { 247 throw new Exception("No parts found in Multipart InputStream"); 248 } 249 250 if (sin != null) { 251 long start = sin.getPosition(); 252 b = readHeaders(stream); 253 if (b == -1) { 254 throw new Exception( 255 "End of Stream encountered while reading part headers"); 256 } 257 long[] v = new long[1]; 258 v[0] = -1; // just to ensure the code later sets it correctly 259 b = readBody(stream, pattern, v, null, sin); 260 // looks like this check has to be disabled 261 // it is allowed to have Mime Package without closing boundary 262 if (!ignoreMissingEndBoundary) { 263 if ((b == -1) && !lastBodyPartFound()) { 264 throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers"); 265 } 266 } 267 long end = v[0]; 268 MimeBodyPart mbp = createMimeBodyPart(sin.newStream(start, end)); 269 addBodyPart(mbp); 270 return mbp; 271 272 } else { 273 InternetHeaders headers = createInternetHeaders(stream); 274 ByteOutputStream baos = new ByteOutputStream(); 275 b = readBody(stream, pattern, null,baos, null); 276 // looks like this check has to be disabled 277 // in the old impl it is allowed to have Mime Package 278 // without closing boundary 279 if (!ignoreMissingEndBoundary) { 280 if ((b == -1) && !lastBodyPartFound()) { 281 throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers"); 282 } 283 } 284 MimeBodyPart mbp = createMimeBodyPart( 285 headers, baos.getBytes(), baos.getCount()); 286 addBodyPart(mbp); 287 return mbp; 288 } 289 290 } 291 292 public boolean parse( 293 InputStream stream, byte[] pattern, SharedInputStream sin) 294 throws Exception { 295 296 while (!lastPartFound.get(0) && (b != -1)) { 297 getNextPart(stream, pattern, sin); 298 } 299 return true; 300 } 301 302 private int readHeaders(InputStream is) throws Exception { 303 // if the headers are to end properly then there has to be CRLF 304 // actually we just need to mark the start and end positions 305 int b = is.read(); 306 while(b != -1) { 307 // when it is a shared input stream no need to copy 308 if (b == '\r') { 309 b = is.read(); 310 if (b == '\n') { 311 b = is.read(); 312 if (b == '\r') { 313 b = is.read(); 314 if (b == '\n') { 315 return b; 316 } else { 317 continue; 318 } 319 } else { 320 continue; 321 } 322 } else { 323 continue; 324 } 325 } 326 b = is.read(); 327 } 328 if (b == -1) { 329 throw new Exception( 330 "End of inputstream while reading Mime-Part Headers"); 331 } 332 return b; 333 } 334 335 private int readBody( 336 InputStream is, byte[] pattern, long[] posVector, 337 ByteOutputStream baos, SharedInputStream sin) 338 throws Exception { 339 if (!find(is, pattern, posVector, baos, sin)) { 340 throw new Exception( 341 "Missing boundary delimitier while reading Body Part"); 342 } 343 return b; 344 } 345 346 private boolean skipPreamble( 347 InputStream is, byte[] pattern, SharedInputStream sin) 348 throws Exception { 349 if (!find(is, pattern, sin)) { 350 return false; 351 } 352 if (lastPartFound.get(0)) { 353 throw new Exception( 354 "Found closing boundary delimiter while trying to skip preamble"); 355 } 356 return true; 357 } 358 359 360 public int readNext(InputStream is, byte[] buff, int patternLength, 361 BitSet eof, long[] posVector, SharedInputStream sin) 362 throws Exception { 363 364 int bufferLength = is.read(buffer, 0, patternLength); 365 if (bufferLength == -1) { 366 eof.flip(0); 367 } else if (bufferLength < patternLength) { 368 //repeatedly read patternLength - bufferLength 369 int temp = 0; 370 long pos = 0; 371 int i = bufferLength; 372 for (; i < patternLength; i++) { 373 if (sin != null) { 374 pos = sin.getPosition(); 375 } 376 temp = is.read(); 377 if (temp == -1) { 378 eof.flip(0); 379 if (sin != null) { 380 posVector[0] = pos; 381 } 382 break; 383 } 384 buffer[i] = (byte)temp; 385 } 386 bufferLength=i; 387 } 388 return bufferLength; 389 } 390 391 public boolean find(InputStream is, byte[] pattern, SharedInputStream sin) 392 throws Exception { 393 int i; 394 int l = pattern.length; 395 int lx = l -1; 396 int bufferLength = 0; 397 BitSet eof = new BitSet(1); 398 long[] posVector = new long[1]; 399 400 while (true) { 401 is.mark(l); 402 bufferLength = readNext(is, buffer, l, eof, posVector, sin); 403 if (eof.get(0)) { 404 // End of stream 405 return false; 406 } 407 408 /* 409 if (bufferLength < l) { 410 //is.reset(); 411 return false; 412 }*/ 413 414 for(i = lx; i >= 0; i--) { 415 if (buffer[i] != pattern[i]) { 416 break; 417 } 418 } 419 420 if (i < 0) { 421 // found the boundary, skip *LWSP-char and CRLF 422 if (!skipLWSPAndCRLF(is)) { 423 throw new Exception("Boundary does not terminate with CRLF"); 424 } 425 return true; 426 } 427 428 int s = Math.max(i + 1 - bcs[buffer[i] & 0x7f], gss[i]); 429 is.reset(); 430 is.skip(s); 431 } 432 } 433 434 public boolean find( 435 InputStream is, byte[] pattern, long[] posVector, 436 ByteOutputStream out, SharedInputStream sin) throws Exception { 437 int i; 438 int l = pattern.length; 439 int lx = l -1; 440 int bufferLength = 0; 441 int s = 0; 442 long endPos = -1; 443 byte[] tmp = null; 444 445 boolean first = true; 446 BitSet eof = new BitSet(1); 447 448 while (true) { 449 is.mark(l); 450 if (!first) { 451 tmp = prevBuffer; 452 prevBuffer = buffer; 453 buffer = tmp; 454 } 455 if (sin != null) { 456 endPos = sin.getPosition(); 457 } 458 459 bufferLength = readNext(is, buffer, l, eof, posVector, sin); 460 461 if (bufferLength == -1) { 462 // End of stream 463 // looks like it is allowed to not have a closing boundary 464 //return false; 465 //if (sin != null) { 466 // posVector[0] = endPos; 467 //} 468 b = -1; 469 if ((s == l) && (sin == null)) { 470 out.write(prevBuffer, 0, s); 471 } 472 return true; 473 } 474 475 if (bufferLength < l) { 476 if (sin != null) { 477 //endPos = sin.getPosition(); 478 //posVector[0] = endPos; 479 } else { 480 // looks like it is allowed to not have a closing boundary 481 // in the old implementation 482 out.write(buffer, 0, bufferLength); 483 } 484 // looks like it is allowed to not have a closing boundary 485 // in the old implementation 486 //return false; 487 b = -1; 488 return true; 489 } 490 491 for(i = lx; i >= 0; i--) { 492 if (buffer[i] != pattern[i]) { 493 break; 494 } 495 } 496 497 if (i < 0) { 498 if (s > 0) { 499 //looks like the earlier impl allowed just an LF 500 // so if s == 1 : it must be an LF 501 // if s == 2 : it must be a CR LF 502 if (s <= 2) { 503 //it could be "some-char\n" so write some-char 504 if (s == 2) { 505 if (prevBuffer[1] == '\n') { 506 if (prevBuffer[0] != '\r' && prevBuffer[0] != '\n') { 507 out.write(prevBuffer,0,1); 508 } 509 if (sin != null) { 510 posVector[0] = endPos; 511 } 512 513 } else { 514 throw new Exception( 515 "Boundary characters encountered in part Body " + 516 "without a preceeding CRLF"); 517 } 518 519 } else if (s==1) { 520 if (prevBuffer[0] != '\n') { 521 throw new Exception( 522 "Boundary characters encountered in part Body " + 523 "without a preceeding CRLF"); 524 }else { 525 if (sin != null) { 526 posVector[0] = endPos; 527 } 528 } 529 } 530 531 } else if (s > 2) { 532 if ((prevBuffer[s-2] == '\r') && (prevBuffer[s-1] == '\n')) { 533 if (sin != null) { 534 posVector[0] = endPos - 2; 535 } else { 536 out.write(prevBuffer, 0, s - 2); 537 } 538 } else if (prevBuffer[s-1] == '\n') { 539 //old impl allowed just a \n 540 if (sin != null) { 541 posVector[0] = endPos - 1; 542 } else { 543 out.write(prevBuffer, 0, s - 1); 544 } 545 } else { 546 throw new Exception( 547 "Boundary characters encountered in part Body " + 548 "without a preceeding CRLF"); 549 } 550 } 551 } 552 // found the boundary, skip *LWSP-char and CRLF 553 if (!skipLWSPAndCRLF(is)) { 554 //throw new Exception( 555 // "Boundary does not terminate with CRLF"); 556 } 557 return true; 558 } 559 560 if ((s > 0) && (sin == null)) { 561 if (prevBuffer[s-1] == (byte)13) { 562 // if buffer[0] == (byte)10 563 if (buffer[0] == (byte)10) { 564 int j=lx-1; 565 for(j = lx-1; j > 0; j--) { 566 if (buffer[j+1] != pattern[j]) { 567 break; 568 } 569 } 570 if (j == 0) { 571 // matched the pattern excluding the last char of the pattern 572 // so dont write the CR into stream 573 out.write(prevBuffer,0,s-1); 574 } else { 575 out.write(prevBuffer,0,s); 576 } 577 } else { 578 out.write(prevBuffer, 0, s); 579 } 580 } else { 581 out.write(prevBuffer, 0, s); 582 } 583 } 584 585 s = Math.max(i + 1 - bcs[buffer[i] & 0x7f], gss[i]); 586 is.reset(); 587 is.skip(s); 588 if (first) { 589 first = false; 590 } 591 } 592 } 593 594 private boolean skipLWSPAndCRLF(InputStream is) throws Exception { 595 596 b = is.read(); 597 //looks like old impl allowed just a \n as well 598 if (b == '\n') { 599 return true; 600 } 601 602 if (b == '\r') { 603 b = is.read(); 604 //skip any multiple '\r' "\r\n" --> "\r\r\n" on Win2k 605 if (b == '\r') { 606 b = is.read(); 607 } 608 if (b == '\n') { 609 return true; 610 } else { 611 throw new Exception( 612 "transport padding after a Mime Boundary should end in a CRLF, found CR only"); 613 } 614 } 615 616 if (b == '-') { 617 b = is.read(); 618 if (b != '-') { 619 throw new Exception( 620 "Unexpected singular '-' character after Mime Boundary"); 621 } else { 622 //System.out.println("Last Part Found"); 623 lastPartFound.flip(0); 624 // read the next char 625 b = is.read(); 626 } 627 } 628 629 while ((b != -1) && ((b == ' ') || (b == '\t'))) { 630 b = is.read(); 631 if (b == '\n') { 632 return true; 633 } 634 if (b == '\r') { 635 b = is.read(); 636 //skip any multiple '\r': "\r\n" --> "\r\r\n" on Win2k 637 if (b == '\r') { 638 b = is.read(); 639 } 640 if (b == '\n') { 641 return true; 642 } 643 } 644 } 645 646 if (b == -1) { 647 // the last boundary need not have CRLF 648 if (!lastPartFound.get(0)) { 649 throw new Exception( 650 "End of Multipart Stream before encountering closing boundary delimiter"); 651 } 652 return true; 653 } 654 return false; 655 } 656 657 private void compile(byte[] pattern) { 658 int l = pattern.length; 659 660 int i; 661 int j; 662 663 // Copied from J2SE 1.4 regex code 664 // java.util.regex.Pattern.java 665 666 // Initialise Bad Character Shift table 667 for (i = 0; i < l; i++) { 668 bcs[pattern[i]] = i + 1; 669 } 670 671 // Initialise Good Suffix Shift table 672 gss = new int[l]; 673 NEXT: for (i = l; i > 0; i--) { 674 // j is the beginning index of suffix being considered 675 for (j = l - 1; j >= i; j--) { 676 // Testing for good suffix 677 if (pattern[j] == pattern[j - i]) { 678 // pattern[j..len] is a good suffix 679 gss[j - 1] = i; 680 } else { 681 // No match. The array has already been 682 // filled up with correct values before. 683 continue NEXT; 684 } 685 } 686 while (j > 0) { 687 gss[--j] = i; 688 } 689 } 690 gss[l - 1] = 1; 691 } 692 693 694 /** 695 * Iterates through all the parts and outputs each Mime part 696 * separated by a boundary. 697 */ 698 byte[] buf = new byte[1024]; 699 700 public void writeTo(OutputStream os) 701 throws IOException, MessagingException { 702 703 // inputStream was not null 704 if (in != null) { 705 contentType.setParameter("boundary", this.boundary); 706 } 707 708 String bnd = "--" + contentType.getParameter("boundary"); 709 for (int i = 0; i < parts.size(); i++) { 710 OutputUtil.writeln(bnd, os); // put out boundary 711 ((MimeBodyPart)parts.get(i)).writeTo(os); 712 OutputUtil.writeln(os); // put out empty line 713 } 714 715 if (in != null) { 716 OutputUtil.writeln(bnd, os); // put out boundary 717 if ((os instanceof ByteOutputStream) && lazyAttachments) { 718 ((ByteOutputStream)os).write(in); 719 } else { 720 ByteOutputStream baos = new ByteOutputStream(in.available()); 721 baos.write(in); 722 baos.writeTo(os); 723 // reset the inputstream so that we can support a 724 //getAttachment later 725 in = baos.newInputStream(); 726 } 727 728 // this will endup writing the end boundary 729 } else { 730 // put out last boundary 731 OutputUtil.writeAsAscii(bnd, os); 732 OutputUtil.writeAsAscii("--", os); 733 } 734 } 735 736 public void setInputStream(InputStream is) { 737 this.in = is; 738 } 739 740 public InputStream getInputStream() { 741 return this.in; 742 } 743 744 public void setBoundary(String bnd) { 745 this.boundary = bnd; 746 if (this.contentType != null) { 747 this.contentType.setParameter("boundary", bnd); 748 } 749 } 750 public String getBoundary() { 751 return this.boundary; 752 } 753 754 public boolean isEndOfStream() { 755 return (b == -1); 756 } 757 758 public void setLazyAttachments(boolean flag) { 759 lazyAttachments = flag; 760 } 761 762 }