Mercurial Hosting > nabble
comparison src/fschmidt/util/java/Base64.java @ 68:00520880ad02
add fschmidt source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sun, 05 Oct 2025 17:24:15 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
67:9d0fefce6985 | 68:00520880ad02 |
---|---|
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 } |