Home | History | Annotate | Download | only in mime4j
      1 /****************************************************************
      2  * Licensed to the Apache Software Foundation (ASF) under one   *
      3  * or more contributor license agreements.  See the NOTICE file *
      4  * distributed with this work for additional information        *
      5  * regarding copyright ownership.  The ASF licenses this file   *
      6  * to you under the Apache License, Version 2.0 (the            *
      7  * "License"); you may not use this file except in compliance   *
      8  * with the License.  You may obtain a copy of the License at   *
      9  *                                                              *
     10  *   http://www.apache.org/licenses/LICENSE-2.0                 *
     11  *                                                              *
     12  * Unless required by applicable law or agreed to in writing,   *
     13  * software distributed under the License is distributed on an  *
     14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
     15  * KIND, either express or implied.  See the License for the    *
     16  * specific language governing permissions and limitations      *
     17  * under the License.                                           *
     18  ****************************************************************/
     19 
     20 package org.apache.james.mime4j;
     21 
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.io.PushbackInputStream;
     25 
     26 /**
     27  * Stream that constrains itself to a single MIME body part.
     28  * After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()}
     29  * can be used to determine if a final boundary has been seen or not.
     30  * If {@link #parentEOF()} is <code>true</code> an unexpected end of stream
     31  * has been detected in the parent stream.
     32  *
     33  *
     34  *
     35  * @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $
     36  */
     37 public class MimeBoundaryInputStream extends InputStream {
     38 
     39     private PushbackInputStream s = null;
     40     private byte[] boundary = null;
     41     private boolean first = true;
     42     private boolean eof = false;
     43     private boolean parenteof = false;
     44     private boolean moreParts = true;
     45 
     46     /**
     47      * Creates a new MimeBoundaryInputStream.
     48      * @param s The underlying stream.
     49      * @param boundary Boundary string (not including leading hyphens).
     50      */
     51     public MimeBoundaryInputStream(InputStream s, String boundary)
     52             throws IOException {
     53 
     54         this.s = new PushbackInputStream(s, boundary.length() + 4);
     55 
     56         boundary = "--" + boundary;
     57         this.boundary = new byte[boundary.length()];
     58         for (int i = 0; i < this.boundary.length; i++) {
     59             this.boundary[i] = (byte) boundary.charAt(i);
     60         }
     61 
     62         /*
     63          * By reading one byte we will update moreParts to be as expected
     64          * before any bytes have been read.
     65          */
     66         int b = read();
     67         if (b != -1) {
     68             this.s.unread(b);
     69         }
     70     }
     71 
     72     /**
     73      * Closes the underlying stream.
     74      *
     75      * @throws IOException on I/O errors.
     76      */
     77     public void close() throws IOException {
     78         s.close();
     79     }
     80 
     81     /**
     82      * Determines if the underlying stream has more parts (this stream has
     83      * not seen an end boundary).
     84      *
     85      * @return <code>true</code> if there are more parts in the underlying
     86      *         stream, <code>false</code> otherwise.
     87      */
     88     public boolean hasMoreParts() {
     89         return moreParts;
     90     }
     91 
     92     /**
     93      * Determines if the parent stream has reached EOF
     94      *
     95      * @return <code>true</code>  if EOF has been reached for the parent stream,
     96      *         <code>false</code> otherwise.
     97      */
     98     public boolean parentEOF() {
     99         return parenteof;
    100     }
    101 
    102     /**
    103      * Consumes all unread bytes of this stream. After a call to this method
    104      * this stream will have reached EOF.
    105      *
    106      * @throws IOException on I/O errors.
    107      */
    108     public void consume() throws IOException {
    109         while (read() != -1) {
    110         }
    111     }
    112 
    113     /**
    114      * @see java.io.InputStream#read()
    115      */
    116     public int read() throws IOException {
    117         if (eof) {
    118             return -1;
    119         }
    120 
    121         if (first) {
    122             first = false;
    123             if (matchBoundary()) {
    124                 return -1;
    125             }
    126         }
    127 
    128         int b1 = s.read();
    129         int b2 = s.read();
    130 
    131         if (b1 == '\r' && b2 == '\n') {
    132             if (matchBoundary()) {
    133                 return -1;
    134             }
    135         }
    136 
    137         if (b2 != -1) {
    138             s.unread(b2);
    139         }
    140 
    141         parenteof = b1 == -1;
    142         eof = parenteof;
    143 
    144         return b1;
    145     }
    146 
    147     private boolean matchBoundary() throws IOException {
    148 
    149         for (int i = 0; i < boundary.length; i++) {
    150             int b = s.read();
    151             if (b != boundary[i]) {
    152                 if (b != -1) {
    153                     s.unread(b);
    154                 }
    155                 for (int j = i - 1; j >= 0; j--) {
    156                     s.unread(boundary[j]);
    157                 }
    158                 return false;
    159             }
    160         }
    161 
    162         /*
    163          * We have a match. Is it an end boundary?
    164          */
    165         int prev = s.read();
    166         int curr = s.read();
    167         moreParts = !(prev == '-' && curr == '-');
    168         do {
    169             if (curr == '\n' && prev == '\r') {
    170                 break;
    171             }
    172             prev = curr;
    173         } while ((curr = s.read()) != -1);
    174 
    175         if (curr == -1) {
    176             moreParts = false;
    177             parenteof = true;
    178         }
    179 
    180         eof = true;
    181 
    182         return true;
    183     }
    184 }