001/* 002 * Copyright 2016-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2017 UnboundID Corp. 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 * 202 * @param in The input stream to use for standard input. It may be 203 * {@code null} if no standard input is needed. 204 * @param out The output stream to which standard out should be written. 205 * It may be {@code null} if standard output should be 206 * suppressed. 207 * @param err The output stream to which standard error should be written. 208 * It may be {@code null} if standard error should be suppressed. 209 */ 210 public Base64Tool(final InputStream in, final OutputStream out, 211 final OutputStream err) 212 { 213 super(out, err); 214 215 this.in = in; 216 217 parser = null; 218 } 219 220 221 222 /** 223 * Retrieves the name of this tool. It should be the name of the command used 224 * to invoke this tool. 225 * 226 * @return The name for this tool. 227 */ 228 @Override() 229 public String getToolName() 230 { 231 return "base64"; 232 } 233 234 235 236 /** 237 * Retrieves a human-readable description for this tool. 238 * 239 * @return A human-readable description for this tool. 240 */ 241 @Override() 242 public String getToolDescription() 243 { 244 return "Base64 encode raw data, or base64-decode encoded data. The data " + 245 "to encode or decode may be provided via an argument value, in a " + 246 "file, or read from standard input. The output may be written to a " + 247 "file or standard output."; 248 } 249 250 251 252 /** 253 * Retrieves a version string for this tool, if available. 254 * 255 * @return A version string for this tool, or {@code null} if none is 256 * available. 257 */ 258 @Override() 259 public String getToolVersion() 260 { 261 return Version.NUMERIC_VERSION_STRING; 262 } 263 264 265 266 /** 267 * Indicates whether this tool should provide support for an interactive mode, 268 * in which the tool offers a mode in which the arguments can be provided in 269 * a text-driven menu rather than requiring them to be given on the command 270 * line. If interactive mode is supported, it may be invoked using the 271 * "--interactive" argument. Alternately, if interactive mode is supported 272 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 273 * interactive mode may be invoked by simply launching the tool without any 274 * arguments. 275 * 276 * @return {@code true} if this tool supports interactive mode, or 277 * {@code false} if not. 278 */ 279 @Override() 280 public boolean supportsInteractiveMode() 281 { 282 // TODO: Add support for interactive mode for tools with subcommands. 283 return true; 284 } 285 286 287 288 /** 289 * Indicates whether this tool defaults to launching in interactive mode if 290 * the tool is invoked without any command-line arguments. This will only be 291 * used if {@link #supportsInteractiveMode()} returns {@code true}. 292 * 293 * @return {@code true} if this tool defaults to using interactive mode if 294 * launched without any command-line arguments, or {@code false} if 295 * not. 296 */ 297 @Override() 298 public boolean defaultsToInteractiveMode() 299 { 300 // TODO: Add support for interactive mode for tools with subcommands. 301 return true; 302 } 303 304 305 306 /** 307 * Indicates whether this tool supports the use of a properties file for 308 * specifying default values for arguments that aren't specified on the 309 * command line. 310 * 311 * @return {@code true} if this tool supports the use of a properties file 312 * for specifying default values for arguments that aren't specified 313 * on the command line, or {@code false} if not. 314 */ 315 @Override() 316 public boolean supportsPropertiesFile() 317 { 318 // TODO: Add support for using a properties file for subcommand-specific 319 // properties. 320 return true; 321 } 322 323 324 325 /** 326 * Indicates whether this tool should provide arguments for redirecting output 327 * to a file. If this method returns {@code true}, then the tool will offer 328 * an "--outputFile" argument that will specify the path to a file to which 329 * all standard output and standard error content will be written, and it will 330 * also offer a "--teeToStandardOut" argument that can only be used if the 331 * "--outputFile" argument is present and will cause all output to be written 332 * to both the specified output file and to standard output. 333 * 334 * @return {@code true} if this tool should provide arguments for redirecting 335 * output to a file, or {@code false} if not. 336 */ 337 @Override() 338 protected boolean supportsOutputFile() 339 { 340 // This tool provides its own output file support. 341 return false; 342 } 343 344 345 346 /** 347 * Adds the command-line arguments supported for use with this tool to the 348 * provided argument parser. The tool may need to retain references to the 349 * arguments (and/or the argument parser, if trailing arguments are allowed) 350 * to it in order to obtain their values for use in later processing. 351 * 352 * @param parser The argument parser to which the arguments are to be added. 353 * 354 * @throws ArgumentException If a problem occurs while adding any of the 355 * tool-specific arguments to the provided 356 * argument parser. 357 */ 358 @Override() 359 public void addToolArguments(final ArgumentParser parser) 360 throws ArgumentException 361 { 362 this.parser = parser; 363 364 365 // Create the subcommand for encoding data. 366 final ArgumentParser encodeParser = 367 new ArgumentParser("encode", "Base64-encodes raw data."); 368 369 final StringArgument encodeDataArgument = new StringArgument('d', 370 ARG_NAME_DATA, false, 1, "{data}", 371 "The raw data to be encoded. If neither the --" + ARG_NAME_DATA + 372 " nor the --" + ARG_NAME_INPUT_FILE + " argument is provided, " + 373 "then the data will be read from standard input."); 374 encodeDataArgument.addLongIdentifier("rawData"); 375 encodeDataArgument.addLongIdentifier("raw-data"); 376 encodeParser.addArgument(encodeDataArgument); 377 378 final FileArgument encodeDataFileArgument = new FileArgument('f', 379 ARG_NAME_INPUT_FILE, false, 1, null, 380 "The path to a file containing the raw data to be encoded. If " + 381 "neither the --" + ARG_NAME_DATA + " nor the --" + 382 ARG_NAME_INPUT_FILE + " argument is provided, then the data " + 383 "will be read from standard input.", 384 true, true, true, false); 385 encodeDataFileArgument.addLongIdentifier("rawDataFile"); 386 encodeDataFileArgument.addLongIdentifier("input-file"); 387 encodeDataFileArgument.addLongIdentifier("raw-data-file"); 388 encodeParser.addArgument(encodeDataFileArgument); 389 390 final FileArgument encodeOutputFileArgument = new FileArgument('o', 391 ARG_NAME_OUTPUT_FILE, false, 1, null, 392 "The path to a file to which the encoded data should be written. " + 393 "If this is not provided, the encoded data will be written to " + 394 "standard output.", 395 false, true, true, false); 396 encodeOutputFileArgument.addLongIdentifier("toEncodedFile"); 397 encodeOutputFileArgument.addLongIdentifier("output-file"); 398 encodeOutputFileArgument.addLongIdentifier("to-encoded-file"); 399 encodeParser.addArgument(encodeOutputFileArgument); 400 401 final BooleanArgument encodeURLArgument = new BooleanArgument(null, 402 ARG_NAME_URL, 403 "Encode the data with the base64url mechanism rather than the " + 404 "standard base64 mechanism."); 405 encodeParser.addArgument(encodeURLArgument); 406 407 final BooleanArgument encodeIgnoreTrailingEOLArgument = new BooleanArgument( 408 null, ARG_NAME_IGNORE_TRAILING_LINE_BREAK, 409 "Ignore any end-of-line marker that may be present at the end of " + 410 "the data to encode."); 411 encodeIgnoreTrailingEOLArgument.addLongIdentifier( 412 "ignore-trailing-line-break"); 413 encodeParser.addArgument(encodeIgnoreTrailingEOLArgument); 414 415 encodeParser.addExclusiveArgumentSet(encodeDataArgument, 416 encodeDataFileArgument); 417 418 final LinkedHashMap<String[],String> encodeExamples = 419 new LinkedHashMap<String[],String>(3); 420 encodeExamples.put( 421 new String[] 422 { 423 "encode", 424 "--data", "Hello" 425 }, 426 "Base64-encodes the string 'Hello' and writes the result to " + 427 "standard output."); 428 encodeExamples.put( 429 new String[] 430 { 431 "encode", 432 "--inputFile", "raw-data.txt", 433 "--outputFile", "encoded-data.txt", 434 }, 435 "Base64-encodes the data contained in the 'raw-data.txt' file and " + 436 "writes the result to the 'encoded-data.txt' file."); 437 encodeExamples.put( 438 new String[] 439 { 440 "encode" 441 }, 442 "Base64-encodes data read from standard input and writes the result " + 443 "to standard output."); 444 445 final SubCommand encodeSubCommand = new SubCommand(SUBCOMMAND_NAME_ENCODE, 446 "Base64-encodes raw data.", encodeParser, encodeExamples); 447 parser.addSubCommand(encodeSubCommand); 448 449 450 // Create the subcommand for decoding data. 451 final ArgumentParser decodeParser = 452 new ArgumentParser("decode", "Decodes base64-encoded data."); 453 454 final StringArgument decodeDataArgument = new StringArgument('d', 455 ARG_NAME_DATA, false, 1, "{data}", 456 "The base64-encoded data to be decoded. If neither the --" + 457 ARG_NAME_DATA + " nor the --" + ARG_NAME_INPUT_FILE + 458 " argument is provided, then the data will be read from " + 459 "standard input."); 460 decodeDataArgument.addLongIdentifier("encodedData"); 461 decodeDataArgument.addLongIdentifier("encoded-data"); 462 decodeParser.addArgument(decodeDataArgument); 463 464 final FileArgument decodeDataFileArgument = new FileArgument('f', 465 ARG_NAME_INPUT_FILE, false, 1, null, 466 "The path to a file containing the base64-encoded data to be " + 467 "decoded. If neither the --" + ARG_NAME_DATA + " nor the --" + 468 ARG_NAME_INPUT_FILE + " argument is provided, then the data " + 469 "will be read from standard input.", 470 true, true, true, false); 471 decodeDataFileArgument.addLongIdentifier("encodedDataFile"); 472 decodeDataFileArgument.addLongIdentifier("input-file"); 473 decodeDataFileArgument.addLongIdentifier("encoded-data-file"); 474 decodeParser.addArgument(decodeDataFileArgument); 475 476 final FileArgument decodeOutputFileArgument = new FileArgument('o', 477 ARG_NAME_OUTPUT_FILE, false, 1, null, 478 "The path to a file to which the decoded data should be written. " + 479 "If this is not provided, the decoded data will be written to " + 480 "standard output.", 481 false, true, true, false); 482 decodeOutputFileArgument.addLongIdentifier("toRawFile"); 483 decodeOutputFileArgument.addLongIdentifier("output-file"); 484 decodeOutputFileArgument.addLongIdentifier("to-raw-file"); 485 decodeParser.addArgument(decodeOutputFileArgument); 486 487 final BooleanArgument decodeURLArgument = new BooleanArgument(null, 488 ARG_NAME_URL, 489 "Decode the data with the base64url mechanism rather than the " + 490 "standard base64 mechanism."); 491 decodeParser.addArgument(decodeURLArgument); 492 493 final BooleanArgument decodeAddTrailingLineBreak = new BooleanArgument( 494 null, ARG_NAME_ADD_TRAILING_LINE_BREAK, 495 "Add a line break to the end of the decoded data."); 496 decodeAddTrailingLineBreak.addLongIdentifier("add-trailing-line-break"); 497 decodeParser.addArgument(decodeAddTrailingLineBreak); 498 499 decodeParser.addExclusiveArgumentSet(decodeDataArgument, 500 decodeDataFileArgument); 501 502 final LinkedHashMap<String[],String> decodeExamples = 503 new LinkedHashMap<String[],String>(3); 504 decodeExamples.put( 505 new String[] 506 { 507 "decode", 508 "--data", "SGVsbG8=" 509 }, 510 "Base64-decodes the string 'SGVsbG8=' and writes the result to " + 511 "standard output."); 512 decodeExamples.put( 513 new String[] 514 { 515 "decode", 516 "--inputFile", "encoded-data.txt", 517 "--outputFile", "decoded-data.txt", 518 }, 519 "Base64-decodes the data contained in the 'encoded-data.txt' file " + 520 "and writes the result to the 'raw-data.txt' file."); 521 decodeExamples.put( 522 new String[] 523 { 524 "decode" 525 }, 526 "Base64-decodes data read from standard input and writes the result " + 527 "to standard output."); 528 529 final SubCommand decodeSubCommand = new SubCommand(SUBCOMMAND_NAME_DECODE, 530 "Decodes base64-encoded data.", decodeParser, decodeExamples); 531 parser.addSubCommand(decodeSubCommand); 532 } 533 534 535 536 /** 537 * Performs the core set of processing for this tool. 538 * 539 * @return A result code that indicates whether the processing completed 540 * successfully. 541 */ 542 @Override() 543 public ResultCode doToolProcessing() 544 { 545 // Get the subcommand selected by the user. 546 final SubCommand subCommand = parser.getSelectedSubCommand(); 547 if (subCommand == null) 548 { 549 // This should never happen. 550 wrapErr(0, WRAP_COLUMN, "No subcommand was selected."); 551 return ResultCode.PARAM_ERROR; 552 } 553 554 555 // Take the appropriate action based on the selected subcommand. 556 if (subCommand.hasName(SUBCOMMAND_NAME_ENCODE)) 557 { 558 return doEncode(subCommand.getArgumentParser()); 559 } 560 else 561 { 562 return doDecode(subCommand.getArgumentParser()); 563 } 564 } 565 566 567 568 /** 569 * Performs the necessary work for base64 encoding. 570 * 571 * @param p The argument parser for the encode subcommand. 572 * 573 * @return A result code that indicates whether the processing completed 574 * successfully. 575 */ 576 private ResultCode doEncode(final ArgumentParser p) 577 { 578 // Get the data to encode. 579 final ByteStringBuffer rawDataBuffer = new ByteStringBuffer(); 580 final StringArgument dataArg = p.getStringArgument(ARG_NAME_DATA); 581 if ((dataArg != null) && dataArg.isPresent()) 582 { 583 rawDataBuffer.append(dataArg.getValue()); 584 } 585 else 586 { 587 try 588 { 589 final InputStream inputStream; 590 final FileArgument inputFileArg = 591 p.getFileArgument(ARG_NAME_INPUT_FILE); 592 if ((inputFileArg != null) && inputFileArg.isPresent()) 593 { 594 inputStream = new FileInputStream(inputFileArg.getValue()); 595 } 596 else 597 { 598 inputStream = in; 599 } 600 601 final byte[] buffer = new byte[8192]; 602 while (true) 603 { 604 final int bytesRead = inputStream.read(buffer); 605 if (bytesRead <= 0) 606 { 607 break; 608 } 609 610 rawDataBuffer.append(buffer, 0, bytesRead); 611 } 612 613 inputStream.close(); 614 } 615 catch (final Exception e) 616 { 617 Debug.debugException(e); 618 wrapErr(0, WRAP_COLUMN, 619 "An error occurred while attempting to read the data to encode: ", 620 StaticUtils.getExceptionMessage(e)); 621 return ResultCode.LOCAL_ERROR; 622 } 623 } 624 625 626 // If we should ignore any trailing end-of-line markers, then do that now. 627 final BooleanArgument ignoreEOLArg = 628 p.getBooleanArgument(ARG_NAME_IGNORE_TRAILING_LINE_BREAK); 629 if ((ignoreEOLArg != null) && ignoreEOLArg.isPresent()) 630 { 631stripEOLLoop: 632 while (rawDataBuffer.length() > 0) 633 { 634 switch (rawDataBuffer.getBackingArray()[rawDataBuffer.length() - 1]) 635 { 636 case '\n': 637 case '\r': 638 rawDataBuffer.delete(rawDataBuffer.length() - 1, 1); 639 break; 640 default: 641 break stripEOLLoop; 642 } 643 } 644 } 645 646 647 // Base64-encode the data. 648 final byte[] rawDataArray = rawDataBuffer.toByteArray(); 649 final ByteStringBuffer encodedDataBuffer = 650 new ByteStringBuffer(4 * rawDataBuffer.length() / 3 + 3); 651 final BooleanArgument urlArg = p.getBooleanArgument(ARG_NAME_URL); 652 if ((urlArg != null) && urlArg.isPresent()) 653 { 654 Base64.urlEncode(rawDataArray, 0, rawDataArray.length, encodedDataBuffer, 655 false); 656 } 657 else 658 { 659 Base64.encode(rawDataArray, encodedDataBuffer); 660 } 661 662 663 // Write the encoded data. 664 final FileArgument outputFileArg = p.getFileArgument(ARG_NAME_OUTPUT_FILE); 665 if ((outputFileArg != null) && outputFileArg.isPresent()) 666 { 667 try 668 { 669 final FileOutputStream outputStream = 670 new FileOutputStream(outputFileArg.getValue(), false); 671 encodedDataBuffer.write(outputStream); 672 outputStream.write(StaticUtils.EOL_BYTES); 673 outputStream.flush(); 674 outputStream.close(); 675 } 676 catch (final Exception e) 677 { 678 Debug.debugException(e); 679 wrapErr(0, WRAP_COLUMN, 680 "An error occurred while attempting to write the base64-encoded " + 681 "data to output file ", 682 outputFileArg.getValue().getAbsolutePath(), ": ", 683 StaticUtils.getExceptionMessage(e)); 684 err("Base64-encoded data:"); 685 err(encodedDataBuffer.toString()); 686 return ResultCode.LOCAL_ERROR; 687 } 688 } 689 else 690 { 691 out(encodedDataBuffer.toString()); 692 } 693 694 695 return ResultCode.SUCCESS; 696 } 697 698 699 700 /** 701 * Performs the necessary work for base64 decoding. 702 * 703 * @param p The argument parser for the decode subcommand. 704 * 705 * @return A result code that indicates whether the processing completed 706 * successfully. 707 */ 708 private ResultCode doDecode(final ArgumentParser p) 709 { 710 // Get the data to decode. We'll always ignore the following: 711 // - Line breaks 712 // - Blank lines 713 // - Lines that start with an octothorpe (#) 714 // 715 // Unless the --url argument was provided, then we'll also ignore lines that 716 // start with a dash (like those used as start and end markers in a 717 // PEM-encoded certificate). Since dashes are part of the base64url 718 // alphabet, we can't ignore dashes if the --url argument was provided. 719 final ByteStringBuffer encodedDataBuffer = new ByteStringBuffer(); 720 final BooleanArgument urlArg = p.getBooleanArgument(ARG_NAME_URL); 721 final StringArgument dataArg = p.getStringArgument(ARG_NAME_DATA); 722 if ((dataArg != null) && dataArg.isPresent()) 723 { 724 encodedDataBuffer.append(dataArg.getValue()); 725 } 726 else 727 { 728 try 729 { 730 final BufferedReader reader; 731 final FileArgument inputFileArg = 732 p.getFileArgument(ARG_NAME_INPUT_FILE); 733 if ((inputFileArg != null) && inputFileArg.isPresent()) 734 { 735 reader = new BufferedReader(new FileReader(inputFileArg.getValue())); 736 } 737 else 738 { 739 reader = new BufferedReader(new InputStreamReader(in)); 740 } 741 742 while (true) 743 { 744 final String line = reader.readLine(); 745 if (line == null) 746 { 747 break; 748 } 749 750 if ((line.length() == 0) || line.startsWith("#")) 751 { 752 continue; 753 } 754 755 if (line.startsWith("-") && 756 ((urlArg == null) || (! urlArg.isPresent()))) 757 { 758 continue; 759 } 760 761 encodedDataBuffer.append(line); 762 } 763 764 reader.close(); 765 } 766 catch (final Exception e) 767 { 768 Debug.debugException(e); 769 wrapErr(0, WRAP_COLUMN, 770 "An error occurred while attempting to read the data to decode: ", 771 StaticUtils.getExceptionMessage(e)); 772 return ResultCode.LOCAL_ERROR; 773 } 774 } 775 776 777 // Base64-decode the data. 778 final ByteStringBuffer rawDataBuffer = new 779 ByteStringBuffer(encodedDataBuffer.length()); 780 if ((urlArg != null) && urlArg.isPresent()) 781 { 782 try 783 { 784 rawDataBuffer.append(Base64.urlDecode(encodedDataBuffer.toString())); 785 } 786 catch (final Exception e) 787 { 788 Debug.debugException(e); 789 wrapErr(0, WRAP_COLUMN, 790 "An error occurred while attempting to base64url-decode the " + 791 "provided data: " + StaticUtils.getExceptionMessage(e)); 792 return ResultCode.LOCAL_ERROR; 793 } 794 } 795 else 796 { 797 try 798 { 799 rawDataBuffer.append(Base64.decode(encodedDataBuffer.toString())); 800 } 801 catch (final Exception e) 802 { 803 Debug.debugException(e); 804 wrapErr(0, WRAP_COLUMN, 805 "An error occurred while attempting to base64-decode the " + 806 "provided data: " + StaticUtils.getExceptionMessage(e)); 807 return ResultCode.LOCAL_ERROR; 808 } 809 } 810 811 812 // If we should add a newline, then do that now. 813 final BooleanArgument addEOLArg = 814 p.getBooleanArgument(ARG_NAME_ADD_TRAILING_LINE_BREAK); 815 if ((addEOLArg != null) && addEOLArg.isPresent()) 816 { 817 rawDataBuffer.append(StaticUtils.EOL_BYTES); 818 } 819 820 821 // Write the decoded data. 822 final FileArgument outputFileArg = p.getFileArgument(ARG_NAME_OUTPUT_FILE); 823 if ((outputFileArg != null) && outputFileArg.isPresent()) 824 { 825 try 826 { 827 final FileOutputStream outputStream = 828 new FileOutputStream(outputFileArg.getValue(), false); 829 rawDataBuffer.write(outputStream); 830 outputStream.flush(); 831 outputStream.close(); 832 } 833 catch (final Exception e) 834 { 835 Debug.debugException(e); 836 wrapErr(0, WRAP_COLUMN, 837 "An error occurred while attempting to write the base64-decoded " + 838 "data to output file ", 839 outputFileArg.getValue().getAbsolutePath(), ": ", 840 StaticUtils.getExceptionMessage(e)); 841 err("Base64-decoded data:"); 842 err(encodedDataBuffer.toString()); 843 return ResultCode.LOCAL_ERROR; 844 } 845 } 846 else 847 { 848 final byte[] rawDataArray = rawDataBuffer.toByteArray(); 849 getOut().write(rawDataArray, 0, rawDataArray.length); 850 getOut().flush(); 851 } 852 853 854 return ResultCode.SUCCESS; 855 } 856 857 858 859 /** 860 * Retrieves a set of information that may be used to generate example usage 861 * information. Each element in the returned map should consist of a map 862 * between an example set of arguments and a string that describes the 863 * behavior of the tool when invoked with that set of arguments. 864 * 865 * @return A set of information that may be used to generate example usage 866 * information. It may be {@code null} or empty if no example usage 867 * information is available. 868 */ 869 @Override() 870 public LinkedHashMap<String[],String> getExampleUsages() 871 { 872 final LinkedHashMap<String[],String> examples = 873 new LinkedHashMap<String[],String>(2); 874 875 examples.put( 876 new String[] 877 { 878 "encode", 879 "--data", "Hello" 880 }, 881 "Base64-encodes the string 'Hello' and writes the result to " + 882 "standard output."); 883 884 examples.put( 885 new String[] 886 { 887 "decode", 888 "--inputFile", "encoded-data.txt", 889 "--outputFile", "decoded-data.txt", 890 }, 891 "Base64-decodes the data contained in the 'encoded-data.txt' file " + 892 "and writes the result to the 'raw-data.txt' file."); 893 894 return examples; 895 } 896}