68
|
1 /*
|
|
2 Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com>
|
|
3
|
|
4 Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5 of this software and associated documentation files (the "Software"), to deal
|
|
6 in the Software without restriction, including without limitation the rights
|
|
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8 copies of the Software, and to permit persons to whom the Software is
|
|
9 furnished to do so, subject to the following conditions:
|
|
10
|
|
11 The above copyright notice and this permission notice shall be included in
|
|
12 all copies or substantial portions of the Software.
|
|
13
|
|
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20 THE SOFTWARE.
|
|
21 */
|
|
22
|
|
23 package fschmidt.util.java;
|
|
24
|
|
25 /**
|
|
26 * Base64 encoding and decoding (MIME spec RFC 1521).
|
|
27 */
|
|
28 public class Base64 {
|
|
29
|
|
30 /**
|
|
31 * Stand alone test
|
|
32 */
|
|
33 public static void main(String[] args)
|
|
34 throws Exception {
|
|
35 if (args.length < 2) {
|
|
36 System.out.println("Usage: Base64 <encode/decode> <inString>");
|
|
37 System.exit(0);
|
|
38 }
|
|
39
|
|
40 String inString = args[1];
|
|
41 System.out.println("InString = " + inString);
|
|
42
|
|
43 if (args[0].equalsIgnoreCase("encode")) {
|
|
44
|
|
45 String encodedString = Base64.encode(inString.getBytes());
|
|
46 System.out.println("EncodedString = " + encodedString);
|
|
47
|
|
48 } else {
|
|
49 String outString = new String(Base64.decode(inString));
|
|
50 System.out.println("DecodedString = " + outString);
|
|
51 }
|
|
52 }
|
|
53
|
|
54 /**
|
|
55 * Base64-encode binary data.
|
|
56 *
|
|
57 * @param bytes A buffer holding the data to encode.
|
|
58 * @return String String containing the encoded data
|
|
59 */
|
|
60 public static String encode(byte[] bytes) {
|
|
61 return encode(bytes, false);
|
|
62 }
|
|
63
|
|
64 /**
|
|
65 * base64-encode binary data.
|
|
66 *
|
|
67 * @param bytes A buffer holding the data to encode.
|
|
68 * @param lineBreaks true if the encoded data will be broken
|
|
69 * into 64-character lines with CRLF pairs.
|
|
70 * @return String String containing the encoded data
|
|
71 */
|
|
72 public static String encode(byte[] bytes,
|
|
73 boolean lineBreaks) {
|
|
74
|
|
75 /* slightly bigger buffer than we usually need */
|
|
76 StringBuilder buf= new StringBuilder((int)(bytes.length * 1.4));
|
|
77
|
|
78 int temp = 0; /* holder for the current group of 24 bits */
|
|
79 int charsWritten = 0; /* count of char written (to current line if lineBreaks == true) */
|
|
80 int bytesRead = 0; /* count of bytes read in the current 24-bit group */
|
|
81 for(int index = 0; index < bytes.length; ++index) {
|
|
82
|
|
83 temp <<= 8;
|
|
84 temp |= (((int)bytes[index]) & EIGHT_BITS);
|
|
85 ++bytesRead;
|
|
86 if(bytesRead == 3) {
|
|
87 /* The lower 3 bytes of temp now hold the 24 bits of a
|
|
88 * translation group. Write them out as 4 characters
|
|
89 * that represent 6 bits each.
|
|
90 */
|
|
91 buf.append(valueToChar[(temp >> 18) & SIX_BITS]);
|
|
92 buf.append(valueToChar[(temp >> 12) & SIX_BITS]);
|
|
93 buf.append(valueToChar[(temp >> 6) & SIX_BITS]);
|
|
94 buf.append(valueToChar[(temp >> 0) & SIX_BITS]);
|
|
95 temp = 0;
|
|
96 bytesRead = 0;
|
|
97 charsWritten += 4;
|
|
98 if(lineBreaks && charsWritten >= 64) {
|
|
99 buf.append(CRLF);
|
|
100 charsWritten = 0;
|
|
101 }
|
|
102 }
|
|
103 }
|
|
104
|
|
105 // The input byte[] is exhaused.
|
|
106 // If there were fewer than 3 bytes in the last translation group,
|
|
107 // we must complete the output and pad it to a multiple of 4 chars.
|
|
108
|
|
109 switch(bytesRead){
|
|
110 case 1:
|
|
111 // The low 8 bits of temp take 2 characters to represent.
|
|
112 buf.append(valueToChar[(temp >> 2) & SIX_BITS]);
|
|
113 buf.append(valueToChar[(temp << 4) & SIX_BITS]);
|
|
114 buf.append('='); // padding
|
|
115 buf.append('='); // padding
|
|
116 if(lineBreaks) {
|
|
117 buf.append(CRLF);
|
|
118 }
|
|
119 break;
|
|
120
|
|
121 case 2:
|
|
122 // The low 16 bits of temp take 3 characters to represent
|
|
123 buf.append(valueToChar[(temp >> 10) & SIX_BITS]);
|
|
124 buf.append(valueToChar[(temp >> 4) & SIX_BITS]);
|
|
125 buf.append(valueToChar[(temp << 2) & SIX_BITS]);
|
|
126 buf.append('='); // padding
|
|
127 if(lineBreaks){
|
|
128 buf.append(CRLF);
|
|
129 }
|
|
130 break;
|
|
131
|
|
132 default:
|
|
133 // There were exactly 3 bytes in the last group. No padding needed.
|
|
134 break;
|
|
135 }
|
|
136 if(charsWritten > 0 && lineBreaks) {
|
|
137 buf.append(CRLF);
|
|
138 }
|
|
139 return buf.toString();
|
|
140 }
|
|
141
|
|
142 /**
|
|
143 * Decode a base64-encoded string into binary data.<P>
|
|
144 *
|
|
145 * Ignores (skips over) carriage-return and line-feed characters.
|
|
146 * Stops decoding when it encounters the base64 pad character ('=')
|
|
147 * or when characters are encountered that are not MIME/base64 numerals.
|
|
148 *
|
|
149 * @param mimeStr a String containing the base64-encoded data
|
|
150 * @return a byte[] containing the decoded binary data.
|
|
151 * @exception RuntimeException if the mimeStr parameter violates
|
|
152 * the base64 encoding rules.
|
|
153 */
|
|
154 public static byte[] decode(String mimeStr) throws RuntimeException {
|
|
155
|
|
156 int inputLength = mimeStr.length();
|
|
157 byte decoded[] = new byte[inputLength];
|
|
158 int temp = 0; // accumulator for 24-bit translation group
|
|
159 int charsRead = 0; // characters read in current translation group
|
|
160 int bytesWritten = 0; // count of bytes written into decoded[]
|
|
161 char currChar; // current character from mimeStr
|
|
162 byte currByte; // base64 value represented by currChar
|
|
163 int equalCount = 0; // The number of '=' characters we've encountered.
|
|
164
|
|
165 for (int i = 0; i < inputLength; ++i) {
|
|
166 currChar = mimeStr.charAt(i);
|
|
167 if (currChar == CR || currChar == LF) {
|
|
168 //Skip over carriage return or line-feed character.
|
|
169 continue;
|
|
170 }
|
|
171 try {
|
|
172 currByte = charToValue[(byte)currChar];
|
|
173 } catch(ArrayIndexOutOfBoundsException obe) {
|
|
174 currByte = BAD_VAL;
|
|
175 }
|
|
176 if (currByte == BAD_VAL) {
|
|
177 if (charsRead == 0) {
|
|
178 /*
|
|
179 * First character of a translation group is a
|
|
180 * non-base64 character. We have presumably
|
|
181 * reached the end of base64 encoded data.
|
|
182 */
|
|
183 break;
|
|
184 } else if (currChar == '=' & charsRead >= 2) {
|
|
185 // Only the 3d and 4th characters may be '='
|
|
186 currByte = 0;
|
|
187 ++equalCount;
|
|
188 } else {
|
|
189 //String[] errArgs = new String[]{"Base64"};
|
|
190 throw new InvalidCharEncodedStringException(); //RuntimeException("E_INVALID_CHAR_ENCODED_STRING");
|
|
191 }
|
|
192 }
|
|
193 temp <<= 6;
|
|
194 temp |= currByte;
|
|
195 ++charsRead;
|
|
196 if (charsRead == 4) {
|
|
197 /*
|
|
198 * temp has all 24 bits of the translation group,
|
|
199 * write the bytes to decode[]
|
|
200 */
|
|
201 decoded[bytesWritten++]= (byte)(temp >> 16);
|
|
202 if(equalCount < 2) {
|
|
203 decoded[bytesWritten++]= (byte)(temp >> 8);
|
|
204 if(equalCount < 1) {
|
|
205 decoded[bytesWritten++]= (byte)(temp >> 0);
|
|
206 }
|
|
207 }
|
|
208 charsRead = 0;
|
|
209 temp = 0;
|
|
210 if (equalCount > 0) {
|
|
211 // Encoded data is not permitted after an '=', we must be done.
|
|
212 break;
|
|
213 }
|
|
214 }
|
|
215 } // end for == end of string
|
|
216
|
|
217 // Trim array to exact length
|
|
218 byte result[] = new byte[bytesWritten];
|
|
219 System.arraycopy(decoded, 0, result, 0, bytesWritten);
|
|
220 return result;
|
|
221 }
|
|
222
|
|
223 private static final byte BAD_VAL = (byte)0xFF;
|
|
224
|
|
225 private static final int SIX_BITS = 0x3F;
|
|
226 private static final int EIGHT_BITS = 0xFF;
|
|
227
|
|
228 private static final char CR = '\r';
|
|
229 private static final char LF = '\n';
|
|
230
|
|
231 private static final String CRLF = "\r\n";
|
|
232
|
|
233 /**
|
|
234 * Value to be encoded is an index into this array, so the
|
|
235 * character representation of byte value = valueToChar[value]
|
|
236 */
|
|
237 private static final char valueToChar[] = {
|
|
238 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
|
239 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
|
240 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
|
241 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
|
242 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
|
243 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
|
244 'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
|
245 '4', '5', '6', '7', '8', '9', '+', '/'
|
|
246 };
|
|
247
|
|
248 /**
|
|
249 * The ASCII value of a character is an index into this array, so that
|
|
250 * decoded byte value = charToValue[(byte)(character)]. <P>
|
|
251 *
|
|
252 * Characters that are not valid base64 numerals are detected
|
|
253 * one of two ways: either the expression above yeilds BAD_VAL
|
|
254 * or the array access gives an ArrayIndexOutOfBoundsException. <P>
|
|
255 *
|
|
256 */
|
|
257 private static final byte charToValue[] = {
|
|
258 BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL,
|
|
259 BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL,
|
|
260 BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL,
|
|
261 BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL,
|
|
262 BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL,
|
|
263 BAD_VAL, BAD_VAL, BAD_VAL, 62, BAD_VAL, BAD_VAL, BAD_VAL, 63,
|
|
264 52, 53, 54, 55, 56, 57, 58, 59,
|
|
265 60, 61, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL,
|
|
266 BAD_VAL, 0, 1, 2, 3, 4, 5, 6,
|
|
267 7, 8, 9, 10, 11, 12, 13, 14,
|
|
268 15, 16, 17, 18, 19, 20, 21, 22,
|
|
269 23, 24, 25, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL,
|
|
270 BAD_VAL, 26, 27, 28, 29, 30, 31, 32,
|
|
271 33, 34, 35, 36, 37, 38, 39, 40,
|
|
272 41, 42, 43, 44, 45, 46, 47, 48,
|
|
273 49, 50, 51, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL
|
|
274 };
|
|
275
|
|
276 public static final class InvalidCharEncodedStringException extends RuntimeException {}
|
|
277
|
|
278 }
|