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 }