001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS. 026 */ 027package org.opends.server.util; 028 029import static org.forgerock.util.Reject.*; 030import static org.opends.messages.ToolMessages.*; 031import static org.opends.messages.UtilityMessages.*; 032import static org.opends.server.util.StaticUtils.*; 033 034import java.io.BufferedReader; 035import java.io.BufferedWriter; 036import java.io.ByteArrayOutputStream; 037import java.io.FileInputStream; 038import java.io.FileOutputStream; 039import java.io.FileReader; 040import java.io.FileWriter; 041import java.io.InputStream; 042import java.io.InputStreamReader; 043import java.io.UnsupportedEncodingException; 044import java.nio.ByteBuffer; 045import java.text.ParseException; 046import java.util.ArrayList; 047import java.util.StringTokenizer; 048 049import org.forgerock.i18n.LocalizableMessage; 050import org.forgerock.opendj.ldap.ByteSequence; 051import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler; 052import org.opends.server.types.NullOutputStream; 053 054import com.forgerock.opendj.cli.ArgumentException; 055import com.forgerock.opendj.cli.BooleanArgument; 056import com.forgerock.opendj.cli.CommonArguments; 057import com.forgerock.opendj.cli.StringArgument; 058import com.forgerock.opendj.cli.SubCommand; 059import com.forgerock.opendj.cli.SubCommandArgumentParser; 060 061/** 062 * This class provides methods for performing base64 encoding and decoding. 063 * Base64 is a mechanism for encoding binary data in ASCII form by converting 064 * sets of three bytes with eight significant bits each to sets of four bytes 065 * with six significant bits each. 066 */ 067@org.opends.server.types.PublicAPI( 068 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 069 mayInstantiate=false, 070 mayExtend=false, 071 mayInvoke=true) 072public final class Base64 073{ 074 /** The set of characters that may be used in base64-encoded values. */ 075 private static final char[] BASE64_ALPHABET = 076 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 077 078 /** Prevent instance creation. */ 079 private Base64() { 080 // No implementation required. 081 } 082 083 /** 084 * Encodes the provided raw data using base64. 085 * 086 * @param rawData The raw data to encode. It must not be <CODE>null</CODE>. 087 * 088 * @return The base64-encoded representation of the provided raw data. 089 */ 090 public static String encode(byte[] rawData) 091 { 092 ifNull(rawData); 093 094 095 StringBuilder buffer = new StringBuilder(4 * rawData.length / 3); 096 097 int pos = 0; 098 int iterations = rawData.length / 3; 099 for (int i=0; i < iterations; i++) 100 { 101 int value = ((rawData[pos++] & 0xFF) << 16) | 102 ((rawData[pos++] & 0xFF) << 8) | (rawData[pos++] & 0xFF); 103 104 buffer.append(BASE64_ALPHABET[(value >>> 18) & 0x3F]); 105 buffer.append(BASE64_ALPHABET[(value >>> 12) & 0x3F]); 106 buffer.append(BASE64_ALPHABET[(value >>> 6) & 0x3F]); 107 buffer.append(BASE64_ALPHABET[value & 0x3F]); 108 } 109 110 111 switch (rawData.length % 3) 112 { 113 case 1: 114 buffer.append(BASE64_ALPHABET[(rawData[pos] >>> 2) & 0x3F]); 115 buffer.append(BASE64_ALPHABET[(rawData[pos] << 4) & 0x3F]); 116 buffer.append("=="); 117 break; 118 case 2: 119 int value = ((rawData[pos++] & 0xFF) << 8) | (rawData[pos] & 0xFF); 120 buffer.append(BASE64_ALPHABET[(value >>> 10) & 0x3F]); 121 buffer.append(BASE64_ALPHABET[(value >>> 4) & 0x3F]); 122 buffer.append(BASE64_ALPHABET[(value << 2) & 0x3F]); 123 buffer.append("="); 124 break; 125 } 126 127 return buffer.toString(); 128 } 129 130 /** 131 * Encodes the provided raw data using base64. 132 * 133 * @param rawData The raw data to encode. It must not be <CODE>null</CODE>. 134 * 135 * @return The base64-encoded representation of the provided raw data. 136 */ 137 public static String encode(ByteSequence rawData) 138 { 139 ifNull(rawData); 140 141 142 StringBuilder buffer = new StringBuilder(4 * rawData.length() / 3); 143 144 int pos = 0; 145 int iterations = rawData.length() / 3; 146 for (int i=0; i < iterations; i++) 147 { 148 int value = ((rawData.byteAt(pos++) & 0xFF) << 16) | 149 ((rawData.byteAt(pos++) & 0xFF) << 8) | 150 (rawData.byteAt(pos++) & 0xFF); 151 152 buffer.append(BASE64_ALPHABET[(value >>> 18) & 0x3F]); 153 buffer.append(BASE64_ALPHABET[(value >>> 12) & 0x3F]); 154 buffer.append(BASE64_ALPHABET[(value >>> 6) & 0x3F]); 155 buffer.append(BASE64_ALPHABET[value & 0x3F]); 156 } 157 158 159 switch (rawData.length() % 3) 160 { 161 case 1: 162 buffer.append(BASE64_ALPHABET[(rawData.byteAt(pos) >>> 2) & 0x3F]); 163 buffer.append(BASE64_ALPHABET[(rawData.byteAt(pos) << 4) & 0x3F]); 164 buffer.append("=="); 165 break; 166 case 2: 167 int value = ((rawData.byteAt(pos++) & 0xFF) << 8) | 168 (rawData.byteAt(pos) & 0xFF); 169 buffer.append(BASE64_ALPHABET[(value >>> 10) & 0x3F]); 170 buffer.append(BASE64_ALPHABET[(value >>> 4) & 0x3F]); 171 buffer.append(BASE64_ALPHABET[(value << 2) & 0x3F]); 172 buffer.append("="); 173 break; 174 } 175 176 return buffer.toString(); 177 } 178 179 180 181 /** 182 * Decodes the provided set of base64-encoded data. 183 * 184 * @param encodedData The base64-encoded data to decode. It must not be 185 * <CODE>null</CODE>. 186 * 187 * @return The decoded raw data. 188 * 189 * @throws ParseException If a problem occurs while attempting to decode the 190 * provided data. 191 */ 192 public static byte[] decode(String encodedData) 193 throws ParseException 194 { 195 ifNull(encodedData); 196 197 198 // The encoded value must have length that is a multiple of four bytes. 199 int length = encodedData.length(); 200 if (length % 4 != 0) 201 { 202 LocalizableMessage message = ERR_BASE64_DECODE_INVALID_LENGTH.get(encodedData); 203 throw new ParseException(message.toString(), 0); 204 } 205 206 207 ByteBuffer buffer = ByteBuffer.allocate(length); 208 for (int i=0; i < length; i += 4) 209 { 210 boolean append = true; 211 int value = 0; 212 213 for (int j=0; j < 4; j++) 214 { 215 switch (encodedData.charAt(i+j)) 216 { 217 case 'A': 218 value <<= 6; 219 break; 220 case 'B': 221 value = (value << 6) | 0x01; 222 break; 223 case 'C': 224 value = (value << 6) | 0x02; 225 break; 226 case 'D': 227 value = (value << 6) | 0x03; 228 break; 229 case 'E': 230 value = (value << 6) | 0x04; 231 break; 232 case 'F': 233 value = (value << 6) | 0x05; 234 break; 235 case 'G': 236 value = (value << 6) | 0x06; 237 break; 238 case 'H': 239 value = (value << 6) | 0x07; 240 break; 241 case 'I': 242 value = (value << 6) | 0x08; 243 break; 244 case 'J': 245 value = (value << 6) | 0x09; 246 break; 247 case 'K': 248 value = (value << 6) | 0x0A; 249 break; 250 case 'L': 251 value = (value << 6) | 0x0B; 252 break; 253 case 'M': 254 value = (value << 6) | 0x0C; 255 break; 256 case 'N': 257 value = (value << 6) | 0x0D; 258 break; 259 case 'O': 260 value = (value << 6) | 0x0E; 261 break; 262 case 'P': 263 value = (value << 6) | 0x0F; 264 break; 265 case 'Q': 266 value = (value << 6) | 0x10; 267 break; 268 case 'R': 269 value = (value << 6) | 0x11; 270 break; 271 case 'S': 272 value = (value << 6) | 0x12; 273 break; 274 case 'T': 275 value = (value << 6) | 0x13; 276 break; 277 case 'U': 278 value = (value << 6) | 0x14; 279 break; 280 case 'V': 281 value = (value << 6) | 0x15; 282 break; 283 case 'W': 284 value = (value << 6) | 0x16; 285 break; 286 case 'X': 287 value = (value << 6) | 0x17; 288 break; 289 case 'Y': 290 value = (value << 6) | 0x18; 291 break; 292 case 'Z': 293 value = (value << 6) | 0x19; 294 break; 295 case 'a': 296 value = (value << 6) | 0x1A; 297 break; 298 case 'b': 299 value = (value << 6) | 0x1B; 300 break; 301 case 'c': 302 value = (value << 6) | 0x1C; 303 break; 304 case 'd': 305 value = (value << 6) | 0x1D; 306 break; 307 case 'e': 308 value = (value << 6) | 0x1E; 309 break; 310 case 'f': 311 value = (value << 6) | 0x1F; 312 break; 313 case 'g': 314 value = (value << 6) | 0x20; 315 break; 316 case 'h': 317 value = (value << 6) | 0x21; 318 break; 319 case 'i': 320 value = (value << 6) | 0x22; 321 break; 322 case 'j': 323 value = (value << 6) | 0x23; 324 break; 325 case 'k': 326 value = (value << 6) | 0x24; 327 break; 328 case 'l': 329 value = (value << 6) | 0x25; 330 break; 331 case 'm': 332 value = (value << 6) | 0x26; 333 break; 334 case 'n': 335 value = (value << 6) | 0x27; 336 break; 337 case 'o': 338 value = (value << 6) | 0x28; 339 break; 340 case 'p': 341 value = (value << 6) | 0x29; 342 break; 343 case 'q': 344 value = (value << 6) | 0x2A; 345 break; 346 case 'r': 347 value = (value << 6) | 0x2B; 348 break; 349 case 's': 350 value = (value << 6) | 0x2C; 351 break; 352 case 't': 353 value = (value << 6) | 0x2D; 354 break; 355 case 'u': 356 value = (value << 6) | 0x2E; 357 break; 358 case 'v': 359 value = (value << 6) | 0x2F; 360 break; 361 case 'w': 362 value = (value << 6) | 0x30; 363 break; 364 case 'x': 365 value = (value << 6) | 0x31; 366 break; 367 case 'y': 368 value = (value << 6) | 0x32; 369 break; 370 case 'z': 371 value = (value << 6) | 0x33; 372 break; 373 case '0': 374 value = (value << 6) | 0x34; 375 break; 376 case '1': 377 value = (value << 6) | 0x35; 378 break; 379 case '2': 380 value = (value << 6) | 0x36; 381 break; 382 case '3': 383 value = (value << 6) | 0x37; 384 break; 385 case '4': 386 value = (value << 6) | 0x38; 387 break; 388 case '5': 389 value = (value << 6) | 0x39; 390 break; 391 case '6': 392 value = (value << 6) | 0x3A; 393 break; 394 case '7': 395 value = (value << 6) | 0x3B; 396 break; 397 case '8': 398 value = (value << 6) | 0x3C; 399 break; 400 case '9': 401 value = (value << 6) | 0x3D; 402 break; 403 case '+': 404 value = (value << 6) | 0x3E; 405 break; 406 case '/': 407 value = (value << 6) | 0x3F; 408 break; 409 case '=': 410 append = false; 411 switch (j) 412 { 413 case 2: 414 buffer.put((byte) ((value >>> 4) & 0xFF)); 415 break; 416 case 3: 417 buffer.put((byte) ((value >>> 10) & 0xFF)); 418 buffer.put((byte) ((value >>> 2) & 0xFF)); 419 break; 420 } 421 break; 422 default: 423 LocalizableMessage message = ERR_BASE64_DECODE_INVALID_CHARACTER.get( 424 encodedData, encodedData.charAt(i+j)); 425 throw new ParseException(message.toString(), i+j); 426 } 427 428 429 if (! append) 430 { 431 break; 432 } 433 } 434 435 436 if (append) 437 { 438 buffer.put((byte) ((value >>> 16) & 0xFF)); 439 buffer.put((byte) ((value >>> 8) & 0xFF)); 440 buffer.put((byte) (value & 0xFF)); 441 } 442 else 443 { 444 break; 445 } 446 } 447 448 449 buffer.flip(); 450 byte[] returnArray = new byte[buffer.limit()]; 451 buffer.get(returnArray); 452 return returnArray; 453 } 454 455 456 457 /** 458 * Provide a command-line utility that may be used to base64-encode and 459 * decode strings and file contents. 460 * 461 * @param args The command-line arguments provided to this program. 462 */ 463 public static void main(String[] args) 464 { 465 LocalizableMessage description = INFO_BASE64_TOOL_DESCRIPTION.get(); 466 SubCommandArgumentParser argParser = 467 new SubCommandArgumentParser(Base64.class.getName(), description, 468 false); 469 argParser.setShortToolDescription(REF_SHORT_DESC_BASE64.get()); 470 argParser.setVersionHandler(new DirectoryServerVersionHandler()); 471 472 BooleanArgument showUsage = null; 473 StringArgument encodedData = null; 474 StringArgument encodedFile = null; 475 StringArgument rawData = null; 476 StringArgument rawFile = null; 477 StringArgument toEncodedFile = null; 478 StringArgument toRawFile = null; 479 SubCommand decodeSubCommand = null; 480 SubCommand encodeSubCommand = null; 481 482 try 483 { 484 decodeSubCommand = new SubCommand(argParser, "decode", 485 INFO_BASE64_DECODE_DESCRIPTION.get()); 486 487 encodeSubCommand = new SubCommand(argParser, "encode", 488 INFO_BASE64_ENCODE_DESCRIPTION.get()); 489 490 491 encodedData = new StringArgument("encodeddata", 'd', "encodedData", false, 492 false, true, INFO_DATA_PLACEHOLDER.get(), null, 493 null, 494 INFO_BASE64_ENCODED_DATA_DESCRIPTION.get()); 495 decodeSubCommand.addArgument(encodedData); 496 497 498 encodedFile = new StringArgument("encodedfile", 'f', "encodedDataFile", 499 false, false, true, INFO_PATH_PLACEHOLDER.get(), 500 null, null, 501 INFO_BASE64_ENCODED_FILE_DESCRIPTION.get()); 502 decodeSubCommand.addArgument(encodedFile); 503 504 505 toRawFile = new StringArgument("torawfile", 'o', "toRawFile", false, 506 false, true, INFO_PATH_PLACEHOLDER.get(), 507 null, null, 508 INFO_BASE64_TO_RAW_FILE_DESCRIPTION.get()); 509 decodeSubCommand.addArgument(toRawFile); 510 511 512 rawData = new StringArgument("rawdata", 'd', "rawData", false, false, 513 true, INFO_DATA_PLACEHOLDER.get(), null, 514 null, 515 INFO_BASE64_RAW_DATA_DESCRIPTION.get()); 516 encodeSubCommand.addArgument(rawData); 517 518 519 rawFile = new StringArgument("rawfile", 'f', "rawDataFile", false, false, 520 true, INFO_PATH_PLACEHOLDER.get(), null, 521 null, 522 INFO_BASE64_RAW_FILE_DESCRIPTION.get()); 523 encodeSubCommand.addArgument(rawFile); 524 525 526 toEncodedFile = new StringArgument("toencodedfile", 'o', "toEncodedFile", 527 false, false, true, INFO_PATH_PLACEHOLDER.get(), 528 null, null, 529 INFO_BASE64_TO_ENCODED_FILE_DESCRIPTION.get()); 530 encodeSubCommand.addArgument(toEncodedFile); 531 532 533 ArrayList<SubCommand> subCommandList = new ArrayList<>(2); 534 subCommandList.add(decodeSubCommand); 535 subCommandList.add(encodeSubCommand); 536 537 538 showUsage = CommonArguments.getShowUsage(); 539 argParser.addGlobalArgument(showUsage); 540 argParser.setUsageGroupArgument(showUsage, subCommandList); 541 argParser.setUsageArgument(showUsage, NullOutputStream.printStream()); 542 } 543 catch (ArgumentException ae) 544 { 545 System.err.println(ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage())); 546 System.exit(1); 547 } 548 549 try 550 { 551 argParser.parseArguments(args); 552 } 553 catch (ArgumentException ae) 554 { 555 argParser.displayMessageAndUsageReference(System.err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 556 System.exit(1); 557 } 558 559 SubCommand subCommand = argParser.getSubCommand(); 560 if (argParser.isUsageArgumentPresent()) 561 { 562 if (subCommand == null) 563 { 564 System.out.println(argParser.getUsage()); 565 } 566 else 567 { 568 final StringBuilder messageBuilder = new StringBuilder(); 569 argParser.getSubCommandUsage(messageBuilder, subCommand); 570 System.out.println(messageBuilder.toString()); 571 } 572 573 return; 574 } 575 576 if (argParser.isVersionArgumentPresent()) 577 { 578 // version has already been printed 579 System.exit(0); 580 } 581 582 if (subCommand == null) 583 { 584 System.err.println(argParser.getUsage()); 585 System.exit(1); 586 } 587 if (subCommand.getName().equals(encodeSubCommand.getName())) 588 { 589 byte[] dataToEncode = null; 590 if (rawData.isPresent()) 591 { 592 try 593 { 594 dataToEncode = rawData.getValue().getBytes("UTF-8"); 595 } 596 catch(UnsupportedEncodingException ex) 597 { 598 System.err.println(ERR_UNEXPECTED.get(ex)); 599 System.exit(1); 600 } 601 } 602 else 603 { 604 try 605 { 606 boolean shouldClose; 607 InputStream inputStream; 608 if (rawFile.isPresent()) 609 { 610 inputStream = new FileInputStream(rawFile.getValue()); 611 shouldClose = true; 612 } 613 else 614 { 615 inputStream = System.in; 616 shouldClose = false; 617 } 618 619 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 620 byte[] buffer = new byte[8192]; 621 while (true) 622 { 623 int bytesRead = inputStream.read(buffer); 624 if (bytesRead < 0) 625 { 626 break; 627 } 628 else 629 { 630 baos.write(buffer, 0, bytesRead); 631 } 632 } 633 634 if (shouldClose) 635 { 636 inputStream.close(); 637 } 638 639 dataToEncode = baos.toByteArray(); 640 } 641 catch (Exception e) 642 { 643 System.err.println(ERR_BASE64_CANNOT_READ_RAW_DATA.get( 644 getExceptionMessage(e))); 645 System.exit(1); 646 } 647 } 648 649 String base64Data = encode(dataToEncode); 650 if (toEncodedFile.isPresent()) 651 { 652 try 653 { 654 BufferedWriter writer = 655 new BufferedWriter(new FileWriter(toEncodedFile.getValue())); 656 writer.write(base64Data); 657 writer.newLine(); 658 writer.close(); 659 } 660 catch (Exception e) 661 { 662 System.err.println(ERR_BASE64_CANNOT_WRITE_ENCODED_DATA.get( 663 getExceptionMessage(e))); 664 System.exit(1); 665 } 666 } 667 else 668 { 669 System.out.println(base64Data); 670 } 671 } 672 else if (subCommand.getName().equals(decodeSubCommand.getName())) 673 { 674 String dataToDecode = null; 675 if (encodedData.isPresent()) 676 { 677 dataToDecode = encodedData.getValue(); 678 } 679 else 680 { 681 try 682 { 683 boolean shouldClose; 684 BufferedReader reader; 685 if (encodedFile.isPresent()) 686 { 687 reader = new BufferedReader(new FileReader(encodedFile.getValue())); 688 shouldClose = true; 689 } 690 else 691 { 692 reader = new BufferedReader(new InputStreamReader(System.in)); 693 shouldClose = false; 694 } 695 696 StringBuilder buffer = new StringBuilder(); 697 while (true) 698 { 699 String line = reader.readLine(); 700 if (line == null) 701 { 702 break; 703 } 704 705 StringTokenizer tokenizer = new StringTokenizer(line); 706 while (tokenizer.hasMoreTokens()) 707 { 708 buffer.append(tokenizer.nextToken()); 709 } 710 } 711 712 if (shouldClose) 713 { 714 reader.close(); 715 } 716 717 dataToDecode = buffer.toString(); 718 } 719 catch (Exception e) 720 { 721 System.err.println(ERR_BASE64_CANNOT_READ_ENCODED_DATA.get( 722 getExceptionMessage(e))); 723 System.exit(1); 724 } 725 } 726 727 byte[] decodedData = null; 728 try 729 { 730 decodedData = decode(dataToDecode); 731 } 732 catch (ParseException pe) 733 { 734 System.err.println(pe.getMessage()); 735 System.exit(1); 736 } 737 738 try 739 { 740 if (toRawFile.isPresent()) 741 { 742 FileOutputStream outputStream = 743 new FileOutputStream(toRawFile.getValue()); 744 outputStream.write(decodedData); 745 outputStream.close(); 746 } 747 else 748 { 749 System.out.write(decodedData); 750 System.out.println(); 751 System.out.flush(); 752 } 753 } 754 catch (Exception e) 755 { 756 System.err.println(ERR_BASE64_CANNOT_WRITE_RAW_DATA.get( 757 getExceptionMessage(e))); 758 System.exit(1); 759 } 760 } 761 else 762 { 763 System.err.println(ERR_BASE64_UNKNOWN_SUBCOMMAND.get( 764 subCommand.getName())); 765 System.exit(1); 766 } 767 } 768}