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