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 package org.apache.myfaces.trinidad.util;
20
21 import java.io.OutputStream;
22 import java.io.Writer;
23 import java.io.IOException;
24 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
25
26 /**
27 * An OutputStream that encodes data in a base64 representation.
28 * It takes a Writer as its single argument to its constructor and all
29 * bytes written to the stream are correspondingly converted into Base64
30 * and written out to the provided writer.
31 */
32 public class Base64OutputStream extends OutputStream
33 {
34 public Base64OutputStream(Writer out)
35 {
36 _out = out;
37 _numLeftoverBytes = 0;
38 _leftoverBytes = new byte[2];
39 }
40
41 /**
42 * Takes a byte writes it out to the writer
43 *
44 * @param b a byte
45 */
46 @Override
47 public void write(int b) throws IOException
48 {
49 _single[0] = (byte) b;
50 this.write(_single, 0, 1);
51 }
52
53 /**
54 * Writes len bytes from the specified byte array starting at offset off
55 * to this output stream. The general contract for write(b, off, len) is
56 * that some of the bytes in the array b are written to the output stream
57 * in order; element b[off] is the first byte written and b[off+len-1] is
58 * the last byte written by this operation.
59 *
60 * The write method of OutputStream calls the write method of one argument
61 * on each of the bytes to be written out. Subclasses are encouraged to
62 * override this method and provide a more efficient implementation.
63 *
64 * If b is null, a NullPointerException is thrown.
65 *
66 * If off is negative, or len is negative, or off+len is greater than the
67 * length of the array b, then an IndexOutOfBoundsException is thrown.
68 *
69 *
70 * @param b the data
71 * @param off the start offset in the data
72 * @param len the number of bytes to read
73 */
74 @Override
75 public void write(byte[] b, int off, int len)
76 throws IOException, NullPointerException
77 {
78 if (b==null)
79 {
80 throw new NullPointerException(_LOG.getMessage(
81 "BYTE_ARRAY_CANNOT_BE_NULL"));
82 }
83
84 if (off<0 || len<0 || off+len>b.length)
85 {
86 throw new IndexOutOfBoundsException(_LOG.getMessage(
87 "ACTUAL_LENGTH_OFFSET", new Object[]{b.length,off,len}));
88 }
89
90 int lengthToProcess = len;
91 int index = off;
92
93 // base case 1: if only processing one byte from byte array
94 if (lengthToProcess==1)
95 {
96 if (_numLeftoverBytes==0)
97 {
98 // remember this byte for next call to write
99 _numLeftoverBytes = 1;
100 _leftoverBytes[0] = b[index];
101 }
102 else if (_numLeftoverBytes==1)
103 {
104 // remember this byte for next call to write
105 _numLeftoverBytes = 2;
106 _leftoverBytes[1] = b[index];
107 }
108 else if (_numLeftoverBytes==2)
109 {
110 // this one byte is enough to complete a triplet
111 // so convert triplet into Base64
112 _writeBase64(_leftoverBytes[0], _leftoverBytes[1], b[index]);
113 _numLeftoverBytes=0;
114 }
115 return;
116 } //end if (lengthToProcess==1)
117
118 // base case 2: if only processing two bytes from byte array
119 if (lengthToProcess==2)
120 {
121 if (_numLeftoverBytes==0)
122 {
123 // not enough to process a triplet, so remember these two bytes
124 // for next call to write
125 _numLeftoverBytes = 2;
126 _leftoverBytes[0] = b[index];
127 _leftoverBytes[1] = b[index+1];
128 }
129 else if (_numLeftoverBytes==1)
130 {
131 // these two bytes form triplet combined with the leftover byte
132 _writeBase64(_leftoverBytes[0], b[index], b[index+1]);
133 _numLeftoverBytes = 0;
134 }
135 else if (_numLeftoverBytes==2)
136 {
137 // two leftover bytes and one new byte form a triplet
138 // the second new byte is remembered for next call to write
139 _writeBase64(_leftoverBytes[0], _leftoverBytes[1], b[index]);
140 _leftoverBytes[0] = b[index+1];
141 _numLeftoverBytes = 1;
142 }
143 return;
144 } // end if (lengthToProcess==2)
145
146 // case involving looping
147 if (lengthToProcess>2)
148 {
149 if (_numLeftoverBytes==1)
150 {
151 _writeBase64(_leftoverBytes[0], b[index], b[index+1]);
152 _numLeftoverBytes = 0;
153 lengthToProcess -= 2;
154 index += 2;
155 // proceed with loop
156 }
157 else if (_numLeftoverBytes==2)
158 {
159 _writeBase64(_leftoverBytes[0], _leftoverBytes[1], b[index]);
160 _numLeftoverBytes = 0;
161 lengthToProcess -= 1;
162 index += 1;
163 // proceed with loop
164 }
165
166 _processArray(b, index, lengthToProcess);
167 }
168
169 } //end write(byte[], int off, int len)
170
171 @Override
172 public void flush() throws IOException
173 {
174 _out.flush();
175 }
176
177 /**
178 * Call this method to indicate end of data stream
179 * and to append any padding characters if necessary. This method should be
180 * called only if there will be no subsequent calls to a write method.
181 * Subsequent calls to the write method will result in incorrect encoding.
182 *
183 * @deprecated use the close() method instead.
184 */
185 @Deprecated
186 public void finish() throws IOException
187 {
188 close();
189 }
190
191 /**
192 * Call this method to indicate end of data stream
193 * and to append any padding characters if necessary. This method should be
194 * called only if there will be no subsequent calls to a write method.
195 * Subsequent calls to the write method will result in incorrect encoding.
196 *
197 */
198 @Override
199 public void close() throws IOException
200 {
201 if (_numLeftoverBytes==1)
202 {
203 // grab the one byte from the leftover array
204 byte b1 = _leftoverBytes[0];
205
206 // convert to two base 64 chars
207 int c1, c2;
208 c1 = (b1>>2)&0x3f;
209 c2 = (b1<<4)&0x3f;
210
211 char[] encodedChars = _fourChars;
212
213 encodedChars[0] = _encode(c1);
214 encodedChars[1] = _encode(c2);
215 // append two padding characters
216 encodedChars[2] = '=';
217 encodedChars[3] = '=';
218
219 _out.write(encodedChars);
220 }
221 else if (_numLeftoverBytes==2)
222 {
223 // grab the two bytes from the leftovers array
224 byte b1, b2;
225 b1 = _leftoverBytes[0];
226 b2 = _leftoverBytes[1];
227
228 // convert the two bytes into three base64 chars
229 int c1, c2, c3;
230 c1 = (b1>>2)&0x3f;
231 c2 = (b1<<4 | ((b2>>4)&0x0f))&0x3f;
232 c3 = (b2<<2)&0x3f;
233
234 char[] encodedChars = _fourChars;
235
236 encodedChars[0] = _encode(c1);
237 encodedChars[1] = _encode(c2);
238 encodedChars[2] = _encode(c3);
239 //append one padding character
240 encodedChars[3] = '=';
241
242 _out.write(encodedChars);
243 }
244 _out.close();
245 }
246
247
248 /**
249 * Encodes three bytes in base64 representation and writes the corresponding
250 * four base64 characters to the output writer.
251 *
252 * @param b1 the first byte
253 * @param b2 the second byte
254 * @param b3 the third byte
255 */
256 private void _writeBase64(byte b1, byte b2, byte b3) throws IOException
257 {
258 int c1, c2, c3, c4;
259 char[] encodedChars = _fourChars;
260
261 c1 = (b1>>2)&0x3f; // b1.high6
262 c2 = (b1<<4 | ((b2>>4)&0x0f) )&0x3f; // b1.low2 + b2.high4
263 c3 = (b2<<2 | ((b3>>6)&0x03) )&0x3f; // b2.low4 + b3.high2
264 c4 = b3&0x3f; // b3.low6
265
266 encodedChars[0] = _encode(c1);
267 encodedChars[1] = _encode(c2);
268 encodedChars[2] = _encode(c3);
269 encodedChars[3] = _encode(c4);
270
271 // write array of chars to writer
272 _out.write(encodedChars);
273 }
274
275 /**
276 * Writes lengthToProcess number of bytes from byte array to writer
277 * in base64 beginning with startIndex. Assumes all leftover bytes from
278 * previous calls to write have been dealt with.
279 *
280 * @param b the data
281 * @param startIndex the start offset in the data
282 * @param lengthToProcess the number of bytes to read
283 */
284 private void _processArray(byte[] b, int startIndex, int lengthToProcess)
285 throws IOException
286 {
287 int index = startIndex;
288
289 // loop through remaining length of array
290 while(lengthToProcess>0) {
291 // base case: only one byte
292 if (lengthToProcess==1)
293 {
294 // save this byte for next call to write
295 _numLeftoverBytes = 1;
296 _leftoverBytes[0] = b[index];
297 return;
298 }
299 // base case: only two bytes
300 else if (lengthToProcess==2)
301 {
302 // save these two bytes for next call to write
303 _numLeftoverBytes = 2;
304 _leftoverBytes[0] = b[index];
305 _leftoverBytes[1] = b[index+1];
306 return;
307 }
308 else
309 {
310 // encode three bytes (24 bits) from input array
311 _writeBase64(b[index],b[index+1],b[index+2]);
312
313 // finally, make some progress in loop
314 lengthToProcess -= 3;
315 index +=3;
316 }
317 } //end while
318 }
319
320
321
322 /**
323 * Encodes a six-bit pattern into a base-64 character.
324 *
325 * @param c an integer whose lower 6 bits contain the base64 representation
326 * all other bits should be zero
327 */
328 private static char _encode(int c)
329 {
330 if (c < 26)
331 return (char)('A' + c);
332 if (c < 52)
333 return (char)('a' + (c-26));
334 if (c < 62)
335 return (char)('0' + (c-52));
336 if (c == 62)
337 return '+';
338 if (c == 63)
339 return '/';
340
341 throw new AssertionError("Invalid B64 character code:"+c);
342 }
343
344
345 /** stores leftover bytes from previous call to write method **/
346 private final byte[] _leftoverBytes;
347
348 /** indicates the number of bytes that were leftover after the last triplet
349 * was formed in the last call to write method **/
350 private int _numLeftoverBytes;
351
352 // cached four-character array
353 private final char[] _fourChars = new char[4];
354 // cached single-byte array
355 private final byte[] _single = new byte[1];
356
357 // Writer that will receive all completed character output
358 private final Writer _out;
359 private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
360 Base64OutputStream.class);
361 }