/Users/lyon/j4p/src/net/proxy/BASE64Encoder.java
|
1 package net.proxy;
2
3 import java.util.Arrays;
4
5 /**
6 * Utility class to do Base64 encoding, as defined by RFC 2045,
7 * section 6.8 (http://www.ietf.org/rfc/rfc2045.txt)
8 * Uses the same class and function names as Sun's implementation from
9 * sun.misc
10 */
11 public class BASE64Encoder {
12
13 /**
14 * Convert a byte to an integer. Needed because in Java bytes
15 * are signed, and for Base64 purposes they are not. If not done
16 * this way, when converted to an int, 0xFF will become -127
17 * @param b Byte value to be converted
18 * @return Value as an integer, as if byte was unsigned
19 */
20 private int uByteToInt(byte b) {
21 if (b >= 0) {
22 return (int) b;
23 }
24
25 return 256 + b;
26 }
27
28 /**
29 * Encode an array of bytes using Base64
30 * @param data[] The bytes to be encoded
31 * @return A valid Base64 representation of the input
32 */
33 public String encode(byte data[]) {
34 // Base64 encoding yields a String that is 33% longer than the byte array
35 int charCount = ((data.length * 4) / 3) + 4;
36
37 // New lines will also be needed for every 76 charactesr, so allocate a
38 // StringBuffer that is long enough to hold the full result without
39 // having to expand later
40 StringBuffer result = new StringBuffer(
41 (charCount * 77) / 76);
42
43 int byteArrayLength = data.length;
44 int byteArrayIndex = 0;
45 int byteTriplet = 0;
46 while (byteArrayIndex < byteArrayLength - 2) {
47 byteArrayIndex = processData(
48 data, byteArrayIndex, result);
49
50 }
51
52 byteArrayIndex = checkIfWeHave1ByteLeftOver(
53 byteArrayIndex, byteArrayLength, data, result);
54 checkIfWeHaveTwoBytesLeftOver(
55 byteArrayIndex, byteArrayLength, data, result);
56
57 return result.toString();
58 }
59
60 private int processData(
61 byte[] data, int index,
62 StringBuffer result) {
63 int byteTriplet;
64 // Build the 24 bit byte triplet from the input data
65 byteTriplet = uByteToInt(data[index++]);
66 // Each input byte contributes 8 bits to the triplet
67 byteTriplet <<= 8;
68 byteTriplet |= uByteToInt(data[index++]);
69 byteTriplet <<= 8;
70 byteTriplet |= uByteToInt(data[index++]);
71
72 // Look at the lowest order six bits and remember them
73 byte b4 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
74 // Move the byte triplet to get the next 6 bit value
75 byteTriplet >>= 6;
76 byte b3 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
77 byteTriplet >>= 6;
78 byte b2 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
79 byteTriplet >>= 6;
80 byte b1 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
81
82 // Add the Base64 encoded character to the result String
83 result.append(byteToChar(b1));
84 result.append(byteToChar(b2));
85 result.append(byteToChar(b3));
86 result.append(byteToChar(b4));
87
88 // There are 57 bytes for every 76 characters, so wrap the line when needed
89 if (index % 57 == 0) {
90 result.append("\n");
91 }
92 return index;
93 }
94
95 private void checkIfWeHaveTwoBytesLeftOver(
96 int index,
97 int length,
98 byte[] byteArray,
99 StringBuffer sb) {
100 int byteTriplet;
101 if (index == length - 2) {
102 // Convert our two bytes to an int
103 byteTriplet = twoBytesToInt(byteArray, index);
104 // Right pad the third 6 bit value with zeros
105 byteTriplet <<= 2;
106
107 byte b3 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
108 byteTriplet >>= 6;
109 byte b2 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
110 byteTriplet >>= 6;
111 byte b1 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
112
113 sb.append(byteToChar(b1));
114 sb.append(byteToChar(b2));
115 sb.append(byteToChar(b3));
116
117 // Add "==" to the output to make it a multiple of 4 Base64 characters
118 sb.append("=");
119 }
120 }
121
122 private int checkIfWeHave1ByteLeftOver(
123 int index,
124 int length,
125 byte[] byteArray,
126 StringBuffer sb) {
127 int byteTriplet;
128 if (index == length - 1) {
129 // Convert our one byte to an int
130 byteTriplet = uByteToInt(byteArray[index++]);
131 // Right pad the second 6 bit value with zeros
132 byteTriplet <<= 4;
133
134 byte b2 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
135 byteTriplet >>= 6;
136 byte b1 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
137
138 sb.append(byteToChar(b1));
139 sb.append(byteToChar(b2));
140
141 // Add "==" to the output to make it a multiple of 4 Base64 characters
142 sb.append("==");
143 }
144 return index;
145 }
146
147 private int twoBytesToInt(byte[] data, int byteArrayIndex) {
148 int byteTriplet;
149 byteTriplet =
150 uByteToInt(data[byteArrayIndex++]);
151 byteTriplet <<= 8;
152 byteTriplet |= uByteToInt(data[byteArrayIndex++]);
153 return byteTriplet;
154 }
155
156 /**
157 * Convert a byte between 0 and 63 to its Base64 character equivalent
158 * @param b Byte value to be converted
159 * @return Base64 char value
160 */
161 private char byteToChar(byte b) {
162 if (b < Constants.LOWER_CASE_A_VALUE) {
163 return (char) ('A' + b);
164 }
165
166 if (b < Constants.ZERO_VALUE) {
167 return (char) ('a' + (b - Constants.LOWER_CASE_A_VALUE));
168 }
169
170 if (b < Constants.PLUS_VALUE) {
171 return (char) ('0' + (b - Constants.ZERO_VALUE));
172 }
173
174 if (b == Constants.PLUS_VALUE) {
175 return '+';
176 }
177
178 if (b == Constants.SLASH_VALUE) {
179 return '/';
180 }
181
182 throw
183 new IllegalArgumentException(
184 "Byte " +
185 new Integer(b) +
186 " is not a valid Base64 value");
187 }
188
189 /**
190 * Simple test method to make sure everything works correctly
191 * Creates 100 randomly sized arrays of random bytes, encodes them,
192 * decodes them, and checks to make sure the result matches the input
193 */
194 public static void main(String args[]) throws Exception {
195 testCodec();
196
197 }
198
199 private static void testCodec() {
200 // sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
201 // sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
202
203 for (int j = 0; j < 100; j++)
204 test(new BASE64Encoder(), new BASE64Decoder());
205
206 }
207
208 private static void test(BASE64Encoder encoder, BASE64Decoder decoder) {
209 byte test[] = new byte[(int) (100000 * Math.random())];
210 for (int i = 0; i < test.length; i++) {
211 test[i] = (byte) (256 * Math.random());
212 }
213
214 String string = encoder.encode(test);
215 byte result[] = decoder.decodeBuffer(string);
216
217 if (!Arrays.equals(test, result) || test.length != result.length) {
218 System.out.println("ARRAYS DO NOT MATCH!");
219 }
220 }
221 }
222