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 }