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