001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.javamail.util;
021    
022    import java.io.IOException;
023    import java.io.Reader; 
024    
025    /**
026     * An implementation of an OutputStream that performs MIME linebreak
027     * canonicalization and "byte-stuff" so that data content does not get mistaken
028     * for a message data-end marker (CRLF.CRLF)l
029     * 
030     * @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
031     */
032    public class MIMEInputReader extends Reader {
033    
034        // the wrappered output stream.
035        protected Reader source;
036    
037        // a flag to indicate we've just processed a line break. This is used for
038        // byte stuffing purposes. This
039        // is initially true, because if the first character of the content is a
040        // period, we need to byte-stuff
041        // immediately.
042        protected boolean atLineBreak = true;
043        // we've hit the terminating marker on the data
044        protected boolean endOfData = false; 
045        
046    
047        /**
048         * Create an input reader that reads from the source input reader  
049         * 
050         * @param out
051         *            The wrapped Reader        
052         */
053        public MIMEInputReader(Reader source) {
054            this.source = source; 
055        }
056        
057        /**
058         * Concrete implementation of the Reader read() 
059         * abstract method.  This appears to be the only 
060         * abstract method, so all of the other reads must 
061         * funnel through this method. 
062         * 
063         * @param buffer The buffer to fill.
064         * @param off    The offset to start adding characters.
065         * @param len    The number of requested characters.
066         * 
067         * @return The actual count of characters read.  Returns -1 
068         *         if we hit an EOF without reading any characters.
069         * @exception IOException
070         */
071        public int read(char buffer[], int off, int len) throws IOException {
072            // we've been asked for nothing, we'll return nothing. 
073            if (len == 0) {
074                return 0; 
075            }
076            
077            // have we hit the end of data?  Return a -1 indicator
078            if (endOfData) {
079                return -1; 
080            }
081            
082            // number of bytes read 
083            int bytesRead = 0; 
084            
085            int lastRead; 
086            
087            while (bytesRead < len && (lastRead = source.read()) >= 0) {
088                // We are checking for the end of a multiline response
089                // the format is .CRLF
090                
091                // we also have to check for byte-stuffing situation 
092                // where we remove a leading period.  
093                if (atLineBreak && lastRead == '.') {
094                    // step to the next character 
095                    lastRead = source.read();
096                    // we have ".CR"...this is our end of stream 
097                    // marker.  Consume the LF from the reader and return 
098                    if (lastRead == '\r') {
099                        source.read(); 
100                        // no more reads from this point. 
101                        endOfData = true; 
102                        break; 
103                    }
104                    // the next character SHOULD be a ".".  We swallow the first 
105                    // dot and just write the next character to the buffer 
106                    atLineBreak = false; 
107                }
108                else if (lastRead == '\n') {
109                    // hit an end-of-line marker?
110                    // remember we just had a line break 
111                    atLineBreak = true; 
112                }
113                else 
114                {
115                    // something other than a line break character 
116                    atLineBreak = false; 
117                }
118                // add the character to the buffer 
119                buffer[off++] = (char)lastRead; 
120                bytesRead++; 
121            }
122            
123            // we must have had an EOF condition of some sort 
124            if (bytesRead == 0) {
125                return -1; 
126            }
127            // return the actual length read in 
128            return bytesRead; 
129        }
130        
131         /**
132          * Close the stream.  This is a NOP for this stream.  
133          * 
134          * @exception IOException
135          */
136         public void close() throws IOException {
137             // does nothing 
138         }
139    }
140