/Users/lyon/j4p/src/net/proxy/BASE64Decoder.java
|
1 package net.proxy;
2
3
4 /**
5 * Utility class to do Base64 decoding, as defined by RFC 2045,
6 * section 6.8 (http://www.ietf.org/rfc/rfc2045.txt)
7 * Uses the same class and function names as Sun's implementation from
8 * sun.misc
9 */
10 public class BASE64Decoder {
11
12 /**
13 * Bit mask for one byte worth of bits in Base64 encoding.
14 * Equivalent to binary value 11111111b.
15 */
16 private static final int EIGHT_BIT_MASK = 0xFF;
17
18 /**
19 * Decode an input String using Base64
20 * @param data The String to be decoded
21 * @return The appropriate byte array
22 */
23 public byte[] decodeBuffer(String data) {
24 // Create a wrapper around the input to screen out non-Base64 characters
25 StringWrapper wrapper = new StringWrapper(data);
26 // A Base64 byte array is 75% the size of its String representation
27 int length = wrapper.getUsefulLength() * 3 / 4;
28
29 byte byteArray[] = new byte[length];
30
31 int byteTriplet = 0;
32 int index = 0;
33
34 // Continue until we have less than 4 full characters left to
35 // decode in the input.
36 while (index + 2 < length) {
37
38 index = processByteArray(
39 wrapper,
40 byteArray,
41 index);
42
43 }
44
45 checkIfWeHave1ByteLeft(
46 index,
47 length,
48 wrapper, byteArray);
49
50 checkIfWeHave2BytesLeft(index,
51 length,
52 wrapper,
53 byteArray);
54
55 return byteArray;
56 }
57
58 private void checkIfWeHave2BytesLeft(int index, int length, StringWrapper wrapper, byte[] byteArray) {
59 int byteTriplet;
60 if (index == length - 2) {
61 // Take out the last three characters from the String
62 byteTriplet = charToInt(wrapper.getNextUsefulChar());
63 byteTriplet <<= 6;
64 byteTriplet |= charToInt(wrapper.getNextUsefulChar());
65 byteTriplet <<= 6;
66 byteTriplet |= charToInt(wrapper.getNextUsefulChar());
67
68 // Remove the padded zeros
69 byteTriplet >>= 2;
70 byteArray[index + 1] = (byte) (byteTriplet & EIGHT_BIT_MASK);
71 byteTriplet >>= 8;
72 byteArray[index] = (byte) (byteTriplet & EIGHT_BIT_MASK);
73 }
74 }
75
76 private void checkIfWeHave1ByteLeft(int byteIndex, int byteArrayLength, StringWrapper wrapper, byte[] result) {
77 int byteTriplet;
78 if (byteIndex == byteArrayLength - 1) {
79 // Take out the last two characters from the String
80 byteTriplet = charToInt(wrapper.getNextUsefulChar());
81 byteTriplet <<= 6;
82 byteTriplet |= charToInt(wrapper.getNextUsefulChar());
83
84 // Remove the padded zeros
85 byteTriplet >>= 4;
86 result[byteIndex] = (byte) (byteTriplet & EIGHT_BIT_MASK);
87 }
88 }
89
90 private int processByteArray(StringWrapper wrapper, byte[] result, int byteIndex) {
91 int byteTriplet;
92 // Package a set of four characters into a byte triplet
93 // Each character contributes 6 bits of useful information
94 byteTriplet = charToInt(wrapper.getNextUsefulChar());
95 byteTriplet <<= 6;
96 byteTriplet |= charToInt(wrapper.getNextUsefulChar());
97 byteTriplet <<= 6;
98 byteTriplet |= charToInt(wrapper.getNextUsefulChar());
99 byteTriplet <<= 6;
100 byteTriplet |= charToInt(wrapper.getNextUsefulChar());
101
102 // Grab a normal byte (eight bits) out of the byte triplet
103 // and put it in the byte array
104 result[byteIndex + 2] = (byte) (byteTriplet & EIGHT_BIT_MASK);
105 byteTriplet >>= 8;
106 result[byteIndex + 1] = (byte) (byteTriplet & EIGHT_BIT_MASK);
107 byteTriplet >>= 8;
108 result[byteIndex] = (byte) (byteTriplet & EIGHT_BIT_MASK);
109 byteIndex += 3;
110 return byteIndex;
111 }
112
113 /**
114 * Convert a Base64 character to its 6 bit value as defined by the mapping.
115 * @param c Base64 character to decode
116 * @return int representation of 6 bit value
117 */
118 private int charToInt(char c) {
119 if (c >= 'A' && c <= 'Z') {
120 return c - 'A';
121 }
122
123 if (c >= 'a' && c <= 'z') {
124 return (c - 'a') + Constants.LOWER_CASE_A_VALUE;
125 }
126
127 if (c >= '0' && c <= '9') {
128 return (c - '0') + Constants.ZERO_VALUE;
129 }
130
131 if (c == '+') {
132 return Constants.PLUS_VALUE;
133 }
134
135 if (c == '/') {
136 return Constants.SLASH_VALUE;
137 }
138
139 throw new IllegalArgumentException(c + " is not a valid Base64 character.");
140 }
141
142 /**
143 * Simple class to wrap around the String input to ignore all of the
144 * non-Base64 characters in the input. Note that although '=' is
145 * a valid character, it does not contribute to the total number
146 * of output bytes, and is therefore ignored
147 */
148 private class StringWrapper {
149
150 /**
151 * The input String to be decoded
152 */
153 private String mString;
154
155 /**
156 * Current position in the String
157 */
158 private int mIndex = 0;
159
160 /**
161 * Total number of Base64 characters in the input
162 */
163 private int mUsefulLength;
164
165 /**
166 * @param c Character to be examined
167 * @return Whether or not the character is a Base64 character
168 */
169 private boolean isUsefulChar(char c) {
170 return (c >= 'A' && c <= 'Z') ||
171 (c >= 'a' && c <= 'z') ||
172 (c >= '0' && c <= '9') ||
173 (c == '+') ||
174 (c == '/');
175 }
176
177 /**
178 * Create the wrapper and determine the number of Base64 characters in
179 * the input
180 * @param s Input String to be decoded
181 */
182 public StringWrapper(String s) {
183 mString = s;
184 mUsefulLength = 0;
185 int length = mString.length();
186 for (int i = 0; i < length; i++) {
187 if (isUsefulChar(mString.charAt(i))) {
188 mUsefulLength++;
189 }
190 }
191 }
192
193 /**
194 * @return Total number of Base64 characters in the input. Does
195 * not include '='
196 */
197 public int getUsefulLength() {
198 return mUsefulLength;
199 }
200
201 /**
202 * Traverse the String until hitting the next Base64 character.
203 * Assumes that there is still another valid Base64 character
204 * left in the String.
205 */
206 public char getNextUsefulChar() {
207 char result = '_'; // Start with a non-Base64 character
208 while (!isUsefulChar(result)) {
209 result = mString.charAt(mIndex++);
210 }
211
212 return result;
213 }
214 }
215 }
216