View Javadoc

1   /*
2    *  Copyright 2006 Simon Raess
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package net.sf.beep4j.internal.util;
17  
18  import java.io.ByteArrayOutputStream;
19  import java.io.IOException;
20  import java.io.Reader;
21  import java.io.StringReader;
22  
23  /**
24   * Encodes binary data to plain text as Base64.
25   *
26   * <p>Despite there being a gazillion other Base64 implementations out there, this has been written as part of XStream as
27   * it forms a core part but is too trivial to warrant an extra dependency.</p>
28   *
29   * <p>This meets the standard as described in RFC 1521, section 5.2 <http://www.freesoft.org/CIE/RFC/1521/7.htm>, allowing
30   * other Base64 tools to manipulate the data.</p>
31   *
32   * @author Joe Walnes
33   */
34  public class Base64Encoder {
35  
36      // Here's how encoding works:
37      //
38      // 1) Incoming bytes are broken up into groups of 3 (each byte having 8 bits).
39      //
40      // 2) The combined 24 bits (3 * 8) are split into 4 groups of 6 bits.
41      //
42      // input  |------||------||------| (3 values each with 8 bits)
43      //        101010101010101010101010
44      // output |----||----||----||----| (4 values each with 6 bits)
45      //
46      // 3) Each of these 4 groups of 6 bits are converted back to a number, which will fall in the range of 0 - 63.
47      //
48      // 4) Each of these 4 numbers are converted to an alphanumeric char in a specified mapping table, to create
49      //    a 4 character string.
50      //
51      // 5) This is repeated for all groups of three bytes.
52      //
53      // 6) Special padding is done at the end of the stream using the '=' char.
54  
55      private static final char[] SIXTY_FOUR_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
56      private static final int[] REVERSE_MAPPING = new int[123];
57  
58      static {
59          for (int i = 0; i < SIXTY_FOUR_CHARS.length; i++) REVERSE_MAPPING[SIXTY_FOUR_CHARS[i]] = i + 1;
60      }
61  
62      public String encode(byte[] input) {
63          StringBuffer result = new StringBuffer();
64          int outputCharCount = 0;
65          for (int i = 0; i < input.length; i += 3) {
66              int remaining = Math.min(3, input.length - i);
67              int oneBigNumber = (input[i] & 0xff) << 16 | (remaining <= 1 ? 0 : input[i + 1] & 0xff) << 8 | (remaining <= 2 ? 0 : input[i + 2] & 0xff);
68              for (int j = 0; j < 4; j++) result.append(remaining + 1 > j ? SIXTY_FOUR_CHARS[0x3f & oneBigNumber >> 6 * (3 - j)] : '=');
69              if ((outputCharCount += 4) % 76 == 0) result.append('\n');
70          }
71          return result.toString();
72      }
73  
74      public byte[] decode(String input) {
75          try {
76              ByteArrayOutputStream out = new ByteArrayOutputStream();
77              StringReader in = new StringReader(input);
78              for (int i = 0; i < input.length(); i += 4) {
79                  int a[] = {mapCharToInt(in), mapCharToInt(in), mapCharToInt(in), mapCharToInt(in)};
80                  int oneBigNumber = (a[0] & 0x3f) << 18 | (a[1] & 0x3f) << 12 | (a[2] & 0x3f) << 6 | (a[3] & 0x3f);
81                  for (int j = 0; j < 3; j++) if (a[j + 1] >= 0) out.write(0xff & oneBigNumber >> 8 * (2 - j));
82              }
83              return out.toByteArray();
84          } catch (IOException e) {
85              throw new Error(e + ": " + e.getMessage());
86          }
87      }
88  
89      private int mapCharToInt(Reader input) throws IOException {
90          int c;
91          while ((c = input.read()) != -1) {
92              int result = REVERSE_MAPPING[c];
93              if (result != 0) return result -1;
94              if (c == '=') return -1;
95          }
96          return -1;
97      }
98      
99  }