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