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