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