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 public class UUEncoderStream extends FilterOutputStream { 035 036 // default values included on the "begin" prefix of the data stream. 037 protected static final int DEFAULT_MODE = 644; 038 protected static final String DEFAULT_NAME = "encoder.buf"; 039 040 protected static final int MAX_CHARS_PER_LINE = 45; 041 042 // the configured name on the "begin" command. 043 protected String name; 044 // the configured mode for the "begin" command. 045 protected int mode; 046 047 // since this is a filtering stream, we need to wait until we have the first byte written for encoding 048 // to write out the "begin" marker. A real pain, but necessary. 049 protected boolean beginWritten = false; 050 051 052 // our encoder utility class. 053 protected UUEncoder encoder = new UUEncoder(); 054 055 // Data is generally written out in 45 character lines, so we're going to buffer that amount before 056 // asking the encoder to process this. 057 058 // the buffered byte count 059 protected int bufferedBytes = 0; 060 061 // we'll encode this part once it is filled up. 062 protected byte[] buffer = new byte[45]; 063 064 /** 065 * Create a Base64 encoder stream that wraps a specifed stream 066 * using the default line break size. 067 * 068 * @param out The wrapped output stream. 069 */ 070 public UUEncoderStream(OutputStream out) { 071 this(out, DEFAULT_NAME, DEFAULT_MODE); 072 } 073 074 075 /** 076 * Create a Base64 encoder stream that wraps a specifed stream 077 * using the default line break size. 078 * 079 * @param out The wrapped output stream. 080 * @param name The filename placed on the "begin" command. 081 */ 082 public UUEncoderStream(OutputStream out, String name) { 083 this(out, name, DEFAULT_MODE); 084 } 085 086 087 public UUEncoderStream(OutputStream out, String name, int mode) { 088 super(out); 089 // fill in the name and mode information. 090 this.name = name; 091 this.mode = mode; 092 } 093 094 095 private void checkBegin() throws IOException { 096 if (!beginWritten) { 097 // grumble...OutputStream doesn't directly support writing String data. We'll wrap this in 098 // a PrintStream() to accomplish the task of writing the begin command. 099 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