1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.james.mime4j.codec;
21
22 import java.io.FilterOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.util.HashSet;
26 import java.util.Set;
27
28
29
30
31
32
33
34
35
36
37 public class Base64OutputStream extends FilterOutputStream {
38
39
40 private static final int DEFAULT_LINE_LENGTH = 76;
41
42
43 private static final byte[] CRLF_SEPARATOR = { '\r', '\n' };
44
45
46
47
48 static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F',
49 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
50 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
51 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
52 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
53 '6', '7', '8', '9', '+', '/' };
54
55
56 private static final byte BASE64_PAD = '=';
57
58
59
60 private static final Set<Byte> BASE64_CHARS = new HashSet<Byte>();
61
62 static {
63 for (byte b : BASE64_TABLE) {
64 BASE64_CHARS.add(b);
65 }
66 BASE64_CHARS.add(BASE64_PAD);
67 }
68
69
70 private static final int MASK_6BITS = 0x3f;
71
72 private static final int ENCODED_BUFFER_SIZE = 2048;
73
74 private final byte[] singleByte = new byte[1];
75
76 private final int lineLength;
77 private final byte[] lineSeparator;
78
79 private boolean closed = false;
80
81 private final byte[] encoded;
82 private int position = 0;
83
84 private int data = 0;
85 private int modulus = 0;
86
87 private int linePosition = 0;
88
89
90
91
92
93
94
95
96
97 public Base64OutputStream(OutputStream out) {
98 this(out, DEFAULT_LINE_LENGTH, CRLF_SEPARATOR);
99 }
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 public Base64OutputStream(OutputStream out, int lineLength) {
115 this(out, lineLength, CRLF_SEPARATOR);
116 }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 public Base64OutputStream(OutputStream out, int lineLength,
138 byte[] lineSeparator) {
139 super(out);
140
141 if (out == null)
142 throw new IllegalArgumentException();
143 if (lineLength < 0)
144 throw new IllegalArgumentException();
145 checkLineSeparator(lineSeparator);
146
147 this.lineLength = lineLength;
148 this.lineSeparator = new byte[lineSeparator.length];
149 System.arraycopy(lineSeparator, 0, this.lineSeparator, 0,
150 lineSeparator.length);
151
152 this.encoded = new byte[ENCODED_BUFFER_SIZE];
153 }
154
155 @Override
156 public final void write(final int b) throws IOException {
157 if (closed)
158 throw new IOException("Base64OutputStream has been closed");
159
160 singleByte[0] = (byte) b;
161 write0(singleByte, 0, 1);
162 }
163
164 @Override
165 public final void write(final byte[] buffer) throws IOException {
166 if (closed)
167 throw new IOException("Base64OutputStream has been closed");
168
169 if (buffer == null)
170 throw new NullPointerException();
171
172 if (buffer.length == 0)
173 return;
174
175 write0(buffer, 0, buffer.length);
176 }
177
178 @Override
179 public final void write(final byte[] buffer, final int offset,
180 final int length) throws IOException {
181 if (closed)
182 throw new IOException("Base64OutputStream has been closed");
183
184 if (buffer == null)
185 throw new NullPointerException();
186
187 if (offset < 0 || length < 0 || offset + length > buffer.length)
188 throw new IndexOutOfBoundsException();
189
190 if (length == 0)
191 return;
192
193 write0(buffer, offset, offset + length);
194 }
195
196 @Override
197 public void flush() throws IOException {
198 if (closed)
199 throw new IOException("Base64OutputStream has been closed");
200
201 flush0();
202 }
203
204 @Override
205 public void close() throws IOException {
206 if (closed)
207 return;
208
209 closed = true;
210 close0();
211 }
212
213 private void write0(final byte[] buffer, final int from, final int to)
214 throws IOException {
215 for (int i = from; i < to; i++) {
216 data = (data << 8) | (buffer[i] & 0xff);
217
218 if (++modulus == 3) {
219 modulus = 0;
220
221
222
223 if (lineLength > 0 && linePosition >= lineLength) {
224
225
226 linePosition = 0;
227
228 if (encoded.length - position < lineSeparator.length)
229 flush0();
230
231 for (byte ls : lineSeparator)
232 encoded[position++] = ls;
233 }
234
235
236
237 if (encoded.length - position < 4)
238 flush0();
239
240 encoded[position++] = BASE64_TABLE[(data >> 18) & MASK_6BITS];
241 encoded[position++] = BASE64_TABLE[(data >> 12) & MASK_6BITS];
242 encoded[position++] = BASE64_TABLE[(data >> 6) & MASK_6BITS];
243 encoded[position++] = BASE64_TABLE[data & MASK_6BITS];
244
245 linePosition += 4;
246 }
247 }
248 }
249
250 private void flush0() throws IOException {
251 if (position > 0) {
252 out.write(encoded, 0, position);
253 position = 0;
254 }
255 }
256
257 private void close0() throws IOException {
258 if (modulus != 0)
259 writePad();
260
261
262
263 if (lineLength > 0 && linePosition > 0) {
264 writeLineSeparator();
265 }
266
267 flush0();
268 }
269
270 private void writePad() throws IOException {
271
272
273 if (lineLength > 0 && linePosition >= lineLength) {
274 writeLineSeparator();
275 }
276
277
278
279 if (encoded.length - position < 4)
280 flush0();
281
282 if (modulus == 1) {
283 encoded[position++] = BASE64_TABLE[(data >> 2) & MASK_6BITS];
284 encoded[position++] = BASE64_TABLE[(data << 4) & MASK_6BITS];
285 encoded[position++] = BASE64_PAD;
286 encoded[position++] = BASE64_PAD;
287 } else {
288 assert modulus == 2;
289 encoded[position++] = BASE64_TABLE[(data >> 10) & MASK_6BITS];
290 encoded[position++] = BASE64_TABLE[(data >> 4) & MASK_6BITS];
291 encoded[position++] = BASE64_TABLE[(data << 2) & MASK_6BITS];
292 encoded[position++] = BASE64_PAD;
293 }
294
295 linePosition += 4;
296 }
297
298 private void writeLineSeparator() throws IOException {
299 linePosition = 0;
300
301 if (encoded.length - position < lineSeparator.length)
302 flush0();
303
304 for (byte ls : lineSeparator)
305 encoded[position++] = ls;
306 }
307
308 private void checkLineSeparator(byte[] lineSeparator) {
309 if (lineSeparator.length > ENCODED_BUFFER_SIZE)
310 throw new IllegalArgumentException("line separator length exceeds "
311 + ENCODED_BUFFER_SIZE);
312
313 for (byte b : lineSeparator) {
314 if (BASE64_CHARS.contains(b)) {
315 throw new IllegalArgumentException(
316 "line separator must not contain base64 character '"
317 + (char) (b & 0xff) + "'");
318 }
319 }
320 }
321 }