View Javadoc

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.InputStream;
24  import java.io.OutputStream;
25  import java.io.PrintStream;
26  
27  public class Base64Encoder
28      implements Encoder
29  {
30      protected final byte[] encodingTable =
31          {
32              (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
33              (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
34              (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
35              (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
36              (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
37              (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
38              (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
39              (byte)'v',
40              (byte)'w', (byte)'x', (byte)'y', (byte)'z',
41              (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6',
42              (byte)'7', (byte)'8', (byte)'9',
43              (byte)'+', (byte)'/'
44          };
45  
46      protected byte    padding = (byte)'=';
47  
48      /*
49       * set up the decoding table.
50       */
51      protected final byte[] decodingTable = new byte[256];
52  
53      protected void initialiseDecodingTable()
54      {
55          for (int i = 0; i < encodingTable.length; i++)
56          {
57              decodingTable[encodingTable[i]] = (byte)i;
58          }
59      }
60  
61      public Base64Encoder()
62      {
63          initialiseDecodingTable();
64      }
65  
66      /**
67       * encode the input data producing a base 64 output stream.
68       *
69       * @return the number of bytes produced.
70       */
71      public int encode(
72          byte[]                data,
73          int                    off,
74          int                    length,
75          OutputStream    out)
76          throws IOException
77      {
78          int modulus = length % 3;
79          int dataLength = (length - modulus);
80          int a1, a2, a3;
81  
82          for (int i = off; i < off + dataLength; i += 3)
83          {
84              a1 = data[i] & 0xff;
85              a2 = data[i + 1] & 0xff;
86              a3 = data[i + 2] & 0xff;
87  
88              out.write(encodingTable[(a1 >>> 2) & 0x3f]);
89              out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
90              out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
91              out.write(encodingTable[a3 & 0x3f]);
92          }
93  
94          /*
95           * process the tail end.
96           */
97          int    b1, b2, b3;
98          int    d1, d2;
99  
100         switch (modulus)
101         {
102         case 0:        /* nothing left to do */
103             break;
104         case 1:
105             d1 = data[off + dataLength] & 0xff;
106             b1 = (d1 >>> 2) & 0x3f;
107             b2 = (d1 << 4) & 0x3f;
108 
109             out.write(encodingTable[b1]);
110             out.write(encodingTable[b2]);
111             out.write(padding);
112             out.write(padding);
113             break;
114         case 2:
115             d1 = data[off + dataLength] & 0xff;
116             d2 = data[off + dataLength + 1] & 0xff;
117 
118             b1 = (d1 >>> 2) & 0x3f;
119             b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
120             b3 = (d2 << 2) & 0x3f;
121 
122             out.write(encodingTable[b1]);
123             out.write(encodingTable[b2]);
124             out.write(encodingTable[b3]);
125             out.write(padding);
126             break;
127         }
128 
129         return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4);
130     }
131 
132     private boolean ignore(
133         char    c)
134     {
135         return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
136     }
137 
138     /**
139      * decode the base 64 encoded byte data writing it to the given output stream,
140      * whitespace characters will be ignored.
141      *
142      * @return the number of bytes produced.
143      */
144     public int decode(
145         byte[]                data,
146         int                    off,
147         int                    length,
148         OutputStream    out)
149         throws IOException
150     {
151         byte[]    bytes;
152         byte    b1, b2, b3, b4;
153         int        outLen = 0;
154 
155         int        end = off + length;
156 
157         while (end > 0)
158         {
159             if (!ignore((char)data[end - 1]))
160             {
161                 break;
162             }
163 
164             end--;
165         }
166 
167         int  i = off;
168         int  finish = end - 4;
169 
170         while (i < finish)
171         {
172             while ((i < finish) && ignore((char)data[i]))
173             {
174                 i++;
175             }
176 
177             b1 = decodingTable[data[i++]];
178 
179             while ((i < finish) && ignore((char)data[i]))
180             {
181                 i++;
182             }
183 
184             b2 = decodingTable[data[i++]];
185 
186             while ((i < finish) && ignore((char)data[i]))
187             {
188                 i++;
189             }
190 
191             b3 = decodingTable[data[i++]];
192 
193             while ((i < finish) && ignore((char)data[i]))
194             {
195                 i++;
196             }
197 
198             b4 = decodingTable[data[i++]];
199 
200             out.write((b1 << 2) | (b2 >> 4));
201             out.write((b2 << 4) | (b3 >> 2));
202             out.write((b3 << 6) | b4);
203 
204             outLen += 3;
205         }
206 
207         if (data[end - 2] == padding)
208         {
209             b1 = decodingTable[data[end - 4]];
210             b2 = decodingTable[data[end - 3]];
211 
212             out.write((b1 << 2) | (b2 >> 4));
213 
214             outLen += 1;
215         }
216         else if (data[end - 1] == padding)
217         {
218             b1 = decodingTable[data[end - 4]];
219             b2 = decodingTable[data[end - 3]];
220             b3 = decodingTable[data[end - 2]];
221 
222             out.write((b1 << 2) | (b2 >> 4));
223             out.write((b2 << 4) | (b3 >> 2));
224 
225             outLen += 2;
226         }
227         else
228         {
229             b1 = decodingTable[data[end - 4]];
230             b2 = decodingTable[data[end - 3]];
231             b3 = decodingTable[data[end - 2]];
232             b4 = decodingTable[data[end - 1]];
233 
234             out.write((b1 << 2) | (b2 >> 4));
235             out.write((b2 << 4) | (b3 >> 2));
236             out.write((b3 << 6) | b4);
237 
238             outLen += 3;
239         }
240 
241         return outLen;
242     }
243 
244     /**
245      * decode the base 64 encoded String data writing it to the given output stream,
246      * whitespace characters will be ignored.
247      *
248      * @return the number of bytes produced.
249      */
250     public int decode(
251         String                data,
252         OutputStream    out)
253         throws IOException
254     {
255         byte[]    bytes;
256         byte    b1, b2, b3, b4;
257         int        length = 0;
258 
259         int        end = data.length();
260 
261         while (end > 0)
262         {
263             if (!ignore(data.charAt(end - 1)))
264             {
265                 break;
266             }
267 
268             end--;
269         }
270 
271         int    i = 0;
272         int   finish = end - 4;
273 
274         while (i < finish)
275         {
276             while ((i < finish) && ignore(data.charAt(i)))
277             {
278                 i++;
279             }
280 
281             b1 = decodingTable[data.charAt(i++)];
282 
283             while ((i < finish) && ignore(data.charAt(i)))
284             {
285                 i++;
286             }
287             b2 = decodingTable[data.charAt(i++)];
288 
289             while ((i < finish) && ignore(data.charAt(i)))
290             {
291                 i++;
292             }
293             b3 = decodingTable[data.charAt(i++)];
294 
295             while ((i < finish) && ignore(data.charAt(i)))
296             {
297                 i++;
298             }
299             b4 = decodingTable[data.charAt(i++)];
300 
301             out.write((b1 << 2) | (b2 >> 4));
302             out.write((b2 << 4) | (b3 >> 2));
303             out.write((b3 << 6) | b4);
304 
305             length += 3;
306         }
307 
308         if (data.charAt(end - 2) == padding)
309         {
310             b1 = decodingTable[data.charAt(end - 4)];
311             b2 = decodingTable[data.charAt(end - 3)];
312 
313             out.write((b1 << 2) | (b2 >> 4));
314 
315             length += 1;
316         }
317         else if (data.charAt(end - 1) == padding)
318         {
319             b1 = decodingTable[data.charAt(end - 4)];
320             b2 = decodingTable[data.charAt(end - 3)];
321             b3 = decodingTable[data.charAt(end - 2)];
322 
323             out.write((b1 << 2) | (b2 >> 4));
324             out.write((b2 << 4) | (b3 >> 2));
325 
326             length += 2;
327         }
328         else
329         {
330             b1 = decodingTable[data.charAt(end - 4)];
331             b2 = decodingTable[data.charAt(end - 3)];
332             b3 = decodingTable[data.charAt(end - 2)];
333             b4 = decodingTable[data.charAt(end - 1)];
334 
335             out.write((b1 << 2) | (b2 >> 4));
336             out.write((b2 << 4) | (b3 >> 2));
337             out.write((b3 << 6) | b4);
338 
339             length += 3;
340         }
341 
342         return length;
343     }
344 
345     /**
346      * decode the base 64 encoded byte data writing it to the provided byte array buffer.
347      *
348      * @return the number of bytes produced.
349      */
350     public int decode(byte[] data, int off, int length, byte[] out) throws IOException
351     {
352         byte[]    bytes;
353         byte    b1, b2, b3, b4;
354         int        outLen = 0;
355 
356         int        end = off + length;
357 
358         while (end > 0)
359         {
360             if (!ignore((char)data[end - 1]))
361             {
362                 break;
363             }
364 
365             end--;
366         }
367 
368         int  i = off;
369         int  finish = end - 4;
370 
371         while (i < finish)
372         {
373             while ((i < finish) && ignore((char)data[i]))
374             {
375                 i++;
376             }
377 
378             b1 = decodingTable[data[i++]];
379 
380             while ((i < finish) && ignore((char)data[i]))
381             {
382                 i++;
383             }
384 
385             b2 = decodingTable[data[i++]];
386 
387             while ((i < finish) && ignore((char)data[i]))
388             {
389                 i++;
390             }
391 
392             b3 = decodingTable[data[i++]];
393 
394             while ((i < finish) && ignore((char)data[i]))
395             {
396                 i++;
397             }
398 
399             b4 = decodingTable[data[i++]];
400 
401             out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
402             out[outLen++] = (byte)((b2 << 4) | (b3 >> 2));
403             out[outLen++] = (byte)((b3 << 6) | b4);
404         }
405 
406         if (data[end - 2] == padding)
407         {
408             b1 = decodingTable[data[end - 4]];
409             b2 = decodingTable[data[end - 3]];
410 
411             out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
412         }
413         else if (data[end - 1] == padding)
414         {
415             b1 = decodingTable[data[end - 4]];
416             b2 = decodingTable[data[end - 3]];
417             b3 = decodingTable[data[end - 2]];
418 
419             out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
420             out[outLen++] = (byte)((b2 << 4) | (b3 >> 2));
421         }
422         else
423         {
424             b1 = decodingTable[data[end - 4]];
425             b2 = decodingTable[data[end - 3]];
426             b3 = decodingTable[data[end - 2]];
427             b4 = decodingTable[data[end - 1]];
428 
429             out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
430             out[outLen++] = (byte)((b2 << 4) | (b3 >> 2));
431             out[outLen++] = (byte)((b3 << 6) | b4);
432         }
433 
434         return outLen;
435     }
436 
437     /**
438      * Test if a character is a valid Base64 encoding character.  This
439      * must be either a valid digit or the padding character ("=").
440      *
441      * @param ch     The test character.
442      *
443      * @return true if this is valid in Base64 encoded data, false otherwise.
444      */
445     public boolean isValidBase64(int ch) {
446         // 'A' has the value 0 in the decoding table, so we need a special one for that
447         return ch == padding || ch == 'A' || decodingTable[ch] != 0;
448     }
449 
450 
451     /**
452      * Perform RFC-2047 word encoding using Base64 data encoding.
453      *
454      * @param in      The source for the encoded data.
455      * @param charset The charset tag to be added to each encoded data section.
456      * @param out     The output stream where the encoded data is to be written.
457      * @param fold    Controls whether separate sections of encoded data are separated by
458      *                linebreaks or whitespace.
459      *
460      * @exception IOException
461      */
462     public void encodeWord(InputStream in, String charset, OutputStream out, boolean fold) throws IOException
463     {
464         PrintStream writer = new PrintStream(out);
465 
466         // encoded words are restricted to 76 bytes, including the control adornments.
467         int limit = 75 - 7 - charset.length();
468         boolean firstLine = true;
469         StringBuffer encodedString = new StringBuffer(76);
470 
471         while (true) {
472             // encode the next segment.
473             encode(in, encodedString, limit);
474             // if we're out of data, nothing will be encoded.
475             if (encodedString.length() == 0) {
476                 break;
477             }
478 
479             // if we have more than one segment, we need to insert separators.  Depending on whether folding
480             // was requested, this is either a blank or a linebreak.
481             if (!firstLine) {
482                 if (fold) {
483                     writer.print("\r\n");
484                 }
485                 else {
486                     writer.print(" ");
487                 }
488             }
489 
490             // add the encoded word header
491             writer.print("=?");
492             writer.print(charset);
493             writer.print("?B?");
494             // the data
495             writer.print(encodedString.toString());
496             // and the word terminator.
497             writer.print("?=");
498             writer.flush();
499 
500             // reset our string buffer for the next segment.
501             encodedString.setLength(0);
502             // we need a delimiter after this 
503             firstLine = false; 
504         }
505     }
506 
507 
508     /**
509      * Perform RFC-2047 word encoding using Base64 data encoding.
510      *
511      * @param in      The source for the encoded data.
512      * @param charset The charset tag to be added to each encoded data section.
513      * @param out     The output stream where the encoded data is to be written.
514      * @param fold    Controls whether separate sections of encoded data are separated by
515      *                linebreaks or whitespace.
516      *
517      * @exception IOException
518      */
519     public void encodeWord(byte[] data, StringBuffer out, String charset) throws IOException
520     {
521         // append the word header 
522         out.append("=?");
523         out.append(charset);
524         out.append("?B?"); 
525         // add on the encodeded data       
526         encodeWordData(data, out); 
527         // the end of the encoding marker 
528         out.append("?="); 
529     }
530     
531     /**
532      * encode the input data producing a base 64 output stream.
533      *
534      * @return the number of bytes produced.
535      */
536     public void encodeWordData(byte[] data, StringBuffer out) 
537     {
538         int modulus = data.length % 3;
539         int dataLength = (data.length - modulus);
540         int a1, a2, a3;
541 
542         for (int i = 0; i < dataLength; i += 3)
543         {
544             a1 = data[i] & 0xff;
545             a2 = data[i + 1] & 0xff;
546             a3 = data[i + 2] & 0xff;
547             
548             out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
549             out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
550             out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
551             out.append((char)encodingTable[a3 & 0x3f]);
552         }
553 
554         /*
555          * process the tail end.
556          */
557         int    b1, b2, b3;
558         int    d1, d2;
559 
560         switch (modulus)
561         {
562         case 0:        /* nothing left to do */
563             break;
564         case 1:
565             d1 = data[dataLength] & 0xff;
566             b1 = (d1 >>> 2) & 0x3f;
567             b2 = (d1 << 4) & 0x3f;
568 
569             out.append((char)encodingTable[b1]);
570             out.append((char)encodingTable[b2]);
571             out.append((char)padding);
572             out.append((char)padding);
573             break;
574         case 2:
575             d1 = data[dataLength] & 0xff;
576             d2 = data[dataLength + 1] & 0xff;
577 
578             b1 = (d1 >>> 2) & 0x3f;
579             b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
580             b3 = (d2 << 2) & 0x3f;
581 
582             out.append((char)encodingTable[b1]);
583             out.append((char)encodingTable[b2]);
584             out.append((char)encodingTable[b3]);
585             out.append((char)padding);
586             break;
587         }
588     }
589     
590 
591     /**
592      * encode the input data producing a base 64 output stream.
593      *
594      * @return the number of bytes produced.
595      */
596     public void encode(InputStream in, StringBuffer out, int limit) throws IOException
597     {
598         int count = limit / 4;
599         byte [] inBuffer = new byte[3];
600 
601         while (count-- > 0) {
602 
603             int readCount = in.read(inBuffer);
604             // did we get a full triplet?  that's an easy encoding.
605             if (readCount == 3) {
606                 int  a1 = inBuffer[0] & 0xff;
607                 int  a2 = inBuffer[1] & 0xff;
608                 int  a3 = inBuffer[2] & 0xff;
609                 
610                 out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
611                 out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
612                 out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
613                 out.append((char)encodingTable[a3 & 0x3f]);
614 
615             }
616             else if (readCount <= 0) {
617                 // eof condition, don'e entirely.
618                 return;
619             }
620             else if (readCount == 1) {
621                 int  a1 = inBuffer[0] & 0xff;
622                 out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
623                 out.append((char)encodingTable[(a1 << 4) & 0x3f]);
624                 out.append((char)padding);
625                 out.append((char)padding);
626                 return;
627             }
628             else if (readCount == 2) {
629                 int  a1 = inBuffer[0] & 0xff;
630                 int  a2 = inBuffer[1] & 0xff;
631 
632                 out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
633                 out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
634                 out.append((char)encodingTable[(a2 << 2) & 0x3f]);
635                 out.append((char)padding);
636                 return;
637             }
638         }
639     }
640     
641     
642     /**
643      * Estimate the final encoded size of a segment of data. 
644      * This is used to ensure that the encoded blocks do 
645      * not get split across a unicode character boundary and 
646      * that the encoding will fit within the bounds of 
647      * a mail header line. 
648      * 
649      * @param data   The data we're anticipating encoding.
650      * 
651      * @return The size of the byte data in encoded form. 
652      */
653     public int estimateEncodedLength(byte[] data) 
654     {
655         return ((data.length + 2) / 3) * 4; 
656     }
657 }