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 } |
