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