1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module hunt.shiro.codec.Base64; 20 21 import hunt.Exceptions; 22 23 24 /** 25 * Provides <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a> encoding and decoding as defined by 26 * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>. 27 * <p/> 28 * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose 29 * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein. 30 * <p/> 31 * This class was borrowed from Apache Commons Codec SVN repository (rev. 618419) with modifications 32 * to enable Base64 conversion without a full dependency on Commons Codec. We didn't want to reinvent the wheel of 33 * great work they've done, but also didn't want to force every Shiro user to depend on the commons-codec.jar 34 * <p/> 35 * As per the Apache 2.0 license, the original copyright notice and all author and copyright information have 36 * remained in tact. 37 * 38 * @see <a href="http://en.wikipedia.org/wiki/Base64">Wikipedia: Base 64</a> 39 * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> 40 * @since 0.9 41 */ 42 class Base64 { 43 44 /** 45 * Chunk size per RFC 2045 section 6.8. 46 * <p/> 47 * The character limit does not count the trailing CRLF, but counts all other characters, including any 48 * equal signs. 49 * 50 * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a> 51 */ 52 enum int CHUNK_SIZE = 76; 53 54 /** 55 * Chunk separator per RFC 2045 section 2.1. 56 * 57 * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a> 58 */ 59 enum byte[] CHUNK_SEPARATOR = cast(byte[])"\r\n".dup; 60 61 /** 62 * The base length. 63 */ 64 private enum int BASELENGTH = 255; 65 66 /** 67 * Lookup length. 68 */ 69 private enum int LOOKUPLENGTH = 64; 70 71 /** 72 * Used to calculate the number of bits in a byte. 73 */ 74 private enum int EIGHTBIT = 8; 75 76 /** 77 * Used when encoding something which has fewer than 24 bits. 78 */ 79 private enum int SIXTEENBIT = 16; 80 81 /** 82 * Used to determine how many bits data contains. 83 */ 84 private enum int TWENTYFOURBITGROUP = 24; 85 86 /** 87 * Used to get the number of Quadruples. 88 */ 89 private enum int FOURBYTE = 4; 90 91 /** 92 * Used to test the sign of a byte. 93 */ 94 private enum int SIGN = -128; 95 96 /** 97 * Byte used to pad output. 98 */ 99 private enum byte PAD = '='; 100 101 // /** 102 // * Contains the Base64 values <code>0</code> through <code>63</code> accessed by using character encodings as 103 // * indices. 104 // * <p/> 105 // * <p>For example, <code>base64Alphabet['+']</code> returns <code>62</code>.</p> 106 // * <p/> 107 // * <p>The value of undefined encodings is <code>-1</code>.</p> 108 // */ 109 // private static final byte[] base64Alphabet = new byte[BASELENGTH]; 110 111 // /** 112 // * <p>Contains the Base64 encodings <code>A</code> through <code>Z</code>, followed by <code>a</code> through 113 // * <code>z</code>, followed by <code>0</code> through <code>9</code>, followed by <code>+</code>, and 114 // * <code>/</code>.</p> 115 // * <p/> 116 // * <p>This array is accessed by using character values as indices.</p> 117 // * <p/> 118 // * <p>For example, <code>lookUpBase64Alphabet[62] </code> returns <code>'+'</code>.</p> 119 // */ 120 // private static final byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH]; 121 122 // // Populating the lookup and character arrays 123 124 // static { 125 // for (int i = 0; i < BASELENGTH; i++) { 126 // base64Alphabet[i] = (byte) -1; 127 // } 128 // for (int i = 'Z'; i >= 'A'; i--) { 129 // base64Alphabet[i] = (byte) (i - 'A'); 130 // } 131 // for (int i = 'z'; i >= 'a'; i--) { 132 // base64Alphabet[i] = (byte) (i - 'a' + 26); 133 // } 134 // for (int i = '9'; i >= '0'; i--) { 135 // base64Alphabet[i] = (byte) (i - '0' + 52); 136 // } 137 138 // base64Alphabet['+'] = 62; 139 // base64Alphabet['/'] = 63; 140 141 // for (int i = 0; i <= 25; i++) { 142 // lookUpBase64Alphabet[i] = (byte) ('A' + i); 143 // } 144 145 // for (int i = 26, j = 0; i <= 51; i++, j++) { 146 // lookUpBase64Alphabet[i] = (byte) ('a' + j); 147 // } 148 149 // for (int i = 52, j = 0; i <= 61; i++, j++) { 150 // lookUpBase64Alphabet[i] = (byte) ('0' + j); 151 // } 152 153 // lookUpBase64Alphabet[62] = (byte) '+'; 154 // lookUpBase64Alphabet[63] = (byte) '/'; 155 // } 156 157 /** 158 * Returns whether or not the <code>octet</code> is in the base 64 alphabet. 159 * 160 * @param octect The value to test 161 * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise. 162 */ 163 // private static bool isBase64(byte octect) { 164 // if (octect == PAD) { 165 // return true; 166 // } else //noinspection RedundantIfStatement 167 // if (octect < 0 || base64Alphabet[octect] == -1) { 168 // return false; 169 // } else { 170 // return true; 171 // } 172 // } 173 174 // /** 175 // * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. 176 // * 177 // * @param arrayOctect byte array to test 178 // * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is 179 // * empty; false, otherwise 180 // */ 181 // static bool isBase64(byte[] arrayOctect) { 182 183 // arrayOctect = discardWhitespace(arrayOctect); 184 185 // int length = arrayOctect.length; 186 // if (length == 0) { 187 // // shouldn't a 0 length array be valid base64 data? 188 // // return false; 189 // return true; 190 // } 191 // for (int i = 0; i < length; i++) { 192 // if (!isBase64(arrayOctect[i])) { 193 // return false; 194 // } 195 // } 196 // return true; 197 // } 198 199 // /** 200 // * Discards any whitespace from a base-64 encoded block. 201 // * 202 // * @param data The base-64 encoded data to discard the whitespace from. 203 // * @return The data, less whitespace (see RFC 2045). 204 // */ 205 // static byte[] discardWhitespace(byte[] data) { 206 // byte groomedData[] = new byte[data.length]; 207 // int bytesCopied = 0; 208 209 // for (byte aByte : data) { 210 // switch (aByte) { 211 // case (byte) ' ': 212 // case (byte) '\n': 213 // case (byte) '\r': 214 // case (byte) '\t': 215 // break; 216 // default: 217 // groomedData[bytesCopied++] = aByte; 218 // } 219 // } 220 221 // byte packedData[] = new byte[bytesCopied]; 222 223 // System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); 224 225 // return packedData; 226 // } 227 228 /** 229 * Base64 encodes the specified byte array and then encodes it as a string using Shiro's preferred character 230 * encoding (UTF-8). 231 * 232 * @param bytes the byte array to Base64 encode. 233 * @return a UTF-8 encoded string of the resulting Base64 encoded byte array. 234 */ 235 static string encodeToString(byte[] bytes) { 236 byte[] encoded = encode(bytes); 237 return cast(string)encoded; 238 } 239 240 /** 241 * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks 242 * 243 * @param binaryData binary data to encodeToChars 244 * @return Base64 characters chunked in 76 character blocks 245 */ 246 static byte[] encodeChunked(byte[] binaryData) { 247 return encode(binaryData, true); 248 } 249 250 /** 251 * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet. 252 * 253 * @param pArray a byte array containing binary data 254 * @return A byte array containing only Base64 character data 255 */ 256 static byte[] encode(byte[] pArray) { 257 return encode(pArray, false); 258 } 259 260 /** 261 * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. 262 * 263 * @param binaryData Array containing binary data to encodeToChars. 264 * @param isChunked if <code>true</code> this encoder will chunk the base64 output into 76 character blocks 265 * @return Base64-encoded data. 266 * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} 267 */ 268 static byte[] encode(byte[] binaryData, bool isChunked) { 269 long binaryDataLength = binaryData.length; 270 long lengthDataBits = binaryDataLength * EIGHTBIT; 271 long fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; 272 long tripletCount = lengthDataBits / TWENTYFOURBITGROUP; 273 long encodedDataLengthLong; 274 int chunckCount = 0; 275 276 implementationMissing(false); 277 return null; 278 279 // if (fewerThan24bits != 0) { 280 // // data not divisible by 24 bit 281 // encodedDataLengthLong = (tripletCount + 1) * 4; 282 // } else { 283 // // 16 or 8 bit 284 // encodedDataLengthLong = tripletCount * 4; 285 // } 286 287 // // If the output is to be "chunked" into 76 character sections, 288 // // for compliance with RFC 2045 MIME, then it is important to 289 // // allow for extra length to account for the separator(s) 290 // if (isChunked) { 291 292 // chunckCount = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math 293 // .ceil((float) encodedDataLengthLong / CHUNK_SIZE)); 294 // encodedDataLengthLong += chunckCount * CHUNK_SEPARATOR.length; 295 // } 296 297 // if (encodedDataLengthLong > Integer.MAX_VALUE) { 298 // throw new IllegalArgumentException( 299 // "Input array too big, output array would be bigger than Integer.MAX_VALUE=" ~ Integer.MAX_VALUE); 300 // } 301 // int encodedDataLength = (int) encodedDataLengthLong; 302 // byte encodedData[] = new byte[encodedDataLength]; 303 304 // byte k, l, b1, b2, b3; 305 306 // int encodedIndex = 0; 307 // int dataIndex; 308 // int i; 309 // int nextSeparatorIndex = CHUNK_SIZE; 310 // int chunksSoFar = 0; 311 312 // // log.debug("number of triplets = " ~ numberTriplets); 313 // for (i = 0; i < tripletCount; i++) { 314 // dataIndex = i * 3; 315 // b1 = binaryData[dataIndex]; 316 // b2 = binaryData[dataIndex + 1]; 317 // b3 = binaryData[dataIndex + 2]; 318 319 // // log.debug("b1= " ~ b1 +", b2= " ~ b2 ~ ", b3= " ~ b3); 320 321 // l = (byte) (b2 & 0x0f); 322 // k = (byte) (b1 & 0x03); 323 324 // byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); 325 // byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); 326 // byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); 327 328 // encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; 329 // // log.debug( "val2 = " ~ val2 ); 330 // // log.debug( "k4 = " ~ (k<<4) ); 331 // // log.debug( "vak = " ~ (val2 | (k<<4)) ); 332 // encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)]; 333 // encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3]; 334 // encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f]; 335 336 // encodedIndex += 4; 337 338 // // If we are chunking, let's put a chunk separator down. 339 // if (isChunked) { 340 // // this assumes that CHUNK_SIZE % 4 == 0 341 // if (encodedIndex == nextSeparatorIndex) { 342 // System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length); 343 // chunksSoFar++; 344 // nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length); 345 // encodedIndex += CHUNK_SEPARATOR.length; 346 // } 347 // } 348 // } 349 350 // // form integral number of 6-bit groups 351 // dataIndex = i * 3; 352 353 // if (fewerThan24bits == EIGHTBIT) { 354 // b1 = binaryData[dataIndex]; 355 // k = (byte) (b1 & 0x03); 356 // // log.debug("b1=" ~ b1); 357 // // log.debug("b1<<2 = " ~ (b1>>2) ); 358 // byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); 359 // encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; 360 // encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4]; 361 // encodedData[encodedIndex + 2] = PAD; 362 // encodedData[encodedIndex + 3] = PAD; 363 // } else if (fewerThan24bits == SIXTEENBIT) { 364 365 // b1 = binaryData[dataIndex]; 366 // b2 = binaryData[dataIndex + 1]; 367 // l = (byte) (b2 & 0x0f); 368 // k = (byte) (b1 & 0x03); 369 370 // byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); 371 // byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); 372 373 // encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; 374 // encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)]; 375 // encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2]; 376 // encodedData[encodedIndex + 3] = PAD; 377 // } 378 379 // if (isChunked) { 380 // // we also add a separator to the end of the final chunk. 381 // if (chunksSoFar < chunckCount) { 382 // System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length, 383 // CHUNK_SEPARATOR.length); 384 // } 385 // } 386 387 // return encodedData; 388 } 389 390 // /** 391 // * Converts the specified UTF-8 Base64 encoded string and decodes it to a resultant UTF-8 encoded string. 392 // * 393 // * @param base64Encoded a UTF-8 Base64 encoded string 394 // * @return the decoded string, UTF-8 encoded. 395 // */ 396 // static string decodeToString(string base64Encoded) { 397 // byte[] encodedBytes = CodecSupport.toBytes(base64Encoded); 398 // return decodeToString(encodedBytes); 399 // } 400 401 // /** 402 // * Decodes the specified Base64 encoded byte array and returns the decoded result as a UTF-8 encoded. 403 // * 404 // * @param base64Encoded a Base64 encoded byte array 405 // * @return the decoded string, UTF-8 encoded. 406 // */ 407 // static string decodeToString(byte[] base64Encoded) { 408 // byte[] decoded = decode(base64Encoded); 409 // return CodecSupport.toString(decoded); 410 // } 411 412 /** 413 * Converts the specified UTF-8 Base64 encoded string and decodes it to a raw Base64 decoded byte array. 414 * 415 * @param base64Encoded a UTF-8 Base64 encoded string 416 * @return the raw Base64 decoded byte array. 417 */ 418 static byte[] decode(string base64Encoded) { 419 return decode(cast(byte[])base64Encoded); 420 } 421 422 /** 423 * Decodes Base64 data into octets 424 * 425 * @param base64Data Byte array containing Base64 data 426 * @return Array containing decoded data. 427 */ 428 static byte[] decode(byte[] base64Data) { 429 implementationMissing(false); 430 return null; 431 // // RFC 2045 requires that we discard ALL non-Base64 characters 432 // base64Data = discardNonBase64(base64Data); 433 434 // // handle the edge case, so we don't have to worry about it later 435 // if (base64Data.length == 0) { 436 // return new byte[0]; 437 // } 438 439 // int numberQuadruple = base64Data.length / FOURBYTE; 440 // byte decodedData[]; 441 // byte b1, b2, b3, b4, marker0, marker1; 442 443 // // Throw away anything not in base64Data 444 445 // int encodedIndex = 0; 446 // int dataIndex; 447 // { 448 // // this sizes the output array properly - rlw 449 // int lastData = base64Data.length; 450 // // ignore the '=' padding 451 // while (base64Data[lastData - 1] == PAD) { 452 // if (--lastData == 0) { 453 // return new byte[0]; 454 // } 455 // } 456 // decodedData = new byte[lastData - numberQuadruple]; 457 // } 458 459 // for (int i = 0; i < numberQuadruple; i++) { 460 // dataIndex = i * 4; 461 // marker0 = base64Data[dataIndex + 2]; 462 // marker1 = base64Data[dataIndex + 3]; 463 464 // b1 = base64Alphabet[base64Data[dataIndex]]; 465 // b2 = base64Alphabet[base64Data[dataIndex + 1]]; 466 467 // if (marker0 != PAD && marker1 != PAD) { 468 // // No PAD e.g 3cQl 469 // b3 = base64Alphabet[marker0]; 470 // b4 = base64Alphabet[marker1]; 471 472 // decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); 473 // decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); 474 // decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); 475 // } else if (marker0 == PAD) { 476 // // Two PAD e.g. 3c[Pad][Pad] 477 // decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); 478 // } else { 479 // // One PAD e.g. 3cQ[Pad] 480 // b3 = base64Alphabet[marker0]; 481 // decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); 482 // decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); 483 // } 484 // encodedIndex += 3; 485 // } 486 // return decodedData; 487 } 488 489 // /** 490 // * Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any 491 // * characters outside of the base64 alphabet are to be ignored in base64 encoded data." 492 // * 493 // * @param data The base-64 encoded data to groom 494 // * @return The data, less non-base64 characters (see RFC 2045). 495 // */ 496 // static byte[] discardNonBase64(byte[] data) { 497 // byte groomedData[] = new byte[data.length]; 498 // int bytesCopied = 0; 499 500 // for (byte aByte : data) { 501 // if (isBase64(aByte)) { 502 // groomedData[bytesCopied++] = aByte; 503 // } 504 // } 505 506 // byte packedData[] = new byte[bytesCopied]; 507 508 // System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); 509 510 // return packedData; 511 // } 512 513 }