1 /**
2 *
3 * Copyright 2003-2006 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.geronimo.mail.util;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.FilterInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.UnsupportedEncodingException;
25
26 /**
27 * An implementation of a FilterOutputStream that decodes the
28 * stream data in UU encoding format. This version does the
29 * decoding "on the fly" rather than decoding a single block of
30 * data. Since this version is intended for use by the MimeUtilty class,
31 * it also handles line breaks in the encoded data.
32 */
33 public class UUDecoderStream extends FilterInputStream {
34
35 protected static final int MAX_CHARS_PER_LINE = 45;
36
37
38 protected UUEncoder decoder = new UUEncoder();
39
40
41 protected byte[] decodedChars;
42
43 protected int decodedCount = 0;
44
45 protected int decodedIndex = 0;
46
47
48 protected boolean beginRead = false;
49
50
51 public UUDecoderStream(InputStream in) {
52 super(in);
53 }
54
55
56 /**
57 * Test for the existance of decoded characters in our buffer
58 * of decoded data.
59 *
60 * @return True if we currently have buffered characters.
61 */
62 private boolean dataAvailable() {
63 return decodedCount != 0;
64 }
65
66 /**
67 * Get the next buffered decoded character.
68 *
69 * @return The next decoded character in the buffer.
70 */
71 private byte getBufferedChar() {
72 decodedCount--;
73 return decodedChars[decodedIndex++];
74 }
75
76 /**
77 * Decode a requested number of bytes of data into a buffer.
78 *
79 * @return true if we were able to obtain more data, false otherwise.
80 */
81 private boolean decodeStreamData() throws IOException {
82 decodedIndex = 0;
83
84
85 return fillEncodedBuffer() != -1;
86 }
87
88
89 /**
90 * Retrieve a single byte from the decoded characters buffer.
91 *
92 * @return The decoded character or -1 if there was an EOF condition.
93 */
94 private int getByte() throws IOException {
95 if (!dataAvailable()) {
96 if (!decodeStreamData()) {
97 return -1;
98 }
99 }
100 decodedCount--;
101 return decodedChars[decodedIndex++];
102 }
103
104 private int getBytes(byte[] data, int offset, int length) throws IOException {
105
106 int readCharacters = 0;
107 while (length > 0) {
108
109 if (!dataAvailable()) {
110
111 if (!decodeStreamData()) {
112 return readCharacters > 0 ? readCharacters : -1;
113 }
114 }
115
116
117 int copyCount = Math.min(decodedCount, length);
118 System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
119 decodedIndex += copyCount;
120 decodedCount -= copyCount;
121 offset += copyCount;
122 length -= copyCount;
123 readCharacters += copyCount;
124 }
125 return readCharacters;
126 }
127
128 /**
129 * Verify that the first line of the buffer is a valid begin
130 * marker.
131 *
132 * @exception IOException
133 */
134 private void checkBegin() throws IOException {
135
136 if (beginRead) {
137 return;
138 }
139
140
141
142 while (true) {
143 String line = readLine();
144 if (line == null) {
145 throw new IOException("Missing UUEncode begin command");
146 }
147
148
149 if (line.regionMatches(true, 0, "begin ", 0, 6)) {
150
151 beginRead = true;
152 return;
153 }
154 }
155 }
156
157
158 /**
159 * Read a line of data. Returns null if there is an EOF.
160 *
161 * @return The next line read from the stream. Returns null if we
162 * hit the end of the stream.
163 * @exception IOException
164 */
165 protected String readLine() throws IOException {
166 decodedIndex = 0;
167
168 StringBuffer buffer = new StringBuffer();
169
170
171 int ch = in.read();
172 while (ch != -1) {
173
174 if (ch == '\n') {
175 break;
176 }
177
178
179 else if (ch == '\r') {
180 ;
181 }
182 else {
183
184 buffer.append((char)ch);
185 }
186 ch = in.read();
187 }
188
189
190 if (ch == -1 && buffer.length() == 0) {
191 return null;
192 }
193
194 return buffer.toString();
195 }
196
197
198 /**
199 * Fill our buffer of input characters for decoding from the
200 * stream. This will attempt read a full buffer, but will
201 * terminate on an EOF or read error. This will filter out
202 * non-Base64 encoding chars and will only return a valid
203 * multiple of 4 number of bytes.
204 *
205 * @return The count of characters read.
206 */
207 private int fillEncodedBuffer() throws IOException
208 {
209 checkBegin();
210
211 decodedIndex = 0;
212
213 while (true) {
214
215
216
217 String line = readLine();
218
219
220 if (line == null) {
221 throw new IOException("Missing end in UUEncoded data");
222 }
223
224
225 if (line.equalsIgnoreCase("end")) {
226
227 return -1;
228 }
229
230 ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_CHARS_PER_LINE);
231
232 byte [] lineBytes;
233 try {
234 lineBytes = line.getBytes("US-ASCII");
235 } catch (UnsupportedEncodingException e) {
236 throw new IOException("Invalid UUEncoding");
237 }
238
239
240 decodedCount = decoder.decode(lineBytes, 0, lineBytes.length, out);
241
242
243 if (decodedCount != 0) {
244
245 decodedChars = out.toByteArray();
246 return decodedCount;
247 }
248 }
249 }
250
251
252
253
254
255 public int read() throws IOException
256 {
257 return getByte();
258 }
259
260
261 public int read(byte [] buffer, int offset, int length) throws IOException {
262 return getBytes(buffer, offset, length);
263 }
264
265
266 public boolean markSupported() {
267 return false;
268 }
269
270
271 public int available() throws IOException {
272 return ((in.available() / 4) * 3) + decodedCount;
273 }
274 }
275