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