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