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