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.IOException;
23 import java.io.OutputStream;
24 import java.io.UnsupportedEncodingException;
25
26 public class UUEncoder implements Encoder {
27
28 // this is the maximum number of chars allowed per line, since we have to include a uuencoded length at
29 // the start of each line.
30 static private final int MAX_CHARS_PER_LINE = 45;
31
32
33 public UUEncoder()
34 {
35 }
36
37 /**
38 * encode the input data producing a UUEncoded output stream.
39 *
40 * @param data The array of byte data.
41 * @param off The starting offset within the data.
42 * @param length Length of the data to encode.
43 * @param out The output stream the encoded data is written to.
44 *
45 * @return the number of bytes produced.
46 */
47 public int encode(byte[] data, int off, int length, OutputStream out) throws IOException
48 {
49 int byteCount = 0;
50
51 while (true) {
52 // keep writing complete lines until we've exhausted the data.
53 if (length > MAX_CHARS_PER_LINE) {
54 // encode another line and adjust the length and position
55 byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
56 length -= MAX_CHARS_PER_LINE;
57 off += MAX_CHARS_PER_LINE;
58 }
59 else {
60 // last line. Encode the partial and quit
61 byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
62 break;
63 }
64 }
65 return byteCount;
66 }
67
68
69 /**
70 * Encode a single line of data (less than or equal to 45 characters).
71 *
72 * @param data The array of byte data.
73 * @param off The starting offset within the data.
74 * @param length Length of the data to encode.
75 * @param out The output stream the encoded data is written to.
76 *
77 * @return The number of bytes written to the output stream.
78 * @exception IOException
79 */
80 private int encodeLine(byte[] data, int offset, int length, OutputStream out) throws IOException {
81 // write out the number of characters encoded in this line.
82 out.write((byte)((length & 0x3F) + ' '));
83 byte a;
84 byte b;
85 byte c;
86
87 // count the bytes written...we add 2, one for the length and 1 for the linend terminator.
88 int bytesWritten = 2;
89
90 for (int i = 0; i < length;) {
91 // set the padding defauls
92 b = 1;
93 c = 1;
94 // get the next 3 bytes (if we have them)
95 a = data[offset + i++];
96 if (i < length) {
97 b = data[offset + i++];
98 if (i < length) {
99 c = data[offset + i++];
100 }
101 }
102
103 byte d1 = (byte)(((a >>> 2) & 0x3F) + ' ');
104 byte d2 = (byte)(((( a << 4) & 0x30) | ((b >>> 4) & 0x0F)) + ' ');
105 byte d3 = (byte)((((b << 2) & 0x3C) | ((c >>> 6) & 0x3)) + ' ');
106 byte d4 = (byte)((c & 0x3F) + ' ');
107
108 out.write(d1);
109 out.write(d2);
110 out.write(d3);
111 out.write(d4);
112
113 bytesWritten += 4;
114 }
115
116 // terminate with a linefeed alone
117 out.write('\n');
118
119 return bytesWritten;
120 }
121
122
123 /**
124 * decode the uuencoded byte data writing it to the given output stream
125 *
126 * @param data The array of byte data to decode.
127 * @param off Starting offset within the array.
128 * @param length The length of data to encode.
129 * @param out The output stream used to return the decoded data.
130 *
131 * @return the number of bytes produced.
132 * @exception IOException
133 */
134 public int decode(byte[] data, int off, int length, OutputStream out) throws IOException
135 {
136 int bytesWritten = 0;
137
138 while (length > 0) {
139 int lineOffset = off;
140
141 // scan forward looking for a EOL terminator for the next line of data.
142 while (length > 0 && data[off] != '\n') {
143 off++;
144 length--;
145 }
146
147 // go decode this line of data
148 bytesWritten += decodeLine(data, lineOffset, off - lineOffset, out);
149
150 // the offset was left pointing at the EOL character, so step over that one before
151 // scanning again.
152 off++;
153 length--;
154 }
155 return bytesWritten;
156 }
157
158
159 /**
160 * decode a single line of uuencoded byte data writing it to the given output stream
161 *
162 * @param data The array of byte data to decode.
163 * @param off Starting offset within the array.
164 * @param length The length of data to decode (length does NOT include the terminating new line).
165 * @param out The output stream used to return the decoded data.
166 *
167 * @return the number of bytes produced.
168 * @exception IOException
169 */
170 private int decodeLine(byte[] data, int off, int length, OutputStream out) throws IOException {
171 int count = data[off++];
172
173 // obtain and validate the count
174 if (count < ' ') {
175 throw new IOException("Invalid UUEncode line length");
176 }
177
178 count = (count - ' ') & 0x3F;
179
180 // get the rounded count of characters that should have been used to encode this. The + 1 is for the
181 // length encoded at the beginning
182 int requiredLength = (((count * 8) + 5) / 6) + 1;
183 if (length < requiredLength) {
184 throw new IOException("UUEncoded data and length do not match");
185 }
186
187 int bytesWritten = 0;
188 // now decode the bytes.
189 while (bytesWritten < count) {
190 // even one byte of data requires two bytes to encode, so we should have that.
191 byte a = (byte)((data[off++] - ' ') & 0x3F);
192 byte b = (byte)((data[off++] - ' ') & 0x3F);
193 byte c = 0;
194 byte d = 0;
195
196 // do the first byte
197 byte first = (byte)(((a << 2) & 0xFC) | ((b >>> 4) & 3));
198 out.write(first);
199 bytesWritten++;
200
201 // still have more bytes to decode? do the second byte of the second. That requires
202 // a third byte from the data.
203 if (bytesWritten < count) {
204 c = (byte)((data[off++] - ' ') & 0x3F);
205 byte second = (byte)(((b << 4) & 0xF0) | ((c >>> 2) & 0x0F));
206 out.write(second);
207 bytesWritten++;
208
209 // need the third one?
210 if (bytesWritten < count) {
211 d = (byte)((data[off++] - ' ') & 0x3F);
212 byte third = (byte)(((c << 6) & 0xC0) | (d & 0x3F));
213 out.write(third);
214 bytesWritten++;
215 }
216 }
217 }
218 return bytesWritten;
219 }
220
221
222 /**
223 * decode the UUEncoded String data writing it to the given output stream.
224 *
225 * @param data The String data to decode.
226 * @param out The output stream to write the decoded data to.
227 *
228 * @return the number of bytes produced.
229 * @exception IOException
230 */
231 public int decode(String data, OutputStream out) throws IOException
232 {
233 try {
234 // just get the byte data and decode.
235 byte[] bytes = data.getBytes("US-ASCII");
236 return decode(bytes, 0, bytes.length, out);
237 } catch (UnsupportedEncodingException e) {
238 throw new IOException("Invalid UUEncoding");
239 }
240 }
241 }
242
243