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 }