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 }