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