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;
300         byte sbiCrop;
301         byte sbiDecode;
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 }