001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2007-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util; 037 038 039 040import java.io.IOException; 041import java.text.ParseException; 042 043import static com.unboundid.util.UtilityMessages.*; 044 045 046 047/** 048 * This class provides methods for encoding and decoding data in base64 as 049 * defined in <A HREF="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</A>. It 050 * provides a relatively compact way of representing binary data using only 051 * printable characters. It uses a six-bit encoding mechanism in which every 052 * three bytes of raw data is converted to four bytes of base64-encoded data, 053 * which means that it only requires about a 33% increase in size (as compared 054 * with a hexadecimal representation, which requires a 100% increase in size). 055 * <BR><BR> 056 * Base64 encoding is used in LDIF processing as per 057 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A> to represent data 058 * that contains special characters or might otherwise be ambiguous. It is also 059 * used in a number of other areas (e.g., for the ASCII representation of 060 * certificates) where it is desirable to deal with a string containing only 061 * printable characters but the raw data may contain other characters outside of 062 * that range. 063 * <BR><BR> 064 * This class also provides support for the URL-safe variant (called base64url) 065 * as described in RFC 4648 section 5. This is nearly the same as base64, 066 * except that the '+' and '/' characters are replaced with '-' and '_', 067 * respectively. The padding may be omitted if the context makes the data size 068 * clear, but if padding is to be used then the URL-encoded "%3d" will be used 069 * instead of "=". 070 * <BR><BR> 071 * <H2>Example</H2> 072 * The following examples demonstrate the process for base64-encoding raw data, 073 * and for decoding a string containing base64-encoded data back to the raw 074 * data used to create it: 075 * <PRE> 076 * // Base64-encode some raw data: 077 * String base64String = Base64.encode(rawDataBytes); 078 * 079 * // Decode a base64 string back to raw data: 080 * byte[] decodedRawDataBytes; 081 * try 082 * { 083 * decodedRawDataBytes = Base64.decode(base64String); 084 * } 085 * catch (ParseException pe) 086 * { 087 * // The string did not represent a valid base64 encoding. 088 * decodedRawDataBytes = null; 089 * } 090 * </PRE> 091 */ 092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 093public final class Base64 094{ 095 /** 096 * The set of characters in the base64 alphabet. 097 */ 098 @NotNull private static final char[] BASE64_ALPHABET = 099 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + 100 "0123456789+/").toCharArray(); 101 102 103 104 /** 105 * The set of characters in the base64url alphabet. 106 */ 107 @NotNull private static final char[] BASE64URL_ALPHABET = 108 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + 109 "0123456789-_").toCharArray(); 110 111 112 113 /** 114 * Prevent this class from being instantiated. 115 */ 116 private Base64() 117 { 118 // No implementation is required. 119 } 120 121 122 123 /** 124 * Encodes the UTF-8 representation of the provided string in base64 format. 125 * 126 * @param data The raw data to be encoded. It must not be {@code null}. 127 * 128 * @return The base64-encoded representation of the provided data. 129 */ 130 @NotNull() 131 public static String encode(@NotNull final String data) 132 { 133 Validator.ensureNotNull(data); 134 135 return encode(StaticUtils.getBytes(data)); 136 } 137 138 139 140 /** 141 * Encodes the provided data in base64 format. 142 * 143 * @param data The raw data to be encoded. It must not be {@code null}. 144 * 145 * @return The base64-encoded representation of the provided data. 146 */ 147 @NotNull() 148 public static String encode(@NotNull final byte[] data) 149 { 150 Validator.ensureNotNull(data); 151 152 final StringBuilder buffer = new StringBuilder(4*data.length/3+1); 153 encode(BASE64_ALPHABET, data, 0, data.length, buffer, "="); 154 return buffer.toString(); 155 } 156 157 158 159 /** 160 * Appends a base64-encoded version of the contents of the provided buffer 161 * (using a UTF-8 representation) to the given buffer. 162 * 163 * @param data The raw data to be encoded. It must not be {@code null}. 164 * @param buffer The buffer to which the base64-encoded data is to be 165 * written. 166 */ 167 public static void encode(@NotNull final String data, 168 @NotNull final StringBuilder buffer) 169 { 170 Validator.ensureNotNull(data); 171 172 encode(StaticUtils.getBytes(data), buffer); 173 } 174 175 176 177 /** 178 * Appends a base64-encoded version of the contents of the provided buffer 179 * (using a UTF-8 representation) to the given buffer. 180 * 181 * @param data The raw data to be encoded. It must not be {@code null}. 182 * @param buffer The buffer to which the base64-encoded data is to be 183 * written. 184 */ 185 public static void encode(@NotNull final String data, 186 @NotNull final ByteStringBuffer buffer) 187 { 188 Validator.ensureNotNull(data); 189 190 encode(StaticUtils.getBytes(data), buffer); 191 } 192 193 194 195 /** 196 * Appends a base64-encoded representation of the provided data to the given 197 * buffer. 198 * 199 * @param data The raw data to be encoded. It must not be {@code null}. 200 * @param buffer The buffer to which the base64-encoded data is to be 201 * written. 202 */ 203 public static void encode(@NotNull final byte[] data, 204 @NotNull final StringBuilder buffer) 205 { 206 encode(BASE64_ALPHABET, data, 0, data.length, buffer, "="); 207 } 208 209 210 211 /** 212 * Appends a base64-encoded representation of the provided data to the given 213 * buffer. 214 * 215 * @param data The array containing the raw data to be encoded. It must 216 * not be {@code null}. 217 * @param off The offset in the array at which the data to encode begins. 218 * @param length The number of bytes to be encoded. 219 * @param buffer The buffer to which the base64-encoded data is to be 220 * written. 221 */ 222 public static void encode(@NotNull final byte[] data, final int off, 223 final int length, 224 @NotNull final StringBuilder buffer) 225 { 226 encode(BASE64_ALPHABET, data, off, length, buffer, "="); 227 } 228 229 230 231 /** 232 * Appends a base64-encoded representation of the provided data to the given 233 * buffer. 234 * 235 * @param data The raw data to be encoded. It must not be {@code null}. 236 * @param buffer The buffer to which the base64-encoded data is to be 237 * written. 238 */ 239 public static void encode(@NotNull final byte[] data, 240 @NotNull final ByteStringBuffer buffer) 241 { 242 encode(BASE64_ALPHABET, data, 0, data.length, buffer, "="); 243 } 244 245 246 247 /** 248 * Appends a base64-encoded representation of the provided data to the given 249 * buffer. 250 * 251 * @param data The raw data to be encoded. It must not be {@code null}. 252 * @param off The offset in the array at which the data to encode begins. 253 * @param length The number of bytes to be encoded. 254 * @param buffer The buffer to which the base64-encoded data is to be 255 * written. 256 */ 257 public static void encode(@NotNull final byte[] data, final int off, 258 final int length, 259 @NotNull final ByteStringBuffer buffer) 260 { 261 encode(BASE64_ALPHABET, data, off, length, buffer, "="); 262 } 263 264 265 266 /** 267 * Retrieves a base64url-encoded representation of the provided data to the 268 * given buffer. 269 * 270 * @param data The raw data to be encoded. It must not be {@code null}. 271 * @param pad Indicates whether to pad the URL if necessary. Padding will 272 * use "%3d", as the URL-escaped representation of the equal 273 * sign. 274 * 275 * @return A base64url-encoded representation of the provided data to the 276 * given buffer. 277 */ 278 @NotNull() 279 public static String urlEncode(@NotNull final String data, final boolean pad) 280 { 281 return urlEncode(StaticUtils.getBytes(data), pad); 282 } 283 284 285 286 /** 287 * Retrieves a base64url-encoded representation of the provided data to the 288 * given buffer. 289 * 290 * @param data The raw data to be encoded. It must not be {@code null}. 291 * @param buffer The buffer to which the base64-encoded data is to be 292 * written. 293 * @param pad Indicates whether to pad the URL if necessary. Padding 294 * will use "%3d", as the URL-escaped representation of the 295 * equal sign. 296 */ 297 public static void urlEncode(@NotNull final String data, 298 @NotNull final StringBuilder buffer, 299 final boolean pad) 300 { 301 final byte[] dataBytes = StaticUtils.getBytes(data); 302 encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer, 303 (pad ? "%3d" : null)); 304 } 305 306 307 308 /** 309 * Retrieves a base64url-encoded representation of the provided data to the 310 * given buffer. 311 * 312 * @param data The raw data to be encoded. It must not be {@code null}. 313 * @param buffer The buffer to which the base64-encoded data is to be 314 * written. 315 * @param pad Indicates whether to pad the URL if necessary. Padding 316 * will use "%3d", as the URL-escaped representation of the 317 * equal sign. 318 */ 319 public static void urlEncode(@NotNull final String data, 320 @NotNull final ByteStringBuffer buffer, 321 final boolean pad) 322 { 323 final byte[] dataBytes = StaticUtils.getBytes(data); 324 encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer, 325 (pad ? "%3d" : null)); 326 } 327 328 329 330 /** 331 * Retrieves a base64url-encoded representation of the provided data to the 332 * given buffer. 333 * 334 * @param data The raw data to be encoded. It must not be {@code null}. 335 * @param pad Indicates whether to pad the URL if necessary. Padding will 336 * use "%3d", as the URL-escaped representation of the equal 337 * sign. 338 * 339 * @return A base64url-encoded representation of the provided data to the 340 * given buffer. 341 */ 342 @NotNull() 343 public static String urlEncode(@NotNull final byte[] data, final boolean pad) 344 { 345 final StringBuilder buffer = new StringBuilder(4*data.length/3+6); 346 encode(BASE64URL_ALPHABET, data, 0, data.length, buffer, 347 (pad ? "%3d" : null)); 348 return buffer.toString(); 349 } 350 351 352 353 /** 354 * Appends a base64url-encoded representation of the provided data to the 355 * given buffer. 356 * 357 * @param data The raw data to be encoded. It must not be {@code null}. 358 * @param off The offset in the array at which the data to encode begins. 359 * @param length The number of bytes to be encoded. 360 * @param buffer The buffer to which the base64-encoded data is to be 361 * written. 362 * @param pad Indicates whether to pad the URL if necessary. Padding 363 * will use "%3d", as the URL-escaped representation of the 364 * equal sign. 365 */ 366 public static void urlEncode(@NotNull final byte[] data, final int off, 367 final int length, 368 @NotNull final StringBuilder buffer, 369 final boolean pad) 370 { 371 encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null)); 372 } 373 374 375 376 /** 377 * Appends a base64url-encoded representation of the provided data to the 378 * given buffer. 379 * 380 * @param data The raw data to be encoded. It must not be {@code null}. 381 * @param off The offset in the array at which the data to encode begins. 382 * @param length The number of bytes to be encoded. 383 * @param buffer The buffer to which the base64-encoded data is to be 384 * written. 385 * @param pad Indicates whether to pad the URL if necessary. Padding 386 * will use "%3d", as the URL-escaped representation of the 387 * equal sign. 388 */ 389 public static void urlEncode(@NotNull final byte[] data, final int off, 390 final int length, 391 @NotNull final ByteStringBuffer buffer, 392 final boolean pad) 393 { 394 encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null)); 395 } 396 397 398 399 /** 400 * Appends a base64-encoded representation of the provided data to the given 401 * buffer. 402 * 403 * @param alphabet The alphabet of base64 characters to use for the 404 * encoding. 405 * @param data The raw data to be encoded. It must not be {@code null}. 406 * @param off The offset in the array at which the data to encode 407 * begins. 408 * @param length The number of bytes to be encoded. 409 * @param buffer The buffer to which the base64-encoded data is to be 410 * written. 411 * @param padStr The string to use for padding. It may be {@code null} if 412 * no padding should be applied. 413 */ 414 private static void encode(@NotNull final char[] alphabet, 415 @NotNull final byte[] data, 416 final int off, final int length, 417 @NotNull final Appendable buffer, 418 @Nullable final String padStr) 419 { 420 Validator.ensureNotNull(data); 421 Validator.ensureTrue(data.length >= off); 422 Validator.ensureTrue(data.length >= (off+length)); 423 424 if (length == 0) 425 { 426 return; 427 } 428 429 try 430 { 431 int pos = off; 432 for (int i=0; i < (length / 3); i++) 433 { 434 final int intValue = ((data[pos++] & 0xFF) << 16) | 435 ((data[pos++] & 0xFF) << 8) | 436 (data[pos++] & 0xFF); 437 438 buffer.append(alphabet[(intValue >> 18) & 0x3F]); 439 buffer.append(alphabet[(intValue >> 12) & 0x3F]); 440 buffer.append(alphabet[(intValue >> 6) & 0x3F]); 441 buffer.append(alphabet[intValue & 0x3F]); 442 } 443 444 switch ((off+length) - pos) 445 { 446 case 1: 447 int intValue = (data[pos] & 0xFF) << 16; 448 buffer.append(alphabet[(intValue >> 18) & 0x3F]); 449 buffer.append(alphabet[(intValue >> 12) & 0x3F]); 450 if (padStr != null) 451 { 452 buffer.append(padStr); 453 buffer.append(padStr); 454 } 455 return; 456 457 case 2: 458 intValue = ((data[pos++] & 0xFF) << 16) | ((data[pos] & 0xFF) << 8); 459 buffer.append(alphabet[(intValue >> 18) & 0x3F]); 460 buffer.append(alphabet[(intValue >> 12) & 0x3F]); 461 buffer.append(alphabet[(intValue >> 6) & 0x3F]); 462 if (padStr != null) 463 { 464 buffer.append(padStr); 465 } 466 return; 467 } 468 } 469 catch (final IOException ioe) 470 { 471 Debug.debugException(ioe); 472 473 // This should never happen. 474 throw new RuntimeException(ioe.getMessage(), ioe); 475 } 476 } 477 478 479 480 /** 481 * Decodes the contents of the provided base64-encoded string. 482 * 483 * @param data The base64-encoded string to decode. It must not be 484 * {@code null}. 485 * 486 * @return A byte array containing the decoded data. 487 * 488 * @throws ParseException If the contents of the provided string cannot be 489 * parsed as base64-encoded data. 490 */ 491 @NotNull() 492 public static byte[] decode(@NotNull final String data) 493 throws ParseException 494 { 495 Validator.ensureNotNull(data); 496 497 final int length = data.length(); 498 if (length == 0) 499 { 500 return StaticUtils.NO_BYTES; 501 } 502 503 if ((length % 4) != 0) 504 { 505 throw new ParseException(ERR_BASE64_DECODE_INVALID_LENGTH.get(), length); 506 } 507 508 int numBytes = 3 * (length / 4); 509 if (data.charAt(length-2) == '=') 510 { 511 numBytes -= 2; 512 } 513 else if (data.charAt(length-1) == '=') 514 { 515 numBytes--; 516 } 517 518 final byte[] b = new byte[numBytes]; 519 520 int stringPos = 0; 521 int arrayPos = 0; 522 while (stringPos < length) 523 { 524 int intValue = 0x00; 525 for (int i=0; i < 4; i++) 526 { 527 intValue <<= 6; 528 switch (data.charAt(stringPos++)) 529 { 530 case 'A': 531 intValue |= 0x00; 532 break; 533 case 'B': 534 intValue |= 0x01; 535 break; 536 case 'C': 537 intValue |= 0x02; 538 break; 539 case 'D': 540 intValue |= 0x03; 541 break; 542 case 'E': 543 intValue |= 0x04; 544 break; 545 case 'F': 546 intValue |= 0x05; 547 break; 548 case 'G': 549 intValue |= 0x06; 550 break; 551 case 'H': 552 intValue |= 0x07; 553 break; 554 case 'I': 555 intValue |= 0x08; 556 break; 557 case 'J': 558 intValue |= 0x09; 559 break; 560 case 'K': 561 intValue |= 0x0A; 562 break; 563 case 'L': 564 intValue |= 0x0B; 565 break; 566 case 'M': 567 intValue |= 0x0C; 568 break; 569 case 'N': 570 intValue |= 0x0D; 571 break; 572 case 'O': 573 intValue |= 0x0E; 574 break; 575 case 'P': 576 intValue |= 0x0F; 577 break; 578 case 'Q': 579 intValue |= 0x10; 580 break; 581 case 'R': 582 intValue |= 0x11; 583 break; 584 case 'S': 585 intValue |= 0x12; 586 break; 587 case 'T': 588 intValue |= 0x13; 589 break; 590 case 'U': 591 intValue |= 0x14; 592 break; 593 case 'V': 594 intValue |= 0x15; 595 break; 596 case 'W': 597 intValue |= 0x16; 598 break; 599 case 'X': 600 intValue |= 0x17; 601 break; 602 case 'Y': 603 intValue |= 0x18; 604 break; 605 case 'Z': 606 intValue |= 0x19; 607 break; 608 case 'a': 609 intValue |= 0x1A; 610 break; 611 case 'b': 612 intValue |= 0x1B; 613 break; 614 case 'c': 615 intValue |= 0x1C; 616 break; 617 case 'd': 618 intValue |= 0x1D; 619 break; 620 case 'e': 621 intValue |= 0x1E; 622 break; 623 case 'f': 624 intValue |= 0x1F; 625 break; 626 case 'g': 627 intValue |= 0x20; 628 break; 629 case 'h': 630 intValue |= 0x21; 631 break; 632 case 'i': 633 intValue |= 0x22; 634 break; 635 case 'j': 636 intValue |= 0x23; 637 break; 638 case 'k': 639 intValue |= 0x24; 640 break; 641 case 'l': 642 intValue |= 0x25; 643 break; 644 case 'm': 645 intValue |= 0x26; 646 break; 647 case 'n': 648 intValue |= 0x27; 649 break; 650 case 'o': 651 intValue |= 0x28; 652 break; 653 case 'p': 654 intValue |= 0x29; 655 break; 656 case 'q': 657 intValue |= 0x2A; 658 break; 659 case 'r': 660 intValue |= 0x2B; 661 break; 662 case 's': 663 intValue |= 0x2C; 664 break; 665 case 't': 666 intValue |= 0x2D; 667 break; 668 case 'u': 669 intValue |= 0x2E; 670 break; 671 case 'v': 672 intValue |= 0x2F; 673 break; 674 case 'w': 675 intValue |= 0x30; 676 break; 677 case 'x': 678 intValue |= 0x31; 679 break; 680 case 'y': 681 intValue |= 0x32; 682 break; 683 case 'z': 684 intValue |= 0x33; 685 break; 686 case '0': 687 intValue |= 0x34; 688 break; 689 case '1': 690 intValue |= 0x35; 691 break; 692 case '2': 693 intValue |= 0x36; 694 break; 695 case '3': 696 intValue |= 0x37; 697 break; 698 case '4': 699 intValue |= 0x38; 700 break; 701 case '5': 702 intValue |= 0x39; 703 break; 704 case '6': 705 intValue |= 0x3A; 706 break; 707 case '7': 708 intValue |= 0x3B; 709 break; 710 case '8': 711 intValue |= 0x3C; 712 break; 713 case '9': 714 intValue |= 0x3D; 715 break; 716 case '+': 717 intValue |= 0x3E; 718 break; 719 case '/': 720 intValue |= 0x3F; 721 break; 722 723 case '=': 724 switch (length - stringPos) 725 { 726 case 0: 727 // The string ended with a single equal sign, so there are only 728 // two bytes left. Shift the value eight bits to the right and 729 // read those two bytes. 730 intValue >>= 8; 731 b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF); 732 b[arrayPos] = (byte) (intValue & 0xFF); 733 return b; 734 735 case 1: 736 // The string ended with two equal signs, so there is only one 737 // byte left. Shift the value ten bits to the right and read 738 // that single byte. 739 intValue >>= 10; 740 b[arrayPos] = (byte) (intValue & 0xFF); 741 return b; 742 743 default: 744 throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_EQUAL.get( 745 (stringPos-1)), 746 (stringPos-1)); 747 } 748 749 default: 750 throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_CHAR.get( 751 data.charAt(stringPos-1)), 752 (stringPos-1)); 753 } 754 } 755 756 b[arrayPos++] = (byte) ((intValue >> 16) & 0xFF); 757 b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF); 758 b[arrayPos++] = (byte) (intValue & 0xFF); 759 } 760 761 return b; 762 } 763 764 765 766 /** 767 * Decodes the contents of the provided base64-encoded string to a string 768 * containing the raw data using the UTF-8 encoding. 769 * 770 * @param data The base64-encoded string to decode. It must not be 771 * {@code null}. 772 * 773 * @return A string containing the decoded data. 774 * 775 * @throws ParseException If the contents of the provided string cannot be 776 * parsed as base64-encoded data using the UTF-8 777 * encoding. 778 */ 779 @NotNull() 780 public static String decodeToString(@NotNull final String data) 781 throws ParseException 782 { 783 Validator.ensureNotNull(data); 784 785 final byte[] decodedBytes = decode(data); 786 return StaticUtils.toUTF8String(decodedBytes); 787 } 788 789 790 791 /** 792 * Decodes the contents of the provided base64url-encoded string. 793 * 794 * @param data The base64url-encoded string to decode. It must not be 795 * {@code null}. 796 * 797 * @return A byte array containing the decoded data. 798 * 799 * @throws ParseException If the contents of the provided string cannot be 800 * parsed as base64url-encoded data. 801 */ 802 @NotNull() 803 public static byte[] urlDecode(@NotNull final String data) 804 throws ParseException 805 { 806 Validator.ensureNotNull(data); 807 808 final int length = data.length(); 809 if (length == 0) 810 { 811 return StaticUtils.NO_BYTES; 812 } 813 814 int stringPos = 0; 815 final ByteStringBuffer buffer = new ByteStringBuffer(length); 816decodeLoop: 817 while (stringPos < length) 818 { 819 int intValue = 0x00; 820 for (int i=0; i < 4; i++) 821 { 822 // Since the value may not be padded, then we need to handle the 823 // possibility of missing characters. 824 final char c; 825 if (stringPos >= length) 826 { 827 c = '='; 828 stringPos++; 829 } 830 else 831 { 832 c = data.charAt(stringPos++); 833 } 834 835 intValue <<= 6; 836 switch (c) 837 { 838 case 'A': 839 intValue |= 0x00; 840 break; 841 case 'B': 842 intValue |= 0x01; 843 break; 844 case 'C': 845 intValue |= 0x02; 846 break; 847 case 'D': 848 intValue |= 0x03; 849 break; 850 case 'E': 851 intValue |= 0x04; 852 break; 853 case 'F': 854 intValue |= 0x05; 855 break; 856 case 'G': 857 intValue |= 0x06; 858 break; 859 case 'H': 860 intValue |= 0x07; 861 break; 862 case 'I': 863 intValue |= 0x08; 864 break; 865 case 'J': 866 intValue |= 0x09; 867 break; 868 case 'K': 869 intValue |= 0x0A; 870 break; 871 case 'L': 872 intValue |= 0x0B; 873 break; 874 case 'M': 875 intValue |= 0x0C; 876 break; 877 case 'N': 878 intValue |= 0x0D; 879 break; 880 case 'O': 881 intValue |= 0x0E; 882 break; 883 case 'P': 884 intValue |= 0x0F; 885 break; 886 case 'Q': 887 intValue |= 0x10; 888 break; 889 case 'R': 890 intValue |= 0x11; 891 break; 892 case 'S': 893 intValue |= 0x12; 894 break; 895 case 'T': 896 intValue |= 0x13; 897 break; 898 case 'U': 899 intValue |= 0x14; 900 break; 901 case 'V': 902 intValue |= 0x15; 903 break; 904 case 'W': 905 intValue |= 0x16; 906 break; 907 case 'X': 908 intValue |= 0x17; 909 break; 910 case 'Y': 911 intValue |= 0x18; 912 break; 913 case 'Z': 914 intValue |= 0x19; 915 break; 916 case 'a': 917 intValue |= 0x1A; 918 break; 919 case 'b': 920 intValue |= 0x1B; 921 break; 922 case 'c': 923 intValue |= 0x1C; 924 break; 925 case 'd': 926 intValue |= 0x1D; 927 break; 928 case 'e': 929 intValue |= 0x1E; 930 break; 931 case 'f': 932 intValue |= 0x1F; 933 break; 934 case 'g': 935 intValue |= 0x20; 936 break; 937 case 'h': 938 intValue |= 0x21; 939 break; 940 case 'i': 941 intValue |= 0x22; 942 break; 943 case 'j': 944 intValue |= 0x23; 945 break; 946 case 'k': 947 intValue |= 0x24; 948 break; 949 case 'l': 950 intValue |= 0x25; 951 break; 952 case 'm': 953 intValue |= 0x26; 954 break; 955 case 'n': 956 intValue |= 0x27; 957 break; 958 case 'o': 959 intValue |= 0x28; 960 break; 961 case 'p': 962 intValue |= 0x29; 963 break; 964 case 'q': 965 intValue |= 0x2A; 966 break; 967 case 'r': 968 intValue |= 0x2B; 969 break; 970 case 's': 971 intValue |= 0x2C; 972 break; 973 case 't': 974 intValue |= 0x2D; 975 break; 976 case 'u': 977 intValue |= 0x2E; 978 break; 979 case 'v': 980 intValue |= 0x2F; 981 break; 982 case 'w': 983 intValue |= 0x30; 984 break; 985 case 'x': 986 intValue |= 0x31; 987 break; 988 case 'y': 989 intValue |= 0x32; 990 break; 991 case 'z': 992 intValue |= 0x33; 993 break; 994 case '0': 995 intValue |= 0x34; 996 break; 997 case '1': 998 intValue |= 0x35; 999 break; 1000 case '2': 1001 intValue |= 0x36; 1002 break; 1003 case '3': 1004 intValue |= 0x37; 1005 break; 1006 case '4': 1007 intValue |= 0x38; 1008 break; 1009 case '5': 1010 intValue |= 0x39; 1011 break; 1012 case '6': 1013 intValue |= 0x3A; 1014 break; 1015 case '7': 1016 intValue |= 0x3B; 1017 break; 1018 case '8': 1019 intValue |= 0x3C; 1020 break; 1021 case '9': 1022 intValue |= 0x3D; 1023 break; 1024 case '-': 1025 intValue |= 0x3E; 1026 break; 1027 case '_': 1028 intValue |= 0x3F; 1029 break; 1030 case '=': 1031 case '%': 1032 switch ((stringPos-1) % 4) 1033 { 1034 case 2: 1035 // The string should have two padding tokens, so only a single 1036 // byte of data remains. Shift the value ten bits to the right 1037 // and read that single byte. 1038 intValue >>= 10; 1039 buffer.append((byte) (intValue & 0xFF)); 1040 break decodeLoop; 1041 case 3: 1042 // The string should have a single padding token, so two bytes 1043 // of data remain. Shift the value eight bits to the right and 1044 // read those two bytes. 1045 intValue >>= 8; 1046 buffer.append((byte) ((intValue >> 8) & 0xFF)); 1047 buffer.append((byte) (intValue & 0xFF)); 1048 break decodeLoop; 1049 } 1050 1051 // If we've gotten here, then that must mean the string had padding 1052 // when none was needed, or it had an invalid length. That's an 1053 // error. 1054 throw new ParseException(ERR_BASE64_URLDECODE_INVALID_LENGTH.get(), 1055 (stringPos-1)); 1056 1057 default: 1058 throw new ParseException( 1059 ERR_BASE64_DECODE_UNEXPECTED_CHAR.get( 1060 data.charAt(stringPos-1)), 1061 (stringPos-1)); 1062 } 1063 } 1064 1065 buffer.append((byte) ((intValue >> 16) & 0xFF)); 1066 buffer.append((byte) ((intValue >> 8) & 0xFF)); 1067 buffer.append((byte) (intValue & 0xFF)); 1068 } 1069 1070 return buffer.toByteArray(); 1071 } 1072 1073 1074 1075 /** 1076 * Decodes the contents of the provided base64-encoded string to a string 1077 * containing the raw data using the UTF-8 encoding. 1078 * 1079 * @param data The base64-encoded string to decode. It must not be 1080 * {@code null}. 1081 * 1082 * @return A string containing the decoded data. 1083 * 1084 * @throws ParseException If the contents of the provided string cannot be 1085 * parsed as base64-encoded data using the UTF-8 1086 * encoding. 1087 */ 1088 @NotNull() 1089 public static String urlDecodeToString(@NotNull final String data) 1090 throws ParseException 1091 { 1092 Validator.ensureNotNull(data); 1093 1094 final byte[] decodedBytes = urlDecode(data); 1095 return StaticUtils.toUTF8String(decodedBytes); 1096 } 1097}