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