001/* 002 * Copyright 2016-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.examples; 022 023 024 025import java.io.BufferedReader; 026import java.io.FileInputStream; 027import java.io.FileReader; 028import java.io.FileOutputStream; 029import java.io.InputStream; 030import java.io.InputStreamReader; 031import java.io.OutputStream; 032import java.util.LinkedHashMap; 033 034import com.unboundid.ldap.sdk.ResultCode; 035import com.unboundid.ldap.sdk.Version; 036import com.unboundid.util.Base64; 037import com.unboundid.util.ByteStringBuffer; 038import com.unboundid.util.CommandLineTool; 039import com.unboundid.util.Debug; 040import com.unboundid.util.StaticUtils; 041import com.unboundid.util.ThreadSafety; 042import com.unboundid.util.ThreadSafetyLevel; 043import com.unboundid.util.args.ArgumentException; 044import com.unboundid.util.args.ArgumentParser; 045import com.unboundid.util.args.BooleanArgument; 046import com.unboundid.util.args.FileArgument; 047import com.unboundid.util.args.StringArgument; 048import com.unboundid.util.args.SubCommand; 049 050 051 052/** 053 * This class provides a tool that can be used to perform base64 encoding and 054 * decoding from the command line. It provides two subcommands: encode and 055 * decode. Each of those subcommands offers the following arguments: 056 * <UL> 057 * <LI> 058 * "--data {data}" -- specifies the data to be encoded or decoded. 059 * </LI> 060 * <LI> 061 * "--inputFile {data}" -- specifies the path to a file containing the data 062 * to be encoded or decoded. 063 * </LI> 064 * <LI> 065 * "--outputFile {data}" -- specifies the path to a file to which the 066 * encoded or decoded data should be written. 067 * </LI> 068 * </UL> 069 * The "--data" and "--inputFile" arguments are mutually exclusive, and if 070 * neither is provided, the data to encode will be read from standard input. 071 * If the "--outputFile" argument is not provided, then the result will be 072 * written to standard output. 073 */ 074@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 075public final class Base64Tool 076 extends CommandLineTool 077{ 078 /** 079 * The column at which to wrap long lines of output. 080 */ 081 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 082 083 084 085 /** 086 * The name of the argument used to indicate whether to add an end-of-line 087 * marker to the end of the base64-encoded data. 088 */ 089 private static final String ARG_NAME_ADD_TRAILING_LINE_BREAK = 090 "addTrailingLineBreak"; 091 092 093 094 /** 095 * The name of the argument used to specify the data to encode or decode. 096 */ 097 private static final String ARG_NAME_DATA = "data"; 098 099 100 101 /** 102 * The name of the argument used to indicate whether to ignore any end-of-line 103 * marker that might be present at the end of the data to encode. 104 */ 105 private static final String ARG_NAME_IGNORE_TRAILING_LINE_BREAK = 106 "ignoreTrailingLineBreak"; 107 108 109 110 /** 111 * The name of the argument used to specify the path to the input file with 112 * the data to encode or decode. 113 */ 114 private static final String ARG_NAME_INPUT_FILE = "inputFile"; 115 116 117 118 /** 119 * The name of the argument used to specify the path to the output file into 120 * which to write the encoded or decoded data. 121 */ 122 private static final String ARG_NAME_OUTPUT_FILE = "outputFile"; 123 124 125 126 /** 127 * The name of the argument used to indicate that the encoding and decoding 128 * should be performed using the base64url alphabet rather than the standard 129 * base64 alphabet. 130 */ 131 private static final String ARG_NAME_URL = "url"; 132 133 134 135 /** 136 * The name of the subcommand used to decode data. 137 */ 138 private static final String SUBCOMMAND_NAME_DECODE = "decode"; 139 140 141 142 /** 143 * The name of the subcommand used to encode data. 144 */ 145 private static final String SUBCOMMAND_NAME_ENCODE = "encode"; 146 147 148 149 // The argument parser for this tool. 150 private volatile ArgumentParser parser; 151 152 // The input stream to use as standard input. 153 private final InputStream in; 154 155 156 157 /** 158 * Runs the tool with the provided set of arguments. 159 * 160 * @param args The command line arguments provided to this program. 161 */ 162 public static void main(final String... args) 163 { 164 final ResultCode resultCode = main(System.in, System.out, System.err, args); 165 if (resultCode != ResultCode.SUCCESS) 166 { 167 System.exit(resultCode.intValue()); 168 } 169 } 170 171 172 173 /** 174 * Runs the tool with the provided information. 175 * 176 * @param in The input stream to use for standard input. It may be 177 * {@code null} if no standard input is needed. 178 * @param out The output stream to which standard out should be written. 179 * It may be {@code null} if standard output should be 180 * suppressed. 181 * @param err The output stream to which standard error should be written. 182 * It may be {@code null} if standard error should be 183 * suppressed. 184 * @param args The command line arguments provided to this program. 185 * 186 * @return The result code obtained from running the tool. A result code 187 * other than {@link ResultCode#SUCCESS} will indicate that an error 188 * occurred. 189 */ 190 public static ResultCode main(final InputStream in, final OutputStream out, 191 final OutputStream err, final String... args) 192 { 193 final Base64Tool tool = new Base64Tool(in, out, err); 194 return tool.runTool(args); 195 } 196 197 198 199 /** 200 * Creates a new instance of this tool with the provided information. 201 * Standard input will not be available. 202 * 203 * @param out The output stream to which standard out should be written. 204 * It may be {@code null} if standard output should be 205 * suppressed. 206 * @param err The output stream to which standard error should be written. 207 * It may be {@code null} if standard error should be suppressed. 208 */ 209 public Base64Tool(final OutputStream out, final OutputStream err) 210 { 211 this(null, out, err); 212 } 213 214 215 216 /** 217 * Creates a new instance of this tool with the provided information. 218 * 219 * @param in The input stream to use for standard input. It may be 220 * {@code null} if no standard input is needed. 221 * @param out The output stream to which standard out should be written. 222 * It may be {@code null} if standard output should be 223 * suppressed. 224 * @param err The output stream to which standard error should be written. 225 * It may be {@code null} if standard error should be suppressed. 226 */ 227 public Base64Tool(final InputStream in, final OutputStream out, 228 final OutputStream err) 229 { 230 super(out, err); 231 232 this.in = in; 233 234 parser = null; 235 } 236 237 238 239 /** 240 * Retrieves the name of this tool. It should be the name of the command used 241 * to invoke this tool. 242 * 243 * @return The name for this tool. 244 */ 245 @Override() 246 public String getToolName() 247 { 248 return "base64"; 249 } 250 251 252 253 /** 254 * Retrieves a human-readable description for this tool. 255 * 256 * @return A human-readable description for this tool. 257 */ 258 @Override() 259 public String getToolDescription() 260 { 261 return "Base64 encode raw data, or base64-decode encoded data. The data " + 262 "to encode or decode may be provided via an argument value, in a " + 263 "file, or read from standard input. The output may be written to a " + 264 "file or standard output."; 265 } 266 267 268 269 /** 270 * Retrieves a version string for this tool, if available. 271 * 272 * @return A version string for this tool, or {@code null} if none is 273 * available. 274 */ 275 @Override() 276 public String getToolVersion() 277 { 278 return Version.NUMERIC_VERSION_STRING; 279 } 280 281 282 283 /** 284 * Indicates whether this tool should provide support for an interactive mode, 285 * in which the tool offers a mode in which the arguments can be provided in 286 * a text-driven menu rather than requiring them to be given on the command 287 * line. If interactive mode is supported, it may be invoked using the 288 * "--interactive" argument. Alternately, if interactive mode is supported 289 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 290 * interactive mode may be invoked by simply launching the tool without any 291 * arguments. 292 * 293 * @return {@code true} if this tool supports interactive mode, or 294 * {@code false} if not. 295 */ 296 @Override() 297 public boolean supportsInteractiveMode() 298 { 299 return true; 300 } 301 302 303 304 /** 305 * Indicates whether this tool defaults to launching in interactive mode if 306 * the tool is invoked without any command-line arguments. This will only be 307 * used if {@link #supportsInteractiveMode()} returns {@code true}. 308 * 309 * @return {@code true} if this tool defaults to using interactive mode if 310 * launched without any command-line arguments, or {@code false} if 311 * not. 312 */ 313 @Override() 314 public boolean defaultsToInteractiveMode() 315 { 316 return true; 317 } 318 319 320 321 /** 322 * Indicates whether this tool supports the use of a properties file for 323 * specifying default values for arguments that aren't specified on the 324 * command line. 325 * 326 * @return {@code true} if this tool supports the use of a properties file 327 * for specifying default values for arguments that aren't specified 328 * on the command line, or {@code false} if not. 329 */ 330 @Override() 331 public boolean supportsPropertiesFile() 332 { 333 return true; 334 } 335 336 337 338 /** 339 * Indicates whether this tool should provide arguments for redirecting output 340 * to a file. If this method returns {@code true}, then the tool will offer 341 * an "--outputFile" argument that will specify the path to a file to which 342 * all standard output and standard error content will be written, and it will 343 * also offer a "--teeToStandardOut" argument that can only be used if the 344 * "--outputFile" argument is present and will cause all output to be written 345 * to both the specified output file and to standard output. 346 * 347 * @return {@code true} if this tool should provide arguments for redirecting 348 * output to a file, or {@code false} if not. 349 */ 350 @Override() 351 protected boolean supportsOutputFile() 352 { 353 // This tool provides its own output file support. 354 return false; 355 } 356 357 358 359 /** 360 * Adds the command-line arguments supported for use with this tool to the 361 * provided argument parser. The tool may need to retain references to the 362 * arguments (and/or the argument parser, if trailing arguments are allowed) 363 * to it in order to obtain their values for use in later processing. 364 * 365 * @param parser The argument parser to which the arguments are to be added. 366 * 367 * @throws ArgumentException If a problem occurs while adding any of the 368 * tool-specific arguments to the provided 369 * argument parser. 370 */ 371 @Override() 372 public void addToolArguments(final ArgumentParser parser) 373 throws ArgumentException 374 { 375 this.parser = parser; 376 377 378 // Create the subcommand for encoding data. 379 final ArgumentParser encodeParser = 380 new ArgumentParser("encode", "Base64-encodes raw data."); 381 382 final StringArgument encodeDataArgument = new StringArgument('d', 383 ARG_NAME_DATA, false, 1, "{data}", 384 "The raw data to be encoded. If neither the --" + ARG_NAME_DATA + 385 " nor the --" + ARG_NAME_INPUT_FILE + " argument is provided, " + 386 "then the data will be read from standard input."); 387 encodeDataArgument.addLongIdentifier("rawData", true); 388 encodeDataArgument.addLongIdentifier("raw-data", true); 389 encodeParser.addArgument(encodeDataArgument); 390 391 final FileArgument encodeDataFileArgument = new FileArgument('f', 392 ARG_NAME_INPUT_FILE, false, 1, null, 393 "The path to a file containing the raw data to be encoded. If " + 394 "neither the --" + ARG_NAME_DATA + " nor the --" + 395 ARG_NAME_INPUT_FILE + " argument is provided, then the data " + 396 "will be read from standard input.", 397 true, true, true, false); 398 encodeDataFileArgument.addLongIdentifier("rawDataFile", true); 399 encodeDataFileArgument.addLongIdentifier("input-file", true); 400 encodeDataFileArgument.addLongIdentifier("raw-data-file", true); 401 encodeParser.addArgument(encodeDataFileArgument); 402 403 final FileArgument encodeOutputFileArgument = new FileArgument('o', 404 ARG_NAME_OUTPUT_FILE, false, 1, null, 405 "The path to a file to which the encoded data should be written. " + 406 "If this is not provided, the encoded data will be written to " + 407 "standard output.", 408 false, true, true, false); 409 encodeOutputFileArgument.addLongIdentifier("toEncodedFile", true); 410 encodeOutputFileArgument.addLongIdentifier("output-file", true); 411 encodeOutputFileArgument.addLongIdentifier("to-encoded-file", true); 412 encodeParser.addArgument(encodeOutputFileArgument); 413 414 final BooleanArgument encodeURLArgument = new BooleanArgument(null, 415 ARG_NAME_URL, 416 "Encode the data with the base64url mechanism rather than the " + 417 "standard base64 mechanism."); 418 encodeParser.addArgument(encodeURLArgument); 419 420 final BooleanArgument encodeIgnoreTrailingEOLArgument = new BooleanArgument( 421 null, ARG_NAME_IGNORE_TRAILING_LINE_BREAK, 422 "Ignore any end-of-line marker that may be present at the end of " + 423 "the data to encode."); 424 encodeIgnoreTrailingEOLArgument.addLongIdentifier( 425 "ignore-trailing-line-break", true); 426 encodeParser.addArgument(encodeIgnoreTrailingEOLArgument); 427 428 encodeParser.addExclusiveArgumentSet(encodeDataArgument, 429 encodeDataFileArgument); 430 431 final LinkedHashMap<String[],String> encodeExamples = 432 new LinkedHashMap<>(StaticUtils.computeMapCapacity(3)); 433 encodeExamples.put( 434 new String[] 435 { 436 "encode", 437 "--data", "Hello" 438 }, 439 "Base64-encodes the string 'Hello' and writes the result to " + 440 "standard output."); 441 encodeExamples.put( 442 new String[] 443 { 444 "encode", 445 "--inputFile", "raw-data.txt", 446 "--outputFile", "encoded-data.txt", 447 }, 448 "Base64-encodes the data contained in the 'raw-data.txt' file and " + 449 "writes the result to the 'encoded-data.txt' file."); 450 encodeExamples.put( 451 new String[] 452 { 453 "encode" 454 }, 455 "Base64-encodes data read from standard input and writes the result " + 456 "to standard output."); 457 458 final SubCommand encodeSubCommand = new SubCommand(SUBCOMMAND_NAME_ENCODE, 459 "Base64-encodes raw data.", encodeParser, encodeExamples); 460 parser.addSubCommand(encodeSubCommand); 461 462 463 // Create the subcommand for decoding data. 464 final ArgumentParser decodeParser = 465 new ArgumentParser("decode", "Decodes base64-encoded data."); 466 467 final StringArgument decodeDataArgument = new StringArgument('d', 468 ARG_NAME_DATA, false, 1, "{data}", 469 "The base64-encoded data to be decoded. If neither the --" + 470 ARG_NAME_DATA + " nor the --" + ARG_NAME_INPUT_FILE + 471 " argument is provided, then the data will be read from " + 472 "standard input."); 473 decodeDataArgument.addLongIdentifier("encodedData", true); 474 decodeDataArgument.addLongIdentifier("encoded-data", true); 475 decodeParser.addArgument(decodeDataArgument); 476 477 final FileArgument decodeDataFileArgument = new FileArgument('f', 478 ARG_NAME_INPUT_FILE, false, 1, null, 479 "The path to a file containing the base64-encoded data to be " + 480 "decoded. If neither the --" + ARG_NAME_DATA + " nor the --" + 481 ARG_NAME_INPUT_FILE + " argument is provided, then the data " + 482 "will be read from standard input.", 483 true, true, true, false); 484 decodeDataFileArgument.addLongIdentifier("encodedDataFile", true); 485 decodeDataFileArgument.addLongIdentifier("input-file", true); 486 decodeDataFileArgument.addLongIdentifier("encoded-data-file", true); 487 decodeParser.addArgument(decodeDataFileArgument); 488 489 final FileArgument decodeOutputFileArgument = new FileArgument('o', 490 ARG_NAME_OUTPUT_FILE, false, 1, null, 491 "The path to a file to which the decoded data should be written. " + 492 "If this is not provided, the decoded data will be written to " + 493 "standard output.", 494 false, true, true, false); 495 decodeOutputFileArgument.addLongIdentifier("toRawFile", true); 496 decodeOutputFileArgument.addLongIdentifier("output-file", true); 497 decodeOutputFileArgument.addLongIdentifier("to-raw-file", true); 498 decodeParser.addArgument(decodeOutputFileArgument); 499 500 final BooleanArgument decodeURLArgument = new BooleanArgument(null, 501 ARG_NAME_URL, 502 "Decode the data with the base64url mechanism rather than the " + 503 "standard base64 mechanism."); 504 decodeParser.addArgument(decodeURLArgument); 505 506 final BooleanArgument decodeAddTrailingLineBreak = new BooleanArgument( 507 null, ARG_NAME_ADD_TRAILING_LINE_BREAK, 508 "Add a line break to the end of the decoded data."); 509 decodeAddTrailingLineBreak.addLongIdentifier("add-trailing-line-break", 510 true); 511 decodeParser.addArgument(decodeAddTrailingLineBreak); 512 513 decodeParser.addExclusiveArgumentSet(decodeDataArgument, 514 decodeDataFileArgument); 515 516 final LinkedHashMap<String[],String> decodeExamples = 517 new LinkedHashMap<>(StaticUtils.computeMapCapacity(3)); 518 decodeExamples.put( 519 new String[] 520 { 521 "decode", 522 "--data", "SGVsbG8=" 523 }, 524 "Base64-decodes the string 'SGVsbG8=' and writes the result to " + 525 "standard output."); 526 decodeExamples.put( 527 new String[] 528 { 529 "decode", 530 "--inputFile", "encoded-data.txt", 531 "--outputFile", "decoded-data.txt", 532 }, 533 "Base64-decodes the data contained in the 'encoded-data.txt' file " + 534 "and writes the result to the 'raw-data.txt' file."); 535 decodeExamples.put( 536 new String[] 537 { 538 "decode" 539 }, 540 "Base64-decodes data read from standard input and writes the result " + 541 "to standard output."); 542 543 final SubCommand decodeSubCommand = new SubCommand(SUBCOMMAND_NAME_DECODE, 544 "Decodes base64-encoded data.", decodeParser, decodeExamples); 545 parser.addSubCommand(decodeSubCommand); 546 } 547 548 549 550 /** 551 * Performs the core set of processing for this tool. 552 * 553 * @return A result code that indicates whether the processing completed 554 * successfully. 555 */ 556 @Override() 557 public ResultCode doToolProcessing() 558 { 559 // Get the subcommand selected by the user. 560 final SubCommand subCommand = parser.getSelectedSubCommand(); 561 if (subCommand == null) 562 { 563 // This should never happen. 564 wrapErr(0, WRAP_COLUMN, "No subcommand was selected."); 565 return ResultCode.PARAM_ERROR; 566 } 567 568 569 // Take the appropriate action based on the selected subcommand. 570 if (subCommand.hasName(SUBCOMMAND_NAME_ENCODE)) 571 { 572 return doEncode(subCommand.getArgumentParser()); 573 } 574 else 575 { 576 return doDecode(subCommand.getArgumentParser()); 577 } 578 } 579 580 581 582 /** 583 * Performs the necessary work for base64 encoding. 584 * 585 * @param p The argument parser for the encode subcommand. 586 * 587 * @return A result code that indicates whether the processing completed 588 * successfully. 589 */ 590 private ResultCode doEncode(final ArgumentParser p) 591 { 592 // Get the data to encode. 593 final ByteStringBuffer rawDataBuffer = new ByteStringBuffer(); 594 final StringArgument dataArg = p.getStringArgument(ARG_NAME_DATA); 595 if ((dataArg != null) && dataArg.isPresent()) 596 { 597 rawDataBuffer.append(dataArg.getValue()); 598 } 599 else 600 { 601 try 602 { 603 final InputStream inputStream; 604 final FileArgument inputFileArg = 605 p.getFileArgument(ARG_NAME_INPUT_FILE); 606 if ((inputFileArg != null) && inputFileArg.isPresent()) 607 { 608 inputStream = new FileInputStream(inputFileArg.getValue()); 609 } 610 else 611 { 612 inputStream = in; 613 } 614 615 final byte[] buffer = new byte[8192]; 616 while (true) 617 { 618 final int bytesRead = inputStream.read(buffer); 619 if (bytesRead <= 0) 620 { 621 break; 622 } 623 624 rawDataBuffer.append(buffer, 0, bytesRead); 625 } 626 627 inputStream.close(); 628 } 629 catch (final Exception e) 630 { 631 Debug.debugException(e); 632 wrapErr(0, WRAP_COLUMN, 633 "An error occurred while attempting to read the data to encode: ", 634 StaticUtils.getExceptionMessage(e)); 635 return ResultCode.LOCAL_ERROR; 636 } 637 } 638 639 640 // If we should ignore any trailing end-of-line markers, then do that now. 641 final BooleanArgument ignoreEOLArg = 642 p.getBooleanArgument(ARG_NAME_IGNORE_TRAILING_LINE_BREAK); 643 if ((ignoreEOLArg != null) && ignoreEOLArg.isPresent()) 644 { 645stripEOLLoop: 646 while (rawDataBuffer.length() > 0) 647 { 648 switch (rawDataBuffer.getBackingArray()[rawDataBuffer.length() - 1]) 649 { 650 case '\n': 651 case '\r': 652 rawDataBuffer.delete(rawDataBuffer.length() - 1, 1); 653 break; 654 default: 655 break stripEOLLoop; 656 } 657 } 658 } 659 660 661 // Base64-encode the data. 662 final byte[] rawDataArray = rawDataBuffer.toByteArray(); 663 final ByteStringBuffer encodedDataBuffer = 664 new ByteStringBuffer(4 * rawDataBuffer.length() / 3 + 3); 665 final BooleanArgument urlArg = p.getBooleanArgument(ARG_NAME_URL); 666 if ((urlArg != null) && urlArg.isPresent()) 667 { 668 Base64.urlEncode(rawDataArray, 0, rawDataArray.length, encodedDataBuffer, 669 false); 670 } 671 else 672 { 673 Base64.encode(rawDataArray, encodedDataBuffer); 674 } 675 676 677 // Write the encoded data. 678 final FileArgument outputFileArg = p.getFileArgument(ARG_NAME_OUTPUT_FILE); 679 if ((outputFileArg != null) && outputFileArg.isPresent()) 680 { 681 try 682 { 683 final FileOutputStream outputStream = 684 new FileOutputStream(outputFileArg.getValue(), false); 685 encodedDataBuffer.write(outputStream); 686 outputStream.write(StaticUtils.EOL_BYTES); 687 outputStream.flush(); 688 outputStream.close(); 689 } 690 catch (final Exception e) 691 { 692 Debug.debugException(e); 693 wrapErr(0, WRAP_COLUMN, 694 "An error occurred while attempting to write the base64-encoded " + 695 "data to output file ", 696 outputFileArg.getValue().getAbsolutePath(), ": ", 697 StaticUtils.getExceptionMessage(e)); 698 err("Base64-encoded data:"); 699 err(encodedDataBuffer.toString()); 700 return ResultCode.LOCAL_ERROR; 701 } 702 } 703 else 704 { 705 out(encodedDataBuffer.toString()); 706 } 707 708 709 return ResultCode.SUCCESS; 710 } 711 712 713 714 /** 715 * Performs the necessary work for base64 decoding. 716 * 717 * @param p The argument parser for the decode subcommand. 718 * 719 * @return A result code that indicates whether the processing completed 720 * successfully. 721 */ 722 private ResultCode doDecode(final ArgumentParser p) 723 { 724 // Get the data to decode. We'll always ignore the following: 725 // - Line breaks 726 // - Blank lines 727 // - Lines that start with an octothorpe (#) 728 // 729 // Unless the --url argument was provided, then we'll also ignore lines that 730 // start with a dash (like those used as start and end markers in a 731 // PEM-encoded certificate). Since dashes are part of the base64url 732 // alphabet, we can't ignore dashes if the --url argument was provided. 733 final ByteStringBuffer encodedDataBuffer = new ByteStringBuffer(); 734 final BooleanArgument urlArg = p.getBooleanArgument(ARG_NAME_URL); 735 final StringArgument dataArg = p.getStringArgument(ARG_NAME_DATA); 736 if ((dataArg != null) && dataArg.isPresent()) 737 { 738 encodedDataBuffer.append(dataArg.getValue()); 739 } 740 else 741 { 742 try 743 { 744 final BufferedReader reader; 745 final FileArgument inputFileArg = 746 p.getFileArgument(ARG_NAME_INPUT_FILE); 747 if ((inputFileArg != null) && inputFileArg.isPresent()) 748 { 749 reader = new BufferedReader(new FileReader(inputFileArg.getValue())); 750 } 751 else 752 { 753 reader = new BufferedReader(new InputStreamReader(in)); 754 } 755 756 while (true) 757 { 758 final String line = reader.readLine(); 759 if (line == null) 760 { 761 break; 762 } 763 764 if ((line.length() == 0) || line.startsWith("#")) 765 { 766 continue; 767 } 768 769 if (line.startsWith("-") && 770 ((urlArg == null) || (! urlArg.isPresent()))) 771 { 772 continue; 773 } 774 775 encodedDataBuffer.append(line); 776 } 777 778 reader.close(); 779 } 780 catch (final Exception e) 781 { 782 Debug.debugException(e); 783 wrapErr(0, WRAP_COLUMN, 784 "An error occurred while attempting to read the data to decode: ", 785 StaticUtils.getExceptionMessage(e)); 786 return ResultCode.LOCAL_ERROR; 787 } 788 } 789 790 791 // Base64-decode the data. 792 final ByteStringBuffer rawDataBuffer = new 793 ByteStringBuffer(encodedDataBuffer.length()); 794 if ((urlArg != null) && urlArg.isPresent()) 795 { 796 try 797 { 798 rawDataBuffer.append(Base64.urlDecode(encodedDataBuffer.toString())); 799 } 800 catch (final Exception e) 801 { 802 Debug.debugException(e); 803 wrapErr(0, WRAP_COLUMN, 804 "An error occurred while attempting to base64url-decode the " + 805 "provided data: " + StaticUtils.getExceptionMessage(e)); 806 return ResultCode.LOCAL_ERROR; 807 } 808 } 809 else 810 { 811 try 812 { 813 rawDataBuffer.append(Base64.decode(encodedDataBuffer.toString())); 814 } 815 catch (final Exception e) 816 { 817 Debug.debugException(e); 818 wrapErr(0, WRAP_COLUMN, 819 "An error occurred while attempting to base64-decode the " + 820 "provided data: " + StaticUtils.getExceptionMessage(e)); 821 return ResultCode.LOCAL_ERROR; 822 } 823 } 824 825 826 // If we should add a newline, then do that now. 827 final BooleanArgument addEOLArg = 828 p.getBooleanArgument(ARG_NAME_ADD_TRAILING_LINE_BREAK); 829 if ((addEOLArg != null) && addEOLArg.isPresent()) 830 { 831 rawDataBuffer.append(StaticUtils.EOL_BYTES); 832 } 833 834 835 // Write the decoded data. 836 final FileArgument outputFileArg = p.getFileArgument(ARG_NAME_OUTPUT_FILE); 837 if ((outputFileArg != null) && outputFileArg.isPresent()) 838 { 839 try 840 { 841 final FileOutputStream outputStream = 842 new FileOutputStream(outputFileArg.getValue(), false); 843 rawDataBuffer.write(outputStream); 844 outputStream.flush(); 845 outputStream.close(); 846 } 847 catch (final Exception e) 848 { 849 Debug.debugException(e); 850 wrapErr(0, WRAP_COLUMN, 851 "An error occurred while attempting to write the base64-decoded " + 852 "data to output file ", 853 outputFileArg.getValue().getAbsolutePath(), ": ", 854 StaticUtils.getExceptionMessage(e)); 855 err("Base64-decoded data:"); 856 err(encodedDataBuffer.toString()); 857 return ResultCode.LOCAL_ERROR; 858 } 859 } 860 else 861 { 862 final byte[] rawDataArray = rawDataBuffer.toByteArray(); 863 getOut().write(rawDataArray, 0, rawDataArray.length); 864 getOut().flush(); 865 } 866 867 868 return ResultCode.SUCCESS; 869 } 870 871 872 873 /** 874 * Retrieves a set of information that may be used to generate example usage 875 * information. Each element in the returned map should consist of a map 876 * between an example set of arguments and a string that describes the 877 * behavior of the tool when invoked with that set of arguments. 878 * 879 * @return A set of information that may be used to generate example usage 880 * information. It may be {@code null} or empty if no example usage 881 * information is available. 882 */ 883 @Override() 884 public LinkedHashMap<String[],String> getExampleUsages() 885 { 886 final LinkedHashMap<String[],String> examples = 887 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 888 889 examples.put( 890 new String[] 891 { 892 "encode", 893 "--data", "Hello" 894 }, 895 "Base64-encodes the string 'Hello' and writes the result to " + 896 "standard output."); 897 898 examples.put( 899 new String[] 900 { 901 "decode", 902 "--inputFile", "encoded-data.txt", 903 "--outputFile", "decoded-data.txt", 904 }, 905 "Base64-decodes the data contained in the 'encoded-data.txt' file " + 906 "and writes the result to the 'raw-data.txt' file."); 907 908 return examples; 909 } 910}