001 /** 002 * 003 * Copyright 2003-2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.mail.util; 019 020 import java.io.FilterOutputStream; 021 import java.io.IOException; 022 import java.io.OutputStream; 023 import java.io.PrintStream; 024 025 /** 026 * An implementation of a FilterOutputStream that encodes the 027 * stream data in UUencoding format. This version does the 028 * encoding "on the fly" rather than encoding a single block of 029 * data. Since this version is intended for use by the MimeUtilty class, 030 * it also handles line breaks in the encoded data. 031 */ 032 public class UUEncoderStream extends FilterOutputStream { 033 034 // default values included on the "begin" prefix of the data stream. 035 protected static final int DEFAULT_MODE = 644; 036 protected static final String DEFAULT_NAME = "encoder.buf"; 037 038 protected static final int MAX_CHARS_PER_LINE = 45; 039 040 // the configured name on the "begin" command. 041 protected String name; 042 // the configured mode for the "begin" command. 043 protected int mode; 044 045 // since this is a filtering stream, we need to wait until we have the first byte written for encoding 046 // to write out the "begin" marker. A real pain, but necessary. 047 protected boolean beginWritten = false; 048 049 050 // our encoder utility class. 051 protected UUEncoder encoder = new UUEncoder(); 052 053 // Data is generally written out in 45 character lines, so we're going to buffer that amount before 054 // asking the encoder to process this. 055 056 // the buffered byte count 057 protected int bufferedBytes = 0; 058 059 // we'll encode this part once it is filled up. 060 protected byte[] buffer = new byte[45]; 061 062 /** 063 * Create a Base64 encoder stream that wraps a specifed stream 064 * using the default line break size. 065 * 066 * @param out The wrapped output stream. 067 */ 068 public UUEncoderStream(OutputStream out) { 069 this(out, DEFAULT_NAME, DEFAULT_MODE); 070 } 071 072 073 /** 074 * Create a Base64 encoder stream that wraps a specifed stream 075 * using the default line break size. 076 * 077 * @param out The wrapped output stream. 078 * @param name The filename placed on the "begin" command. 079 */ 080 public UUEncoderStream(OutputStream out, String name) { 081 this(out, name, DEFAULT_MODE); 082 } 083 084 085 public UUEncoderStream(OutputStream out, String name, int mode) { 086 super(out); 087 // fill in the name and mode information. 088 this.name = name; 089 this.mode = mode; 090 } 091 092 093 private void checkBegin() throws IOException { 094 if (!beginWritten) { 095 // grumble...OutputStream doesn't directly support writing String data. We'll wrap this in 096 // a PrintStream() to accomplish the task of writing the begin command. 097 098 PrintStream writer = new PrintStream(out); 099 // write out the stream with a CRLF marker 100 writer.print("begin " + mode + " " + name + "\r\n"); 101 writer.flush(); 102 beginWritten = true; 103 } 104 } 105 106 private void writeEnd() throws IOException { 107 PrintStream writer = new PrintStream(out); 108 // write out the stream with a CRLF marker 109 writer.print("\nend\r\n"); 110 writer.flush(); 111 } 112 113 private void flushBuffer() throws IOException { 114 // make sure we've written the begin marker first 115 checkBegin(); 116 // ask the encoder to encode and write this out. 117 if (bufferedBytes != 0) { 118 encoder.encode(buffer, 0, bufferedBytes, out); 119 // reset the buffer count 120 bufferedBytes = 0; 121 } 122 } 123 124 private int bufferSpace() { 125 return MAX_CHARS_PER_LINE - bufferedBytes; 126 } 127 128 private boolean isBufferFull() { 129 return bufferedBytes >= MAX_CHARS_PER_LINE; 130 } 131 132 133 // in order for this to work, we need to override the 3 different signatures for write 134 135 public void write(int ch) throws IOException { 136 // store this in the buffer. 137 buffer[bufferedBytes++] = (byte)ch; 138 139 // if we filled this up, time to encode and write to the output stream. 140 if (isBufferFull()) { 141 flushBuffer(); 142 } 143 } 144 145 public void write(byte [] data) throws IOException { 146 write(data, 0, data.length); 147 } 148 149 public void write(byte [] data, int offset, int length) throws IOException { 150 // first check to see how much space we have left in the buffer, and copy that over 151 int copyBytes = Math.min(bufferSpace(), length); 152 153 System.arraycopy(buffer, bufferedBytes, data, offset, copyBytes); 154 bufferedBytes += copyBytes; 155 offset += copyBytes; 156 length -= copyBytes; 157 158 // if we filled this up, time to encode and write to the output stream. 159 if (isBufferFull()) { 160 flushBuffer(); 161 } 162 163 // we've flushed the leading part up to the line break. Now if we have complete lines 164 // of data left, we can have the encoder process all of these lines directly. 165 if (length >= MAX_CHARS_PER_LINE) { 166 int fullLinesLength = (length / MAX_CHARS_PER_LINE) * MAX_CHARS_PER_LINE; 167 // ask the encoder to encode and write this out. 168 encoder.encode(data, offset, fullLinesLength, out); 169 offset += fullLinesLength; 170 length -= fullLinesLength; 171 } 172 173 // ok, now we're down to a potential trailing bit we need to move into the 174 // buffer for later processing. 175 176 if (length > 0) { 177 System.arraycopy(buffer, 0, data, offset, length); 178 bufferedBytes += length; 179 offset += length; 180 length -= length; 181 } 182 } 183 184 public void flush() throws IOException { 185 // flush any unencoded characters we're holding. 186 flushBuffer(); 187 // write out the data end marker 188 writeEnd(); 189 // and flush the output stream too so that this data is available. 190 out.flush(); 191 } 192 193 public void close() throws IOException { 194 // flush all of the streams and close the target output stream. 195 flush(); 196 out.close(); 197 } 198 199 } 200 201