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