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