1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.geronimo.mail.util; 21 22 import java.io.FilterOutputStream; 23 import java.io.IOException; 24 import java.io.OutputStream; 25 import java.io.PrintStream; 26 27 /** 28 * An implementation of a FilterOutputStream that encodes the 29 * stream data in UUencoding format. This version does the 30 * encoding "on the fly" rather than encoding a single block of 31 * data. Since this version is intended for use by the MimeUtilty class, 32 * it also handles line breaks in the encoded data. 33 */ 34 public class UUEncoderStream extends FilterOutputStream { 35 36 // default values included on the "begin" prefix of the data stream. 37 protected static final int DEFAULT_MODE = 644; 38 protected static final String DEFAULT_NAME = "encoder.buf"; 39 40 protected static final int MAX_CHARS_PER_LINE = 45; 41 42 // the configured name on the "begin" command. 43 protected String name; 44 // the configured mode for the "begin" command. 45 protected int mode; 46 47 // since this is a filtering stream, we need to wait until we have the first byte written for encoding 48 // to write out the "begin" marker. A real pain, but necessary. 49 protected boolean beginWritten = false; 50 51 52 // our encoder utility class. 53 protected UUEncoder encoder = new UUEncoder(); 54 55 // Data is generally written out in 45 character lines, so we're going to buffer that amount before 56 // asking the encoder to process this. 57 58 // the buffered byte count 59 protected int bufferedBytes = 0; 60 61 // we'll encode this part once it is filled up. 62 protected byte[] buffer = new byte[45]; 63 64 /** 65 * Create a Base64 encoder stream that wraps a specifed stream 66 * using the default line break size. 67 * 68 * @param out The wrapped output stream. 69 */ 70 public UUEncoderStream(OutputStream out) { 71 this(out, DEFAULT_NAME, DEFAULT_MODE); 72 } 73 74 75 /** 76 * Create a Base64 encoder stream that wraps a specifed stream 77 * using the default line break size. 78 * 79 * @param out The wrapped output stream. 80 * @param name The filename placed on the "begin" command. 81 */ 82 public UUEncoderStream(OutputStream out, String name) { 83 this(out, name, DEFAULT_MODE); 84 } 85 86 87 public UUEncoderStream(OutputStream out, String name, int mode) { 88 super(out); 89 // fill in the name and mode information. 90 this.name = name; 91 this.mode = mode; 92 } 93 94 95 private void checkBegin() throws IOException { 96 if (!beginWritten) { 97 // grumble...OutputStream doesn't directly support writing String data. We'll wrap this in 98 // a PrintStream() to accomplish the task of writing the begin command. 99 100 PrintStream writer = new PrintStream(out); 101 // write out the stream with a CRLF marker 102 writer.print("begin " + mode + " " + name + "\r\n"); 103 writer.flush(); 104 beginWritten = true; 105 } 106 } 107 108 private void writeEnd() throws IOException { 109 PrintStream writer = new PrintStream(out); 110 // write out the stream with a CRLF marker 111 writer.print("\nend\r\n"); 112 writer.flush(); 113 } 114 115 private void flushBuffer() throws IOException { 116 // make sure we've written the begin marker first 117 checkBegin(); 118 // ask the encoder to encode and write this out. 119 if (bufferedBytes != 0) { 120 encoder.encode(buffer, 0, bufferedBytes, out); 121 // reset the buffer count 122 bufferedBytes = 0; 123 } 124 } 125 126 private int bufferSpace() { 127 return MAX_CHARS_PER_LINE - bufferedBytes; 128 } 129 130 private boolean isBufferFull() { 131 return bufferedBytes >= MAX_CHARS_PER_LINE; 132 } 133 134 135 // in order for this to work, we need to override the 3 different signatures for write 136 137 public void write(int ch) throws IOException { 138 // store this in the buffer. 139 buffer[bufferedBytes++] = (byte)ch; 140 141 // if we filled this up, time to encode and write to the output stream. 142 if (isBufferFull()) { 143 flushBuffer(); 144 } 145 } 146 147 public void write(byte [] data) throws IOException { 148 write(data, 0, data.length); 149 } 150 151 public void write(byte [] data, int offset, int length) throws IOException { 152 // first check to see how much space we have left in the buffer, and copy that over 153 int copyBytes = Math.min(bufferSpace(), length); 154 155 System.arraycopy(buffer, bufferedBytes, data, offset, copyBytes); 156 bufferedBytes += copyBytes; 157 offset += copyBytes; 158 length -= copyBytes; 159 160 // if we filled this up, time to encode and write to the output stream. 161 if (isBufferFull()) { 162 flushBuffer(); 163 } 164 165 // we've flushed the leading part up to the line break. Now if we have complete lines 166 // of data left, we can have the encoder process all of these lines directly. 167 if (length >= MAX_CHARS_PER_LINE) { 168 int fullLinesLength = (length / MAX_CHARS_PER_LINE) * MAX_CHARS_PER_LINE; 169 // ask the encoder to encode and write this out. 170 encoder.encode(data, offset, fullLinesLength, out); 171 offset += fullLinesLength; 172 length -= fullLinesLength; 173 } 174 175 // ok, now we're down to a potential trailing bit we need to move into the 176 // buffer for later processing. 177 178 if (length > 0) { 179 System.arraycopy(buffer, 0, data, offset, length); 180 bufferedBytes += length; 181 offset += length; 182 length -= length; 183 } 184 } 185 186 public void flush() throws IOException { 187 // flush any unencoded characters we're holding. 188 flushBuffer(); 189 // write out the data end marker 190 writeEnd(); 191 // and flush the output stream too so that this data is available. 192 out.flush(); 193 } 194 195 public void close() throws IOException { 196 // flush all of the streams and close the target output stream. 197 flush(); 198 out.close(); 199 } 200 201 } 202 203