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