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