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.OutputStream;
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: 670717 $ $Date: 2008-06-23 15:41:19 -0400 (Mon, 23 Jun 2008) $
031     */
032    public class MIMEOutputStream extends OutputStream {
033    
034        // the wrappered output stream.
035        protected OutputStream out;
036    
037        // last character we handled...used to recongnize line breaks.
038        protected int lastWrite = -1;
039    
040        // a flag to indicate we've just processed a line break. This is used for
041        // byte stuffing purposes. This
042        // is initially true, because if the first character of the content is a
043        // period, we need to byte-stuff
044        // immediately.
045        protected boolean atLineBreak = true;
046    
047        /**
048         * Create an output stream that writes to the target output stream.
049         * 
050         * @param out
051         *            The wrapped output stream.
052         */
053        public MIMEOutputStream(OutputStream out) {
054            this.out = out;
055        }
056    
057        // in order for this to work, we only need override the single character
058        // form, as the others
059        // funnel through this one by default.
060        public void write(int ch) throws IOException {
061            // if this is a CR character, always write out a full sequence, and
062            // remember that we just did this.
063            if (ch == '\r') {
064                out.write((byte) '\r');
065                out.write((byte) '\n');
066                // we've just taken a break;
067                atLineBreak = true;
068            }
069            // if this is a new line, then we need to determine if this is a loner
070            // or part of a CRLF sequence.
071            else if (ch == '\n') {
072                // is this a lone ranger?
073                if (lastWrite != '\r') {
074                    // write the full CRLF sequence.
075                    out.write((byte) '\r');
076                    out.write((byte) '\n');
077                }
078                // regardless of whether we wrote something or not, we're still at a
079                // line break.
080                atLineBreak = true;
081            }
082            // potential byte-stuffing situation?
083            else if (ch == '.') {
084                // ok, this is a potential stuff situation. Did we just have a line
085                // break? Double up the character.
086                if (atLineBreak) {
087                    out.write('.');
088                }
089                out.write('.');
090                atLineBreak = false;
091            } else {
092                // just write this out and flip the linebreak flag.
093                out.write(ch);
094                atLineBreak = false;
095            }
096            // remember this last one for CRLF tracking purposes.
097            lastWrite = ch;
098        }
099        
100        
101        /**
102         * Force the stream to be terminated at a line break. 
103         * This is generally in preparation for the transport to 
104         * write out an end-of-data marker, which generally 
105         * needs to be preceded by a CRLF sequence. 
106         * 
107         * @exception IOException
108         */
109        public void forceTerminatingLineBreak() throws IOException {
110            if (!atLineBreak) {
111                out.write((byte) '\r');
112                out.write((byte) '\n');
113                // we've just taken a break;
114                atLineBreak = true;
115            }
116        }
117        
118        
119        /**
120         * Write out the SMTP terminator to the output stream. 
121         * This ensures that we don't write out an extra 
122         * CRLF if the data terminates with that value.  
123         * 
124         * @exception IOException
125         */
126        public void writeSMTPTerminator() throws IOException {
127            forceTerminatingLineBreak(); 
128            out.write('.'); 
129            out.write('\r'); 
130            out.write('\n'); 
131        }
132    }