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.Unpooled;
24  
25  /**
26   * Utility class for {@link ByteBuf} that encodes and decodes to and from
27   * <a href="http://en.wikipedia.org/wiki/Base64">Base64</a> notation.
28   * <p>
29   * The encoding and decoding algorithm in this class has been derived from
30   * <a href="http://iharder.sourceforge.net/current/java/base64/">Robert Harder's Public Domain
31   * Base64 Encoder/Decoder</a>.
32   */
33  public final class Base64 {
34  
35      /** Maximum line length (76) of Base64 output. */
36      private static final int MAX_LINE_LENGTH = 76;
37  
38      /** The equals sign (=) as a byte. */
39      private static final byte EQUALS_SIGN = (byte) '=';
40  
41      /** The new line character (\n) as a byte. */
42      private static final byte NEW_LINE = (byte) '\n';
43  
44      private static final byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
45  
46      private static final byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
47  
48      private static byte[] alphabet(Base64Dialect dialect) {
49          if (dialect == null) {
50              throw new NullPointerException("dialect");
51          }
52          return dialect.alphabet;
53      }
54  
55      private static byte[] decodabet(Base64Dialect dialect) {
56          if (dialect == null) {
57              throw new NullPointerException("dialect");
58          }
59          return dialect.decodabet;
60      }
61  
62      private static boolean breakLines(Base64Dialect dialect) {
63          if (dialect == null) {
64              throw new NullPointerException("dialect");
65          }
66          return dialect.breakLinesByDefault;
67      }
68  
69      public static ByteBuf encode(ByteBuf src) {
70          return encode(src, Base64Dialect.STANDARD);
71      }
72  
73      public static ByteBuf encode(ByteBuf src, Base64Dialect dialect) {
74          return encode(src, breakLines(dialect), dialect);
75      }
76  
77      public static ByteBuf encode(ByteBuf src, boolean breakLines) {
78          return encode(src, breakLines, Base64Dialect.STANDARD);
79      }
80  
81      public static ByteBuf encode(ByteBuf src, boolean breakLines, Base64Dialect dialect) {
82  
83          if (src == null) {
84              throw new NullPointerException("src");
85          }
86  
87          ByteBuf dest = encode(src, src.readerIndex(), src.readableBytes(), breakLines, dialect);
88          src.readerIndex(src.writerIndex());
89          return dest;
90      }
91  
92      public static ByteBuf encode(ByteBuf src, int off, int len) {
93          return encode(src, off, len, Base64Dialect.STANDARD);
94      }
95  
96      public static ByteBuf encode(ByteBuf src, int off, int len, Base64Dialect dialect) {
97          return encode(src, off, len, breakLines(dialect), dialect);
98      }
99  
100     public static ByteBuf encode(
101             ByteBuf src, int off, int len, boolean breakLines) {
102         return encode(src, off, len, breakLines, Base64Dialect.STANDARD);
103     }
104 
105     public static ByteBuf encode(
106             ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect) {
107 
108         if (src == null) {
109             throw new NullPointerException("src");
110         }
111         if (dialect == null) {
112             throw new NullPointerException("dialect");
113         }
114 
115         int len43 = len * 4 / 3;
116         ByteBuf dest = Unpooled.buffer(
117                 len43 +
118                         (len % 3 > 0 ? 4 : 0) + // Account for padding
119                         (breakLines ? len43 / MAX_LINE_LENGTH : 0)).order(src.order()); // New lines
120         int d = 0;
121         int e = 0;
122         int len2 = len - 2;
123         int lineLength = 0;
124         for (; d < len2; d += 3, e += 4) {
125             encode3to4(src, d + off, 3, dest, e, dialect);
126 
127             lineLength += 4;
128             if (breakLines && lineLength == MAX_LINE_LENGTH) {
129                 dest.setByte(e + 4, NEW_LINE);
130                 e ++;
131                 lineLength = 0;
132             } // end if: end of line
133         } // end for: each piece of array
134 
135         if (d < len) {
136             encode3to4(src, d + off, len - d, dest, e, dialect);
137             e += 4;
138         } // end if: some padding needed
139 
140         return dest.slice(0, e);
141     }
142 
143     private static void encode3to4(
144             ByteBuf src, int srcOffset, int numSigBytes,
145             ByteBuf dest, int destOffset, Base64Dialect dialect) {
146 
147         byte[] ALPHABET = alphabet(dialect);
148 
149         //           1         2         3
150         // 01234567890123456789012345678901 Bit position
151         // --------000000001111111122222222 Array position from threeBytes
152         // --------|    ||    ||    ||    | Six bit groups to index ALPHABET
153         //          >>18  >>12  >> 6  >> 0  Right shift necessary
154         //                0x3f  0x3f  0x3f  Additional AND
155 
156         // Create buffer with zero-padding if there are only one or two
157         // significant bytes passed in the array.
158         // We have to shift left 24 in order to flush out the 1's that appear
159         // when Java treats a value as negative that is cast from a byte to an int.
160         int inBuff =
161                 (numSigBytes > 0? src.getByte(srcOffset)     << 24 >>>  8 : 0) |
162                 (numSigBytes > 1? src.getByte(srcOffset + 1) << 24 >>> 16 : 0) |
163                 (numSigBytes > 2? src.getByte(srcOffset + 2) << 24 >>> 24 : 0);
164 
165         switch (numSigBytes) {
166         case 3:
167             dest.setByte(destOffset    , ALPHABET[inBuff >>> 18       ]);
168             dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
169             dest.setByte(destOffset + 2, ALPHABET[inBuff >>>  6 & 0x3f]);
170             dest.setByte(destOffset + 3, ALPHABET[inBuff        & 0x3f]);
171             break;
172         case 2:
173             dest.setByte(destOffset    , ALPHABET[inBuff >>> 18       ]);
174             dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
175             dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6  & 0x3f]);
176             dest.setByte(destOffset + 3, EQUALS_SIGN);
177             break;
178         case 1:
179             dest.setByte(destOffset    , ALPHABET[inBuff >>> 18       ]);
180             dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
181             dest.setByte(destOffset + 2, EQUALS_SIGN);
182             dest.setByte(destOffset + 3, EQUALS_SIGN);
183             break;
184         }
185     }
186 
187     public static ByteBuf decode(ByteBuf src) {
188         return decode(src, Base64Dialect.STANDARD);
189     }
190 
191     public static ByteBuf decode(ByteBuf src, Base64Dialect dialect) {
192 
193         if (src == null) {
194             throw new NullPointerException("src");
195         }
196 
197         ByteBuf dest = decode(src, src.readerIndex(), src.readableBytes(), dialect);
198         src.readerIndex(src.writerIndex());
199         return dest;
200     }
201 
202     public static ByteBuf decode(
203             ByteBuf src, int off, int len) {
204         return decode(src, off, len, Base64Dialect.STANDARD);
205     }
206 
207     public static ByteBuf decode(
208             ByteBuf src, int off, int len, Base64Dialect dialect) {
209 
210         if (src == null) {
211             throw new NullPointerException("src");
212         }
213         if (dialect == null) {
214             throw new NullPointerException("dialect");
215         }
216 
217         byte[] DECODABET = decodabet(dialect);
218 
219         int len34 = len * 3 / 4;
220         ByteBuf dest = src.alloc().buffer(len34).order(src.order()); // Upper limit on size of output
221         int outBuffPosn = 0;
222 
223         byte[] b4 = new byte[4];
224         int b4Posn = 0;
225         int i;
226         byte sbiCrop;
227         byte sbiDecode;
228         for (i = off; i < off + len; i ++) {
229             sbiCrop = (byte) (src.getByte(i) & 0x7f); // Only the low seven bits
230             sbiDecode = DECODABET[sbiCrop];
231 
232             if (sbiDecode >= WHITE_SPACE_ENC) { // White space, Equals sign or better
233                 if (sbiDecode >= EQUALS_SIGN_ENC) { // Equals sign or better
234                     b4[b4Posn ++] = sbiCrop;
235                     if (b4Posn > 3) { // Quartet built
236                         outBuffPosn += decode4to3(
237                                 b4, 0, dest, outBuffPosn, dialect);
238                         b4Posn = 0;
239 
240                         // If that was the equals sign, break out of 'for' loop
241                         if (sbiCrop == EQUALS_SIGN) {
242                             break;
243                         }
244                     }
245                 }
246             } else {
247                 throw new IllegalArgumentException(
248                         "bad Base64 input character at " + i + ": " +
249                         src.getUnsignedByte(i) + " (decimal)");
250             }
251         }
252 
253         return dest.slice(0, outBuffPosn);
254     }
255 
256     private static int decode4to3(
257             byte[] src, int srcOffset,
258             ByteBuf dest, int destOffset, Base64Dialect dialect) {
259 
260         byte[] DECODABET = decodabet(dialect);
261 
262         if (src[srcOffset + 2] == EQUALS_SIGN) {
263             // Example: Dk==
264             int outBuff =
265                     (DECODABET[src[srcOffset    ]] & 0xFF) << 18 |
266                     (DECODABET[src[srcOffset + 1]] & 0xFF) << 12;
267 
268             dest.setByte(destOffset, (byte) (outBuff >>> 16));
269             return 1;
270         } else if (src[srcOffset + 3] == EQUALS_SIGN) {
271             // Example: DkL=
272             int outBuff =
273                     (DECODABET[src[srcOffset    ]] & 0xFF) << 18 |
274                     (DECODABET[src[srcOffset + 1]] & 0xFF) << 12 |
275                     (DECODABET[src[srcOffset + 2]] & 0xFF) <<  6;
276 
277             dest.setByte(destOffset    , (byte) (outBuff >>> 16));
278             dest.setByte(destOffset + 1, (byte) (outBuff >>>  8));
279             return 2;
280         } else {
281             // Example: DkLE
282             int outBuff;
283             try {
284                 outBuff =
285                         (DECODABET[src[srcOffset    ]] & 0xFF) << 18 |
286                         (DECODABET[src[srcOffset + 1]] & 0xFF) << 12 |
287                         (DECODABET[src[srcOffset + 2]] & 0xFF) <<  6 |
288                          DECODABET[src[srcOffset + 3]] & 0xFF;
289             } catch (IndexOutOfBoundsException ignored) {
290                 throw new IllegalArgumentException("not encoded in Base64");
291             }
292 
293             dest.setByte(destOffset    , (byte) (outBuff >> 16));
294             dest.setByte(destOffset + 1, (byte) (outBuff >>  8));
295             dest.setByte(destOffset + 2, (byte)  outBuff);
296             return 3;
297         }
298     }
299 
300     private Base64() {
301         // Unused
302     }
303 }