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