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 io.netty.handler.codec.base64;
21  
22  import io.netty.buffer.ByteBuf;
23  import io.netty.buffer.ByteBufAllocator;
24  import io.netty.buffer.ByteBufProcessor;
25  import io.netty.util.internal.PlatformDependent;
26  
27  import java.nio.ByteOrder;
28  
29  /**
30   * Utility class for {@link ByteBuf} that encodes and decodes to and from
31   * <a href="http://en.wikipedia.org/wiki/Base64">Base64</a> notation.
32   * <p>
33   * The encoding and decoding algorithm in this class has been derived from
34   * <a href="http://iharder.sourceforge.net/current/java/base64/">Robert Harder's Public Domain
35   * Base64 Encoder/Decoder</a>.
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 ByteBuf encode(ByteBuf src) {
74          return encode(src, Base64Dialect.STANDARD);
75      }
76  
77      public static ByteBuf encode(ByteBuf src, Base64Dialect dialect) {
78          return encode(src, breakLines(dialect), dialect);
79      }
80  
81      public static ByteBuf encode(ByteBuf src, boolean breakLines) {
82          return encode(src, breakLines, Base64Dialect.STANDARD);
83      }
84  
85      public static ByteBuf encode(ByteBuf src, boolean breakLines, Base64Dialect dialect) {
86  
87          if (src == null) {
88              throw new NullPointerException("src");
89          }
90  
91          ByteBuf dest = encode(src, src.readerIndex(), src.readableBytes(), breakLines, dialect);
92          src.readerIndex(src.writerIndex());
93          return dest;
94      }
95  
96      public static ByteBuf encode(ByteBuf src, int off, int len) {
97          return encode(src, off, len, Base64Dialect.STANDARD);
98      }
99  
100     public static ByteBuf encode(ByteBuf src, int off, int len, Base64Dialect dialect) {
101         return encode(src, off, len, breakLines(dialect), dialect);
102     }
103 
104     public static ByteBuf encode(
105             ByteBuf src, int off, int len, boolean breakLines) {
106         return encode(src, off, len, breakLines, Base64Dialect.STANDARD);
107     }
108 
109     public static ByteBuf encode(
110             ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect) {
111         return encode(src, off, len, breakLines, dialect, src.alloc());
112     }
113 
114     public static ByteBuf encode(
115             ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator) {
116         if (src == null) {
117             throw new NullPointerException("src");
118         }
119         if (dialect == null) {
120             throw new NullPointerException("dialect");
121         }
122 
123         ByteBuf dest = allocator.buffer(encodedBufferSize(len, breakLines)).order(src.order());
124         byte[] alphabet = alphabet(dialect);
125         int d = 0;
126         int e = 0;
127         int len2 = len - 2;
128         int lineLength = 0;
129         for (; d < len2; d += 3, e += 4) {
130             encode3to4(src, d + off, 3, dest, e, alphabet);
131 
132             lineLength += 4;
133 
134             if (breakLines && lineLength == MAX_LINE_LENGTH) {
135                 dest.setByte(e + 4, NEW_LINE);
136                 e ++;
137                 lineLength = 0;
138             } // end if: end of line
139         } // end for: each piece of array
140 
141         if (d < len) {
142             encode3to4(src, d + off, len - d, dest, e, alphabet);
143             e += 4;
144         } // end if: some padding needed
145 
146         // Remove last byte if it's a newline
147         if (e > 1 && dest.getByte(e - 1) == NEW_LINE) {
148             e--;
149         }
150 
151         return dest.slice(0, e);
152     }
153 
154     private static void encode3to4(
155             ByteBuf src, int srcOffset, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
156         //           1         2         3
157         // 01234567890123456789012345678901 Bit position
158         // --------000000001111111122222222 Array position from threeBytes
159         // --------|    ||    ||    ||    | Six bit groups to index ALPHABET
160         //          >>18  >>12  >> 6  >> 0  Right shift necessary
161         //                0x3f  0x3f  0x3f  Additional AND
162 
163         // Create buffer with zero-padding if there are only one or two
164         // significant bytes passed in the array.
165         // We have to shift left 24 in order to flush out the 1's that appear
166         // when Java treats a value as negative that is cast from a byte to an int.
167         if (src.order() == ByteOrder.BIG_ENDIAN) {
168             final int inBuff;
169             switch (numSigBytes) {
170                 case 1:
171                     inBuff = toInt(src.getByte(srcOffset));
172                     break;
173                 case 2:
174                     inBuff = toIntBE(src.getShort(srcOffset));
175                     break;
176                 default:
177                     inBuff = numSigBytes <= 0 ? 0 : toIntBE(src.getMedium(srcOffset));
178                     break;
179             }
180             encode3to4BigEndian(inBuff, numSigBytes, dest, destOffset, alphabet);
181         } else {
182             final int inBuff;
183             switch (numSigBytes) {
184                 case 1:
185                     inBuff = toInt(src.getByte(srcOffset));
186                     break;
187                 case 2:
188                     inBuff = toIntLE(src.getShort(srcOffset));
189                     break;
190                 default:
191                     inBuff = numSigBytes <= 0 ? 0 : toIntLE(src.getMedium(srcOffset));
192                     break;
193             }
194             encode3to4LittleEndian(inBuff, numSigBytes, dest, destOffset, alphabet);
195         }
196     }
197 
198     // package-private for testing
199     static int encodedBufferSize(int len, boolean breakLines) {
200         // Cast len to long to prevent overflow
201         long len43 = ((long) len << 2) / 3;
202 
203         // Account for padding
204         long ret = (len43 + 3) & ~3;
205 
206         if (breakLines) {
207             ret += len43 / MAX_LINE_LENGTH;
208         }
209 
210         return ret < Integer.MAX_VALUE ? (int) ret : Integer.MAX_VALUE;
211     }
212 
213     private static int toInt(byte value) {
214         return (value & 0xff) << 16;
215     }
216 
217     private static int toIntBE(short value) {
218         return (value & 0xff00) << 8 | (value & 0xff) << 8;
219     }
220 
221     private static int toIntLE(short value) {
222         return (value & 0xff) << 16 | (value & 0xff00);
223     }
224 
225     private static int toIntBE(int mediumValue) {
226         return (mediumValue & 0xff0000) | (mediumValue & 0xff00) | (mediumValue & 0xff);
227     }
228 
229     private static int toIntLE(int mediumValue) {
230         return (mediumValue & 0xff) << 16 | (mediumValue & 0xff00) | (mediumValue & 0xff0000) >>> 16;
231     }
232 
233     private static void encode3to4BigEndian(
234             int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
235         // Packing bytes into an int to reduce bound and reference count checking.
236         switch (numSigBytes) {
237             case 3:
238                 dest.setInt(destOffset, alphabet[inBuff >>> 18       ] << 24 |
239                                         alphabet[inBuff >>> 12 & 0x3f] << 16 |
240                                         alphabet[inBuff >>>  6 & 0x3f] << 8  |
241                                         alphabet[inBuff        & 0x3f]);
242                 break;
243             case 2:
244                 dest.setInt(destOffset, alphabet[inBuff >>> 18       ] << 24 |
245                                         alphabet[inBuff >>> 12 & 0x3f] << 16 |
246                                         alphabet[inBuff >>> 6  & 0x3f] << 8  |
247                                         EQUALS_SIGN);
248                 break;
249             case 1:
250                 dest.setInt(destOffset, alphabet[inBuff >>> 18       ] << 24 |
251                                         alphabet[inBuff >>> 12 & 0x3f] << 16 |
252                                         EQUALS_SIGN << 8                     |
253                                         EQUALS_SIGN);
254                 break;
255             default:
256                 // NOOP
257                 break;
258         }
259     }
260 
261     private static void encode3to4LittleEndian(
262             int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
263         // Packing bytes into an int to reduce bound and reference count checking.
264         switch (numSigBytes) {
265             case 3:
266                 dest.setInt(destOffset, alphabet[inBuff >>> 18       ]       |
267                                         alphabet[inBuff >>> 12 & 0x3f] << 8  |
268                                         alphabet[inBuff >>>  6 & 0x3f] << 16 |
269                                         alphabet[inBuff        & 0x3f] << 24);
270                 break;
271             case 2:
272                 dest.setInt(destOffset, alphabet[inBuff >>> 18       ]       |
273                                         alphabet[inBuff >>> 12 & 0x3f] << 8  |
274                                         alphabet[inBuff >>> 6  & 0x3f] << 16 |
275                                         EQUALS_SIGN << 24);
276                 break;
277             case 1:
278                 dest.setInt(destOffset, alphabet[inBuff >>> 18       ]      |
279                                         alphabet[inBuff >>> 12 & 0x3f] << 8 |
280                                         EQUALS_SIGN << 16                   |
281                                         EQUALS_SIGN << 24);
282                 break;
283             default:
284                 // NOOP
285                 break;
286         }
287     }
288 
289     public static ByteBuf decode(ByteBuf src) {
290         return decode(src, Base64Dialect.STANDARD);
291     }
292 
293     public static ByteBuf decode(ByteBuf src, Base64Dialect dialect) {
294         if (src == null) {
295             throw new NullPointerException("src");
296         }
297 
298         ByteBuf dest = decode(src, src.readerIndex(), src.readableBytes(), dialect);
299         src.readerIndex(src.writerIndex());
300         return dest;
301     }
302 
303     public static ByteBuf decode(
304             ByteBuf src, int off, int len) {
305         return decode(src, off, len, Base64Dialect.STANDARD);
306     }
307 
308     public static ByteBuf decode(
309             ByteBuf src, int off, int len, Base64Dialect dialect) {
310         return decode(src, off, len, dialect, src.alloc());
311     }
312 
313     public static ByteBuf decode(
314             ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator) {
315         if (src == null) {
316             throw new NullPointerException("src");
317         }
318         if (dialect == null) {
319             throw new NullPointerException("dialect");
320         }
321 
322         // Using a ByteProcessor to reduce bound and reference count checking.
323         return new Decoder().decode(src, off, len, allocator, dialect);
324     }
325 
326     // package-private for testing
327     static int decodedBufferSize(int len) {
328         return len - (len >>> 2);
329     }
330 
331     private static final class Decoder implements ByteBufProcessor {
332 
333         private final byte[] b4 = new byte[4];
334         private int b4Posn;
335         private byte sbiCrop;
336         private byte sbiDecode;
337         private byte[] decodabet;
338         private int outBuffPosn;
339         private ByteBuf dest;
340 
341         ByteBuf decode(ByteBuf src, int off, int len, ByteBufAllocator allocator, Base64Dialect dialect) {
342             dest = allocator.buffer(decodedBufferSize(len)).order(src.order()); // Upper limit on size of output
343 
344             decodabet = decodabet(dialect);
345             try {
346                 src.forEachByte(off, len, this);
347                 return dest.slice(0, outBuffPosn);
348             } catch (Throwable cause) {
349                 dest.release();
350                 PlatformDependent.throwException(cause);
351                 return null;
352             }
353         }
354 
355         @Override
356         public boolean process(byte value) throws Exception {
357             sbiCrop = (byte) (value & 0x7f); // Only the low seven bits
358             sbiDecode = decodabet[sbiCrop];
359 
360             if (sbiDecode >= WHITE_SPACE_ENC) { // White space, Equals sign or better
361                 if (sbiDecode >= EQUALS_SIGN_ENC) { // Equals sign or better
362                     b4[b4Posn ++] = sbiCrop;
363                     if (b4Posn > 3) { // Quartet built
364                         outBuffPosn += decode4to3(b4, dest, outBuffPosn, decodabet);
365                         b4Posn = 0;
366 
367                         // If that was the equals sign, break out of 'for' loop
368                         if (sbiCrop == EQUALS_SIGN) {
369                             return false;
370                         }
371                     }
372                 }
373                 return true;
374             }
375             throw new IllegalArgumentException(
376                     "invalid bad Base64 input character: " + (short) (value & 0xFF) + " (decimal)");
377         }
378 
379         private static int decode4to3(byte[] src, ByteBuf dest, int destOffset, byte[] decodabet) {
380             final byte src0 = src[0];
381             final byte src1 = src[1];
382             final byte src2 = src[2];
383             final int decodedValue;
384             if (src2 == EQUALS_SIGN) {
385                 // Example: Dk==
386                 try {
387                     decodedValue = (decodabet[src0] & 0xff) << 2 | (decodabet[src1] & 0xff) >>> 4;
388                 } catch (IndexOutOfBoundsException ignored) {
389                     throw new IllegalArgumentException("not encoded in Base64");
390                 }
391                 dest.setByte(destOffset, decodedValue);
392                 return 1;
393             }
394 
395             final byte src3 = src[3];
396             if (src3 == EQUALS_SIGN) {
397                 // Example: DkL=
398                 final byte b1 = decodabet[src1];
399                 // Packing bytes into a short to reduce bound and reference count checking.
400                 try {
401                     if (dest.order() == ByteOrder.BIG_ENDIAN) {
402                         // The decodabet bytes are meant to straddle byte boundaries and so we must carefully mask out
403                         // the bits we care about.
404                         decodedValue = ((decodabet[src0] & 0x3f) << 2 | (b1 & 0xf0) >> 4) << 8 |
405                                         (b1 & 0xf) << 4 | (decodabet[src2] & 0xfc) >>> 2;
406                     } else {
407                         // This is just a simple byte swap of the operation above.
408                         decodedValue = (decodabet[src0] & 0x3f) << 2 | (b1 & 0xf0) >> 4 |
409                                       ((b1 & 0xf) << 4 | (decodabet[src2] & 0xfc) >>> 2) << 8;
410                     }
411                 } catch (IndexOutOfBoundsException ignored) {
412                     throw new IllegalArgumentException("not encoded in Base64");
413                 }
414                 dest.setShort(destOffset, decodedValue);
415                 return 2;
416             }
417 
418             // Example: DkLE
419             try {
420                 if (dest.order() == ByteOrder.BIG_ENDIAN) {
421                     decodedValue = (decodabet[src0] & 0x3f) << 18 |
422                                    (decodabet[src1] & 0xff) << 12 |
423                                    (decodabet[src2] & 0xff) << 6 |
424                                     decodabet[src3] & 0xff;
425                 } else {
426                     final byte b1 = decodabet[src1];
427                     final byte b2 = decodabet[src2];
428                     // The goal is to byte swap the BIG_ENDIAN case above. There are 2 interesting things to consider:
429                     // 1. We are byte swapping a 3 byte data type. The left and the right byte switch, but the middle
430                     //    remains the same.
431                     // 2. The contents straddles byte boundaries. This means bytes will be pulled apart during the byte
432                     //    swapping process.
433                     decodedValue = (decodabet[src0] & 0x3f) << 2 |
434                                    // The bottom half of b1 remains in the middle.
435                                    (b1 & 0xf) << 12 |
436                                    // The top half of b1 are the least significant bits after the swap.
437                                    (b1 & 0xf0) >>> 4 |
438                                    // The bottom 2 bits of b2 will be the most significant bits after the swap.
439                                    (b2 & 0x3) << 22 |
440                                    // The remaining 6 bits of b2 remain in the middle.
441                                    (b2 & 0xfc) << 6 |
442                                    (decodabet[src3] & 0xff) << 16;
443                 }
444             } catch (IndexOutOfBoundsException ignored) {
445                 throw new IllegalArgumentException("not encoded in Base64");
446             }
447             dest.setMedium(destOffset, decodedValue);
448             return 3;
449         }
450     }
451 
452     private Base64() {
453         // Unused
454     }
455 }