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 }