Base64.java

/*
** Module   :Base64.java
** Abstract :String <-> byte array conversion using base64 encoding.
**
** Copyright (c) 2004-2020, Golden Code Development Corporation.

**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 SIY 20041228  @20014  Created initial version
** 002 SIY 20050224  @20014  Updated for use by Directory package.
** 003 SIY 20050310  @20353  Organized imports.
** 004 SIY 20050323  @20467  Fixed formatting and comments.  
** 005 SIY 20050429  @21006  Fixed formatting.  
** 006 CA  20200519          Added safeBase64ToByteArray.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.directory;

import java.io.ByteArrayOutputStream;

/**
 * This class provides static methods to perform base64 encoding/decoding.
 * Code is based on the base64 implementation from the RXIP project. Note that
 * resulting base64 output is not completely conforms canonical rules: it does
 * not split string into chunks at most 76 chars long separated by CRLF
 * sequence.
 * 
 * @author  SIY
 * @version 1.0
 */
public class Base64
{
   /**
    * Character conversion table from RFC2045
    *
    * Value Encoding  Value Encoding  Value Encoding  Value Encoding
    *       0 A            17 R            34 i            51 z
    *       1 B            18 S            35 j            52 0
    *       2 C            19 T            36 k            53 1
    *       3 D            20 U            37 l            54 2
    *       4 E            21 V            38 m            55 3
    *       5 F            22 W            39 n            56 4
    *       6 G            23 X            40 o            57 5
    *       7 H            24 Y            41 p            58 6
    *       8 I            25 Z            42 q            59 7
    *       9 J            26 a            43 r            60 8
    *      10 K            27 b            44 s            61 9
    *      11 L            28 c            45 t            62 +
    *      12 M            29 d            46 u            63 /
    *      13 N            30 e            47 v
    *      14 O            31 f            48 w         (pad) =
    *      15 P            32 g            49 x
    *      16 Q            33 h            50 y
    */
   private static char table1[]=
   {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
      'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
      'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
      'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
      'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
      'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
      'w', 'x', 'y', 'z', '0', '1', '2', '3',
      '4', '5', '6', '7', '8', '9', '+', '/',
      '='
   };

   /**
    * Decode input <code>String</code> into byte array.
    * <p>
    * If the input's length is not a multiple of 4, chars will be removed from its end.
    * <p>
    * If the input is null, then the returned value will be null.
    * 
    * @param   input
    *          Source data consisting.
    *          
    * @return  byte array converted into <code>String</code> consisting characters from limited range.
    */
   public static byte[] safeBase64ToByteArray(String input)
   {
      if (input == null)
      {
         return null;
      }
      String s = "";
      for (int i = 0; i < input.length(); i++)
      {
         char c = input.charAt(i);
         if (c == '+' || c == '/' || c == '=' || 
             (c >= '0' && c <= '9') || 
             (c >= 'A' && c <= 'Z') || 
             (c >= 'a' && c <= 'z'))
         {
            s = s + c;
         }
      }
      while (s.length() % 4 != 0)
      {
         s = s.substring(0, s.length() - 1);
      }
      
      return base64ToByteArray(s);
   }

   /**
    * Convert input byte array into <code>String</code>.
    * 
    * @param   src
    *          Input byte array.
    * @return  byte array converted into <code>String</code> consisting
    *          characters from limited range.
    */
   static public String byteArrayToBase64(byte[] src)
   {
      if (src == null)
         return null;

      int cout[] = new int[4];
      char buf[] = new char[(src.length + 2) * 2];
      int iOut = 0;
      int iPos = 0;

      while (iPos < src.length)
      {
         cout[0] = (src[iPos] & 0xFC) >> 2;
         cout[1] = (src[iPos] & 0x03) << 4;

         iPos++;

         if (iPos == src.length)
         {
            cout[2] = 64;
         }
         else
         {
            cout[1] |= (src[iPos] & 0xF0) >> 4;
            cout[2] = (src[iPos] & 0x0F) << 2;
            iPos++;
         }

         if (iPos == src.length)
         {
            cout[3] = 64;
         }
         else
         {
            cout[2] |= (src[iPos] & 0xC0) >> 6;
            cout[3] = (src[iPos] & 0x3F);
            iPos++;
         }

         buf[iOut++] = table1[cout[0]];
         buf[iOut++] = table1[cout[1]];
         buf[iOut++] = table1[cout[2]];
         buf[iOut++] = table1[cout[3]];
      }

      return new String(buf, 0, iOut);
   }

   /**
    * Decode input <code>String</code> into byte array.
    * 
    * @param   input
    *          Source data consisting.
    * @return  decoded byte array.
    */
   static public byte[] base64ToByteArray(String input)
   {
      if (input == null)
         return null;

      ByteArrayOutputStream buf = new ByteArrayOutputStream(input.length());

      int uBuf = 0;
      int iCnt = 0;

      for (int i = 0; i < input.length(); i++)
      {
         int chr = input.charAt(i);

         do
         {
            if (chr >= 'A' && chr <= 'Z')
            {
               uBuf <<= 6;
               uBuf |= (chr - 'A') & 0x3F;
               iCnt += 6;
               break;
            }

            if (chr >= 'a' && chr <= 'z')
            {
               uBuf <<= 6;
               uBuf |= (chr - 'a' + 26) & 0x3F;
               iCnt += 6;
               break;
            }

            if (chr >= '0' && chr <= '9')
            {
               uBuf <<= 6;
               uBuf |= (chr - '0' + 52) & 0x3F;
               iCnt += 6;
               break;
            }

            if (chr == '+' || chr == '/')
            {
               uBuf <<= 6;
               uBuf |= ((chr == '+') ? 62 : 63) & 0x3F;
               iCnt += 6;
               break;
            }
            continue;
         }
         while (false);

         if (iCnt >= 8)
         {
            buf.write((uBuf >> (iCnt - 8)) & 0xFF);
            iCnt -= 8;
         }
      }

      try
      {
         buf.close();
      }
      catch (Exception e)
      {
      }

      return buf.toByteArray();
   }
}