View Javadoc

    1   /*
    2    * Copyright 2012 The Netty Project
    3    *
    4    * The Netty Project licenses this file to you under the Apache License,
    5    * version 2.0 (the "License"); you may not use this file except in compliance
    6    * with the License. You may obtain a copy of the License at:
    7    *
    8    *   http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    13   * License for the specific language governing permissions and limitations
    14   * under the License.
    15   */
    16  /*
    17   * Written by Robert Harder and released to the public domain, as explained at
    18   * http://creativecommons.org/licenses/publicdomain
    19   */
    20  package org.jboss.netty.handler.codec.base64;
    21
    22  import org.jboss.netty.buffer.ChannelBuffer;
    23  import org.jboss.netty.buffer.ChannelBufferFactory;
    24  import org.jboss.netty.buffer.HeapChannelBufferFactory;
    25
    26  /**
    27   * Utility class for {@link ChannelBuffer} that encodes and decodes to and from
    28   * <a href="http://en.wikipedia.org/wiki/Base64">Base64</a> notation.
    29   * <p>
    30   * The encoding and decoding algorithm in this class has been derived from
    31   * <a href="http://iharder.sourceforge.net/current/java/base64/">Robert Harder's Public Domain
    32   * Base64 Encoder/Decoder</a>.
    33   *
    34   * @apiviz.landmark
    35   * @apiviz.uses org.jboss.netty.handler.codec.base64.Base64Dialect
    36   */
    37  public final class Base64 {
    38
    39      /** Maximum line length (76) of Base64 output. */
    40      private static final int MAX_LINE_LENGTH = 76;
    41
    42      /** The equals sign (=) as a byte. */
    43      private static final byte EQUALS_SIGN = (byte) '=';
    44
    45      /** The new line character (\n) as a byte. */
    46      private static final byte NEW_LINE = (byte) '\n';
    47
    48      private static final byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
    49
    50      private static final byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
    51
    52      private static byte[] alphabet(Base64Dialect dialect) {
    53          if (dialect == null) {
    54              throw new NullPointerException("dialect");
    55          }
    56          return dialect.alphabet;
    57      }
    58
    59      private static byte[] decodabet(Base64Dialect dialect) {
    60          if (dialect == null) {
    61              throw new NullPointerException("dialect");
    62          }
    63          return dialect.decodabet;
    64      }
    65
    66      private static boolean breakLines(Base64Dialect dialect) {
    67          if (dialect == null) {
    68              throw new NullPointerException("dialect");
    69          }
    70          return dialect.breakLinesByDefault;
    71      }
    72
    73      public static ChannelBuffer encode(ChannelBuffer src) {
    74          return encode(src, Base64Dialect.STANDARD);
    75      }
    76
    77      public static ChannelBuffer encode(ChannelBuffer src, Base64Dialect dialect) {
    78          return encode(src, breakLines(dialect), dialect);
    79      }
    80
    81      public static ChannelBuffer encode(
    82              ChannelBuffer src, ChannelBufferFactory bufferFactory) {
    83          return encode(src, Base64Dialect.STANDARD, bufferFactory);
    84      }
    85
    86      public static ChannelBuffer encode(
    87              ChannelBuffer src, Base64Dialect dialect, ChannelBufferFactory bufferFactory) {
    88          return encode(src, breakLines(dialect), dialect, bufferFactory);
    89      }
    90
    91      public static ChannelBuffer encode(ChannelBuffer src, boolean breakLines) {
    92          return encode(src, breakLines, Base64Dialect.STANDARD);
    93      }
    94
    95      public static ChannelBuffer encode(
    96              ChannelBuffer src, boolean breakLines, Base64Dialect dialect) {
    97          return encode(src, breakLines, dialect, HeapChannelBufferFactory.getInstance());
    98      }
    99
    100     public static ChannelBuffer encode(
    101             ChannelBuffer src, boolean breakLines, ChannelBufferFactory bufferFactory) {
    102         return encode(src, breakLines, Base64Dialect.STANDARD, bufferFactory);
    103     }
    104
    105     public static ChannelBuffer encode(
    106             ChannelBuffer src, boolean breakLines, Base64Dialect dialect, ChannelBufferFactory bufferFactory) {
    107
    108         if (src == null) {
    109             throw new NullPointerException("src");
    110         }
    111
    112         ChannelBuffer dest = encode(
    113                 src, src.readerIndex(), src.readableBytes(), breakLines, dialect, bufferFactory);
    114         src.readerIndex(src.writerIndex());
    115         return dest;
    116     }
    117
    118     public static ChannelBuffer encode(ChannelBuffer src, int off, int len) {
    119         return encode(src, off, len, Base64Dialect.STANDARD);
    120     }
    121
    122     public static ChannelBuffer encode(ChannelBuffer src, int off, int len, Base64Dialect dialect) {
    123         return encode(src, off, len, breakLines(dialect), dialect);
    124     }
    125
    126     public static ChannelBuffer encode(ChannelBuffer src, int off, int len, ChannelBufferFactory bufferFactory) {
    127         return encode(src, off, len, Base64Dialect.STANDARD, bufferFactory);
    128     }
    129
    130     public static ChannelBuffer encode(
    131             ChannelBuffer src, int off, int len, Base64Dialect dialect, ChannelBufferFactory bufferFactory) {
    132         return encode(src, off, len, breakLines(dialect), dialect, bufferFactory);
    133     }
    134
    135     public static ChannelBuffer encode(
    136             ChannelBuffer src, int off, int len, boolean breakLines) {
    137         return encode(src, off, len, breakLines, Base64Dialect.STANDARD);
    138     }
    139
    140     public static ChannelBuffer encode(
    141             ChannelBuffer src, int off, int len,
    142             boolean breakLines, Base64Dialect dialect) {
    143         return encode(src, off, len, breakLines, dialect, HeapChannelBufferFactory.getInstance());
    144     }
    145
    146     public static ChannelBuffer encode(
    147             ChannelBuffer src, int off, int len,
    148             boolean breakLines, ChannelBufferFactory bufferFactory) {
    149         return encode(src, off, len, breakLines, Base64Dialect.STANDARD, bufferFactory);
    150     }
    151
    152     public static ChannelBuffer encode(
    153             ChannelBuffer src, int off, int len,
    154             boolean breakLines, Base64Dialect dialect, ChannelBufferFactory bufferFactory) {
    155
    156         if (src == null) {
    157             throw new NullPointerException("src");
    158         }
    159         if (dialect == null) {
    160             throw new NullPointerException("dialect");
    161         }
    162         if (bufferFactory == null) {
    163             throw new NullPointerException("bufferFactory");
    164         }
    165
    166         int len43 = len * 4 / 3;
    167         ChannelBuffer dest = bufferFactory.getBuffer(
    168                 src.order(),
    169                 len43 +
    170                 (len % 3 > 0? 4 : 0) + // Account for padding
    171                 (breakLines? len43 / MAX_LINE_LENGTH : 0)); // New lines
    172         int d = 0;
    173         int e = 0;
    174         int len2 = len - 2;
    175         int lineLength = 0;
    176         for (; d < len2; d += 3, e += 4) {
    177             encode3to4(src, d + off, 3, dest, e, dialect);
    178
    179             lineLength += 4;
    180             if (breakLines && lineLength == MAX_LINE_LENGTH) {
    181                 dest.setByte(e + 4, NEW_LINE);
    182                 e ++;
    183                 lineLength = 0;
    184             } // end if: end of line
    185         } // end for: each piece of array
    186
    187         if (d < len) {
    188             encode3to4(src, d + off, len - d, dest, e, dialect);
    189             e += 4;
    190         } // end if: some padding needed
    191
    192         return dest.slice(0, e);
    193     }
    194
    195     private static void encode3to4(
    196             ChannelBuffer src, int srcOffset, int numSigBytes,
    197             ChannelBuffer dest, int destOffset, Base64Dialect dialect) {
    198
    199         byte[] ALPHABET = alphabet(dialect);
    200
    201         //           1         2         3
    202         // 01234567890123456789012345678901 Bit position
    203         // --------000000001111111122222222 Array position from threeBytes
    204         // --------|    ||    ||    ||    | Six bit groups to index ALPHABET
    205         //          >>18  >>12  >> 6  >> 0  Right shift necessary
    206         //                0x3f  0x3f  0x3f  Additional AND
    207
    208         // Create buffer with zero-padding if there are only one or two
    209         // significant bytes passed in the array.
    210         // We have to shift left 24 in order to flush out the 1's that appear
    211         // when Java treats a value as negative that is cast from a byte to an int.
    212         int inBuff =
    213                 (numSigBytes > 0? src.getByte(srcOffset)     << 24 >>>  8 : 0) |
    214                 (numSigBytes > 1? src.getByte(srcOffset + 1) << 24 >>> 16 : 0) |
    215                 (numSigBytes > 2? src.getByte(srcOffset + 2) << 24 >>> 24 : 0);
    216
    217         switch (numSigBytes) {
    218         case 3:
    219             dest.setByte(destOffset    , ALPHABET[inBuff >>> 18       ]);
    220             dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
    221             dest.setByte(destOffset + 2, ALPHABET[inBuff >>>  6 & 0x3f]);
    222             dest.setByte(destOffset + 3, ALPHABET[inBuff        & 0x3f]);
    223             break;
    224         case 2:
    225             dest.setByte(destOffset    , ALPHABET[inBuff >>> 18       ]);
    226             dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
    227             dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6  & 0x3f]);
    228             dest.setByte(destOffset + 3, EQUALS_SIGN);
    229             break;
    230         case 1:
    231             dest.setByte(destOffset    , ALPHABET[inBuff >>> 18       ]);
    232             dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
    233             dest.setByte(destOffset + 2, EQUALS_SIGN);
    234             dest.setByte(destOffset + 3, EQUALS_SIGN);
    235             break;
    236         }
    237     }
    238
    239     public static ChannelBuffer decode(ChannelBuffer src) {
    240         return decode(src, Base64Dialect.STANDARD);
    241     }
    242
    243     public static ChannelBuffer decode(ChannelBuffer src, Base64Dialect dialect) {
    244         return decode(src, dialect, HeapChannelBufferFactory.getInstance());
    245     }
    246
    247     public static ChannelBuffer decode(ChannelBuffer src, ChannelBufferFactory bufferFactory) {
    248         return decode(src, Base64Dialect.STANDARD, bufferFactory);
    249     }
    250
    251     public static ChannelBuffer decode(ChannelBuffer src, Base64Dialect dialect, ChannelBufferFactory bufferFactory) {
    252
    253         if (src == null) {
    254             throw new NullPointerException("src");
    255         }
    256
    257         ChannelBuffer dest = decode(src, src.readerIndex(), src.readableBytes(), dialect, bufferFactory);
    258         src.readerIndex(src.writerIndex());
    259         return dest;
    260     }
    261
    262     public static ChannelBuffer decode(
    263             ChannelBuffer src, int off, int len) {
    264         return decode(src, off, len, Base64Dialect.STANDARD);
    265     }
    266
    267     public static ChannelBuffer decode(
    268             ChannelBuffer src, int off, int len, Base64Dialect dialect) {
    269         return decode(src, off, len, dialect, HeapChannelBufferFactory.getInstance());
    270     }
    271
    272     public static ChannelBuffer decode(
    273             ChannelBuffer src, int off, int len, ChannelBufferFactory bufferFactory) {
    274         return decode(src, off, len, Base64Dialect.STANDARD, bufferFactory);
    275     }
    276
    277     public static ChannelBuffer decode(
    278             ChannelBuffer src, int off, int len, Base64Dialect dialect,
    279             ChannelBufferFactory bufferFactory) {
    280
    281         if (src == null) {
    282             throw new NullPointerException("src");
    283         }
    284         if (dialect == null) {
    285             throw new NullPointerException("dialect");
    286         }
    287         if (bufferFactory == null) {
    288             throw new NullPointerException("bufferFactory");
    289         }
    290
    291         byte[] DECODABET = decodabet(dialect);
    292
    293         int len34 = len * 3 / 4;
    294         ChannelBuffer dest = bufferFactory.getBuffer(src.order(), len34); // Upper limit on size of output
    295         int outBuffPosn = 0;
    296
    297         byte[] b4 = new byte[4];
    298         int b4Posn = 0;
    299         int i = 0;
    300         byte sbiCrop = 0;
    301         byte sbiDecode = 0;
    302         for (i = off; i < off + len; i ++) {
    303             sbiCrop = (byte) (src.getByte(i) & 0x7f); // Only the low seven bits
    304             sbiDecode = DECODABET[sbiCrop];
    305
    306             if (sbiDecode >= WHITE_SPACE_ENC) { // White space, Equals sign or better
    307                 if (sbiDecode >= EQUALS_SIGN_ENC) { // Equals sign or better?
    308                     b4[b4Posn ++] = sbiCrop;
    309                     if (b4Posn > 3) { // Quartet built?
    310                         outBuffPosn += decode4to3(
    311                                 b4, 0, dest, outBuffPosn, dialect);
    312                         b4Posn = 0;
    313
    314                         // If that was the equals sign, break out of 'for' loop
    315                         if (sbiCrop == EQUALS_SIGN) {
    316                             break;
    317                         }
    318                     }
    319                 }
    320             } else {
    321                 throw new IllegalArgumentException(
    322                         "bad Base64 input character at " + i + ": " +
    323                         src.getUnsignedByte(i) + " (decimal)");
    324             }
    325         }
    326
    327         return dest.slice(0, outBuffPosn);
    328     }
    329
    330     private static int decode4to3(
    331             byte[] src, int srcOffset,
    332             ChannelBuffer dest, int destOffset, Base64Dialect dialect) {
    333
    334         byte[] DECODABET = decodabet(dialect);
    335
    336         if (src[srcOffset + 2] == EQUALS_SIGN) {
    337             // Example: Dk==
    338             int outBuff =
    339                     (DECODABET[src[srcOffset    ]] & 0xFF) << 18 |
    340                     (DECODABET[src[srcOffset + 1]] & 0xFF) << 12;
    341
    342             dest.setByte(destOffset, (byte) (outBuff >>> 16));
    343             return 1;
    344         } else if (src[srcOffset + 3] == EQUALS_SIGN) {
    345             // Example: DkL=
    346             int outBuff =
    347                     (DECODABET[src[srcOffset    ]] & 0xFF) << 18 |
    348                     (DECODABET[src[srcOffset + 1]] & 0xFF) << 12 |
    349                     (DECODABET[src[srcOffset + 2]] & 0xFF) <<  6;
    350
    351             dest.setByte(destOffset    , (byte) (outBuff >>> 16));
    352             dest.setByte(destOffset + 1, (byte) (outBuff >>>  8));
    353             return 2;
    354         } else {
    355             // Example: DkLE
    356             int outBuff;
    357             try {
    358                 outBuff =
    359                         (DECODABET[src[srcOffset    ]] & 0xFF) << 18 |
    360                         (DECODABET[src[srcOffset + 1]] & 0xFF) << 12 |
    361                         (DECODABET[src[srcOffset + 2]] & 0xFF) <<  6 |
    362                          DECODABET[src[srcOffset + 3]] & 0xFF;
    363             } catch (IndexOutOfBoundsException e) {
    364                 throw new IllegalArgumentException("not encoded in Base64");
    365             }
    366
    367             dest.setByte(destOffset    , (byte) (outBuff >> 16));
    368             dest.setByte(destOffset + 1, (byte) (outBuff >>  8));
    369             dest.setByte(destOffset + 2, (byte)  outBuff);
    370             return 3;
    371         }
    372     }
    373
    374     private Base64() {
    375         // Unused
    376     }
    377 }