View Javadoc

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