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