View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.util;
    21
    22  import java.security.InvalidParameterException;
    23
    24  /**
    25   * Provides Base64 encoding and decoding as defined by RFC 2045.
    26   *
    27   * <p>This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite>
    28   * from RFC 2045 <cite>Multipurpose Internet Mail Extensions (MIME) Part One:
    29   * Format of Internet Message Bodies</cite> by Freed and Borenstein.</p>
    30   *
    31   * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
    32   *
    33   *  This class was
    34   * @author Apache Software Foundation commons codec (http://commons.apache.org/codec/)
    35   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    36   */
    37  public class Base64 {
    38
    39      /**
    40       * Chunk size per RFC 2045 section 6.8.
    41       *
    42       * <p>The {@value} character limit does not count the trailing CRLF, but counts
    43       * all other characters, including any equal signs.</p>
    44       *
    45       * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
    46       */
    47      static final int CHUNK_SIZE = 76;
    48
    49      /**
    50       * Chunk separator per RFC 2045 section 2.1.
    51       *
    52       * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
    53       */
    54      static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
    55
    56      /**
    57       * The base length.
    58       */
    59      static final int BASELENGTH = 255;
    60
    61      /**
    62       * Lookup length.
    63       */
    64      static final int LOOKUPLENGTH = 64;
    65
    66      /**
    67       * Used to calculate the number of bits in a byte.
    68       */
    69      static final int EIGHTBIT = 8;
    70
    71      /**
    72       * Used when encoding something which has fewer than 24 bits.
    73       */
    74      static final int SIXTEENBIT = 16;
    75
    76      /**
    77       * Used to determine how many bits data contains.
    78       */
    79      static final int TWENTYFOURBITGROUP = 24;
    80
    81      /**
    82       * Used to get the number of Quadruples.
    83       */
    84      static final int FOURBYTE = 4;
    85
    86      /**
    87       * Used to test the sign of a byte.
    88       */
    89      static final int SIGN = -128;
    90
    91      /**
    92       * Byte used to pad output.
    93       */
    94      static final byte PAD = (byte) '=';
    95
    96      // Create arrays to hold the base64 characters and a
    97      // lookup for base64 chars
    98      private static byte[] base64Alphabet = new byte[BASELENGTH];
    99
    100     private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
    101
    102     // Populating the lookup and character arrays
    103     static {
    104         for (int i = 0; i < BASELENGTH; i++) {
    105             base64Alphabet[i] = (byte) -1;
    106         }
    107         for (int i = 'Z'; i >= 'A'; i--) {
    108             base64Alphabet[i] = (byte) (i - 'A');
    109         }
    110         for (int i = 'z'; i >= 'a'; i--) {
    111             base64Alphabet[i] = (byte) (i - 'a' + 26);
    112         }
    113         for (int i = '9'; i >= '0'; i--) {
    114             base64Alphabet[i] = (byte) (i - '0' + 52);
    115         }
    116
    117         base64Alphabet['+'] = 62;
    118         base64Alphabet['/'] = 63;
    119
    120         for (int i = 0; i <= 25; i++) {
    121             lookUpBase64Alphabet[i] = (byte) ('A' + i);
    122         }
    123
    124         for (int i = 26, j = 0; i <= 51; i++, j++) {
    125             lookUpBase64Alphabet[i] = (byte) ('a' + j);
    126         }
    127
    128         for (int i = 52, j = 0; i <= 61; i++, j++) {
    129             lookUpBase64Alphabet[i] = (byte) ('0' + j);
    130         }
    131
    132         lookUpBase64Alphabet[62] = (byte) '+';
    133         lookUpBase64Alphabet[63] = (byte) '/';
    134     }
    135
    136     private static boolean isBase64(byte octect) {
    137         if (octect == PAD) {
    138             return true;
    139         } else if (base64Alphabet[octect] == -1) {
    140             return false;
    141         } else {
    142             return true;
    143         }
    144     }
    145
    146     /**
    147      * Tests a given byte array to see if it contains
    148      * only valid characters within the Base64 alphabet.
    149      *
    150      * @param arrayOctect byte array to test
    151      * @return true if all bytes are valid characters in the Base64
    152      *         alphabet or if the byte array is empty; false, otherwise
    153      */
    154     public static boolean isArrayByteBase64(byte[] arrayOctect) {
    155
    156         arrayOctect = discardWhitespace(arrayOctect);
    157
    158         int length = arrayOctect.length;
    159         if (length == 0) {
    160             // shouldn't a 0 length array be valid base64 data?
    161             // return false;
    162             return true;
    163         }
    164         for (int i = 0; i < length; i++) {
    165             if (!isBase64(arrayOctect[i])) {
    166                 return false;
    167             }
    168         }
    169         return true;
    170     }
    171
    172     /**
    173      * Encodes binary data using the base64 algorithm but
    174      * does not chunk the output.
    175      *
    176      * @param binaryData binary data to encode
    177      * @return Base64 characters
    178      */
    179     public static byte[] encodeBase64(byte[] binaryData) {
    180         return encodeBase64(binaryData, false);
    181     }
    182
    183     /**
    184      * Encodes binary data using the base64 algorithm and chunks
    185      * the encoded output into 76 character blocks
    186      *
    187      * @param binaryData binary data to encode
    188      * @return Base64 characters chunked in 76 character blocks
    189      */
    190     public static byte[] encodeBase64Chunked(byte[] binaryData) {
    191         return encodeBase64(binaryData, true);
    192     }
    193
    194     /**
    195      * Decodes an Object using the base64 algorithm.  This method
    196      * is provided in order to satisfy the requirements of the
    197      * Decoder interface, and will throw a DecoderException if the
    198      * supplied object is not of type byte[].
    199      *
    200      * @param pObject Object to decode
    201      * @return An object (of type byte[]) containing the
    202      *         binary data which corresponds to the byte[] supplied.
    203      * @throws InvalidParameterException if the parameter supplied is not
    204      *                          of type byte[]
    205      */
    206     public Object decode(Object pObject) {
    207         if (!(pObject instanceof byte[])) {
    208             throw new InvalidParameterException("Parameter supplied to Base64 decode is not a byte[]");
    209         }
    210         return decode((byte[]) pObject);
    211     }
    212
    213     /**
    214      * Decodes a byte[] containing containing
    215      * characters in the Base64 alphabet.
    216      *
    217      * @param pArray A byte array containing Base64 character data
    218      * @return a byte array containing binary data
    219      */
    220     public byte[] decode(byte[] pArray) {
    221         return decodeBase64(pArray);
    222     }
    223
    224     /**
    225      * Encodes binary data using the base64 algorithm, optionally
    226      * chunking the output into 76 character blocks.
    227      *
    228      * @param binaryData Array containing binary data to encode.
    229      * @param isChunked if isChunked is true this encoder will chunk
    230      *                  the base64 output into 76 character blocks
    231      * @return Base64-encoded data.
    232      */
    233     public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
    234         int lengthDataBits = binaryData.length * EIGHTBIT;
    235         int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
    236         int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
    237         byte encodedData[] = null;
    238         int encodedDataLength = 0;
    239         int nbrChunks = 0;
    240
    241         if (fewerThan24bits != 0) {
    242             //data not divisible by 24 bit
    243             encodedDataLength = (numberTriplets + 1) * 4;
    244         } else {
    245             // 16 or 8 bit
    246             encodedDataLength = numberTriplets * 4;
    247         }
    248
    249         // If the output is to be "chunked" into 76 character sections,
    250         // for compliance with RFC 2045 MIME, then it is important to
    251         // allow for extra length to account for the separator(s)
    252         if (isChunked) {
    253
    254             nbrChunks = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math.ceil((float) encodedDataLength / CHUNK_SIZE));
    255             encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length;
    256         }
    257
    258         encodedData = new byte[encodedDataLength];
    259
    260         byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
    261
    262         int encodedIndex = 0;
    263         int dataIndex = 0;
    264         int i = 0;
    265         int nextSeparatorIndex = CHUNK_SIZE;
    266         int chunksSoFar = 0;
    267
    268         //log.debug("number of triplets = " + numberTriplets);
    269         for (i = 0; i < numberTriplets; i++) {
    270             dataIndex = i * 3;
    271             b1 = binaryData[dataIndex];
    272             b2 = binaryData[dataIndex + 1];
    273             b3 = binaryData[dataIndex + 2];
    274
    275             //log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
    276
    277             l = (byte) (b2 & 0x0f);
    278             k = (byte) (b1 & 0x03);
    279
    280             byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
    281             byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
    282             byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
    283
    284             encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
    285             //log.debug( "val2 = " + val2 );
    286             //log.debug( "k4   = " + (k<<4) );
    287             //log.debug(  "vak  = " + (val2 | (k<<4)) );
    288             encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
    289             encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3];
    290             encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
    291
    292             encodedIndex += 4;
    293
    294             // If we are chunking, let's put a chunk separator down.
    295             if (isChunked) {
    296                 // this assumes that CHUNK_SIZE % 4 == 0
    297                 if (encodedIndex == nextSeparatorIndex) {
    298                     System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length);
    299                     chunksSoFar++;
    300                     nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length);
    301                     encodedIndex += CHUNK_SEPARATOR.length;
    302                 }
    303             }
    304         }
    305
    306         // form integral number of 6-bit groups
    307         dataIndex = i * 3;
    308
    309         if (fewerThan24bits == EIGHTBIT) {
    310             b1 = binaryData[dataIndex];
    311             k = (byte) (b1 & 0x03);
    312             //log.debug("b1=" + b1);
    313             //log.debug("b1<<2 = " + (b1>>2) );
    314             byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
    315             encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
    316             encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
    317             encodedData[encodedIndex + 2] = PAD;
    318             encodedData[encodedIndex + 3] = PAD;
    319         } else if (fewerThan24bits == SIXTEENBIT) {
    320
    321             b1 = binaryData[dataIndex];
    322             b2 = binaryData[dataIndex + 1];
    323             l = (byte) (b2 & 0x0f);
    324             k = (byte) (b1 & 0x03);
    325
    326             byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
    327             byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
    328
    329             encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
    330             encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
    331             encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
    332             encodedData[encodedIndex + 3] = PAD;
    333         }
    334
    335         if (isChunked) {
    336             // we also add a separator to the end of the final chunk.
    337             if (chunksSoFar < nbrChunks) {
    338                 System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length,
    339                         CHUNK_SEPARATOR.length);
    340             }
    341         }
    342
    343         return encodedData;
    344     }
    345
    346     /**
    347      * Decodes Base64 data into octects
    348      *
    349      * @param base64Data Byte array containing Base64 data
    350      * @return Array containing decoded data.
    351      */
    352     public static byte[] decodeBase64(byte[] base64Data) {
    353         // RFC 2045 requires that we discard ALL non-Base64 characters
    354         base64Data = discardNonBase64(base64Data);
    355
    356         // handle the edge case, so we don't have to worry about it later
    357         if (base64Data.length == 0) {
    358             return new byte[0];
    359         }
    360
    361         int numberQuadruple = base64Data.length / FOURBYTE;
    362         byte decodedData[] = null;
    363         byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
    364
    365         // Throw away anything not in base64Data
    366
    367         int encodedIndex = 0;
    368         int dataIndex = 0;
    369         {
    370             // this sizes the output array properly - rlw
    371             int lastData = base64Data.length;
    372             // ignore the '=' padding
    373             while (base64Data[lastData - 1] == PAD) {
    374                 if (--lastData == 0) {
    375                     return new byte[0];
    376                 }
    377             }
    378             decodedData = new byte[lastData - numberQuadruple];
    379         }
    380
    381         for (int i = 0; i < numberQuadruple; i++) {
    382             dataIndex = i * 4;
    383             marker0 = base64Data[dataIndex + 2];
    384             marker1 = base64Data[dataIndex + 3];
    385
    386             b1 = base64Alphabet[base64Data[dataIndex]];
    387             b2 = base64Alphabet[base64Data[dataIndex + 1]];
    388
    389             if (marker0 != PAD && marker1 != PAD) {
    390                 //No PAD e.g 3cQl
    391                 b3 = base64Alphabet[marker0];
    392                 b4 = base64Alphabet[marker1];
    393
    394                 decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
    395                 decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
    396                 decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
    397             } else if (marker0 == PAD) {
    398                 //Two PAD e.g. 3c[Pad][Pad]
    399                 decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
    400             } else if (marker1 == PAD) {
    401                 //One PAD e.g. 3cQ[Pad]
    402                 b3 = base64Alphabet[marker0];
    403
    404                 decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
    405                 decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
    406             }
    407             encodedIndex += 3;
    408         }
    409         return decodedData;
    410     }
    411
    412     /**
    413      * Discards any whitespace from a base-64 encoded block.
    414      *
    415      * @param data The base-64 encoded data to discard the whitespace
    416      * from.
    417      * @return The data, less whitespace (see RFC 2045).
    418      */
    419     static byte[] discardWhitespace(byte[] data) {
    420         byte groomedData[] = new byte[data.length];
    421         int bytesCopied = 0;
    422
    423         for (int i = 0; i < data.length; i++) {
    424             switch (data[i]) {
    425             case (byte) ' ':
    426             case (byte) '\n':
    427             case (byte) '\r':
    428             case (byte) '\t':
    429                 break;
    430             default:
    431                 groomedData[bytesCopied++] = data[i];
    432             }
    433         }
    434
    435         byte packedData[] = new byte[bytesCopied];
    436
    437         System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
    438
    439         return packedData;
    440     }
    441
    442     /**
    443      * Discards any characters outside of the base64 alphabet, per
    444      * the requirements on page 25 of RFC 2045 - "Any characters
    445      * outside of the base64 alphabet are to be ignored in base64
    446      * encoded data."
    447      *
    448      * @param data The base-64 encoded data to groom
    449      * @return The data, less non-base64 characters (see RFC 2045).
    450      */
    451     static byte[] discardNonBase64(byte[] data) {
    452         byte groomedData[] = new byte[data.length];
    453         int bytesCopied = 0;
    454
    455         for (int i = 0; i < data.length; i++) {
    456             if (isBase64(data[i])) {
    457                 groomedData[bytesCopied++] = data[i];
    458             }
    459         }
    460
    461         byte packedData[] = new byte[bytesCopied];
    462
    463         System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
    464
    465         return packedData;
    466     }
    467
    468     // Implementation of the Encoder Interface
    469
    470     /**
    471      * Encodes an Object using the base64 algorithm.  This method
    472      * is provided in order to satisfy the requirements of the
    473      * Encoder interface, and will throw an EncoderException if the
    474      * supplied object is not of type byte[].
    475      *
    476      * @param pObject Object to encode
    477      * @return An object (of type byte[]) containing the
    478      *         base64 encoded data which corresponds to the byte[] supplied.
    479      * @throws InvalidParameterException if the parameter supplied is not
    480      *                          of type byte[]
    481      */
    482     public Object encode(Object pObject) {
    483         if (!(pObject instanceof byte[])) {
    484             throw new InvalidParameterException("Parameter supplied to Base64 encode is not a byte[]");
    485         }
    486         return encode((byte[]) pObject);
    487     }
    488
    489     /**
    490      * Encodes a byte[] containing binary data, into a byte[] containing
    491      * characters in the Base64 alphabet.
    492      *
    493      * @param pArray a byte array containing binary data
    494      * @return A byte array containing only Base64 character data
    495      */
    496     public byte[] encode(byte[] pArray) {
    497         return encodeBase64(pArray, false);
    498     }
    499
    500 }