1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module hunt.shiro.codec.Base64;
20 
21 import hunt.Exceptions;
22 
23 
24 /**
25  * Provides <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a> encoding and decoding as defined by
26  * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
27  * <p/>
28  * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
29  * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
30  * <p/>
31  * This class was borrowed from Apache Commons Codec SVN repository (rev. 618419) with modifications
32  * to enable Base64 conversion without a full dependency on Commons Codec.  We didn't want to reinvent the wheel of
33  * great work they've done, but also didn't want to force every Shiro user to depend on the commons-codec.jar
34  * <p/>
35  * As per the Apache 2.0 license, the original copyright notice and all author and copyright information have
36  * remained in tact.
37  *
38  * @see <a href="http://en.wikipedia.org/wiki/Base64">Wikipedia: Base 64</a>
39  * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
40  * @since 0.9
41  */
42 class Base64 {
43 
44     /**
45      * Chunk size per RFC 2045 section 6.8.
46      * <p/>
47      * The character limit does not count the trailing CRLF, but counts all other characters, including any
48      * equal signs.
49      *
50      * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
51      */
52     enum int CHUNK_SIZE = 76;
53 
54     /**
55      * Chunk separator per RFC 2045 section 2.1.
56      *
57      * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
58      */
59     enum byte[] CHUNK_SEPARATOR = cast(byte[])"\r\n".dup;
60 
61     /**
62      * The base length.
63      */
64     private enum int BASELENGTH = 255;
65 
66     /**
67      * Lookup length.
68      */
69     private enum int LOOKUPLENGTH = 64;
70 
71     /**
72      * Used to calculate the number of bits in a byte.
73      */
74     private enum int EIGHTBIT = 8;
75 
76     /**
77      * Used when encoding something which has fewer than 24 bits.
78      */
79     private enum int SIXTEENBIT = 16;
80 
81     /**
82      * Used to determine how many bits data contains.
83      */
84     private enum int TWENTYFOURBITGROUP = 24;
85 
86     /**
87      * Used to get the number of Quadruples.
88      */
89     private enum int FOURBYTE = 4;
90 
91     /**
92      * Used to test the sign of a byte.
93      */
94     private enum int SIGN = -128;
95 
96     /**
97      * Byte used to pad output.
98      */
99     private enum byte PAD = '=';
100 
101     // /**
102     //  * Contains the Base64 values <code>0</code> through <code>63</code> accessed by using character encodings as
103     //  * indices.
104     //  * <p/>
105     //  * <p>For example, <code>base64Alphabet['+']</code> returns <code>62</code>.</p>
106     //  * <p/>
107     //  * <p>The value of undefined encodings is <code>-1</code>.</p>
108     //  */
109     // private static final byte[] base64Alphabet = new byte[BASELENGTH];
110 
111     // /**
112     //  * <p>Contains the Base64 encodings <code>A</code> through <code>Z</code>, followed by <code>a</code> through
113     //  * <code>z</code>, followed by <code>0</code> through <code>9</code>, followed by <code>+</code>, and
114     //  * <code>/</code>.</p>
115     //  * <p/>
116     //  * <p>This array is accessed by using character values as indices.</p>
117     //  * <p/>
118     //  * <p>For example, <code>lookUpBase64Alphabet[62] </code> returns <code>'+'</code>.</p>
119     //  */
120     // private static final byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
121 
122     // // Populating the lookup and character arrays
123 
124     // static {
125     //     for (int i = 0; i < BASELENGTH; i++) {
126     //         base64Alphabet[i] = (byte) -1;
127     //     }
128     //     for (int i = 'Z'; i >= 'A'; i--) {
129     //         base64Alphabet[i] = (byte) (i - 'A');
130     //     }
131     //     for (int i = 'z'; i >= 'a'; i--) {
132     //         base64Alphabet[i] = (byte) (i - 'a' + 26);
133     //     }
134     //     for (int i = '9'; i >= '0'; i--) {
135     //         base64Alphabet[i] = (byte) (i - '0' + 52);
136     //     }
137 
138     //     base64Alphabet['+'] = 62;
139     //     base64Alphabet['/'] = 63;
140 
141     //     for (int i = 0; i <= 25; i++) {
142     //         lookUpBase64Alphabet[i] = (byte) ('A' + i);
143     //     }
144 
145     //     for (int i = 26, j = 0; i <= 51; i++, j++) {
146     //         lookUpBase64Alphabet[i] = (byte) ('a' + j);
147     //     }
148 
149     //     for (int i = 52, j = 0; i <= 61; i++, j++) {
150     //         lookUpBase64Alphabet[i] = (byte) ('0' + j);
151     //     }
152 
153     //     lookUpBase64Alphabet[62] = (byte) '+';
154     //     lookUpBase64Alphabet[63] = (byte) '/';
155     // }
156 
157     /**
158      * Returns whether or not the <code>octet</code> is in the base 64 alphabet.
159      *
160      * @param octect The value to test
161      * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
162      */
163     // private static bool isBase64(byte octect) {
164     //     if (octect == PAD) {
165     //         return true;
166     //     } else //noinspection RedundantIfStatement
167     //         if (octect < 0 || base64Alphabet[octect] == -1) {
168     //             return false;
169     //         } else {
170     //             return true;
171     //         }
172     // }
173 
174     // /**
175     //  * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
176     //  *
177     //  * @param arrayOctect byte array to test
178     //  * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is
179     //  *         empty; false, otherwise
180     //  */
181     // static bool isBase64(byte[] arrayOctect) {
182 
183     //     arrayOctect = discardWhitespace(arrayOctect);
184 
185     //     int length = arrayOctect.length;
186     //     if (length == 0) {
187     //         // shouldn't a 0 length array be valid base64 data?
188     //         // return false;
189     //         return true;
190     //     }
191     //     for (int i = 0; i < length; i++) {
192     //         if (!isBase64(arrayOctect[i])) {
193     //             return false;
194     //         }
195     //     }
196     //     return true;
197     // }
198 
199     // /**
200     //  * Discards any whitespace from a base-64 encoded block.
201     //  *
202     //  * @param data The base-64 encoded data to discard the whitespace from.
203     //  * @return The data, less whitespace (see RFC 2045).
204     //  */
205     // static byte[] discardWhitespace(byte[] data) {
206     //     byte groomedData[] = new byte[data.length];
207     //     int bytesCopied = 0;
208 
209     //     for (byte aByte : data) {
210     //         switch (aByte) {
211     //             case (byte) ' ':
212     //             case (byte) '\n':
213     //             case (byte) '\r':
214     //             case (byte) '\t':
215     //                 break;
216     //             default:
217     //                 groomedData[bytesCopied++] = aByte;
218     //         }
219     //     }
220 
221     //     byte packedData[] = new byte[bytesCopied];
222 
223     //     System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
224 
225     //     return packedData;
226     // }
227 
228     /**
229      * Base64 encodes the specified byte array and then encodes it as a string using Shiro's preferred character
230      * encoding (UTF-8).
231      *
232      * @param bytes the byte array to Base64 encode.
233      * @return a UTF-8 encoded string of the resulting Base64 encoded byte array.
234      */
235     static string encodeToString(byte[] bytes) {
236         byte[] encoded = encode(bytes);
237         return cast(string)encoded;
238     }
239 
240     /**
241      * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
242      *
243      * @param binaryData binary data to encodeToChars
244      * @return Base64 characters chunked in 76 character blocks
245      */
246     static byte[] encodeChunked(byte[] binaryData) {
247         return encode(binaryData, true);
248     }
249 
250     /**
251      * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet.
252      *
253      * @param pArray a byte array containing binary data
254      * @return A byte array containing only Base64 character data
255      */
256     static byte[] encode(byte[] pArray) {
257         return encode(pArray, false);
258     }
259 
260     /**
261      * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
262      *
263      * @param binaryData Array containing binary data to encodeToChars.
264      * @param isChunked  if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
265      * @return Base64-encoded data.
266      * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
267      */
268     static byte[] encode(byte[] binaryData, bool isChunked) {
269         long binaryDataLength = binaryData.length;
270         long lengthDataBits = binaryDataLength * EIGHTBIT;
271         long fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
272         long tripletCount = lengthDataBits / TWENTYFOURBITGROUP;
273         long encodedDataLengthLong;
274         int chunckCount = 0;
275 
276         implementationMissing(false);
277         return null;
278 
279     //     if (fewerThan24bits != 0) {
280     //         // data not divisible by 24 bit
281     //         encodedDataLengthLong = (tripletCount + 1) * 4;
282     //     } else {
283     //         // 16 or 8 bit
284     //         encodedDataLengthLong = tripletCount * 4;
285     //     }
286 
287     //     // If the output is to be "chunked" into 76 character sections,
288     //     // for compliance with RFC 2045 MIME, then it is important to
289     //     // allow for extra length to account for the separator(s)
290     //     if (isChunked) {
291 
292     //         chunckCount = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math
293     //                 .ceil((float) encodedDataLengthLong / CHUNK_SIZE));
294     //         encodedDataLengthLong += chunckCount * CHUNK_SEPARATOR.length;
295     //     }
296 
297     //     if (encodedDataLengthLong > Integer.MAX_VALUE) {
298     //         throw new IllegalArgumentException(
299     //                 "Input array too big, output array would be bigger than Integer.MAX_VALUE=" ~ Integer.MAX_VALUE);
300     //     }
301     //     int encodedDataLength = (int) encodedDataLengthLong;
302     //     byte encodedData[] = new byte[encodedDataLength];
303 
304     //     byte k, l, b1, b2, b3;
305 
306     //     int encodedIndex = 0;
307     //     int dataIndex;
308     //     int i;
309     //     int nextSeparatorIndex = CHUNK_SIZE;
310     //     int chunksSoFar = 0;
311 
312     //     // log.debug("number of triplets = " ~ numberTriplets);
313     //     for (i = 0; i < tripletCount; i++) {
314     //         dataIndex = i * 3;
315     //         b1 = binaryData[dataIndex];
316     //         b2 = binaryData[dataIndex + 1];
317     //         b3 = binaryData[dataIndex + 2];
318 
319     //         // log.debug("b1= " ~ b1 +", b2= " ~ b2 ~ ", b3= " ~ b3);
320 
321     //         l = (byte) (b2 & 0x0f);
322     //         k = (byte) (b1 & 0x03);
323 
324     //         byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
325     //         byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
326     //         byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
327 
328     //         encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
329     //         // log.debug( "val2 = " ~ val2 );
330     //         // log.debug( "k4 = " ~ (k<<4) );
331     //         // log.debug( "vak = " ~ (val2 | (k<<4)) );
332     //         encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
333     //         encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3];
334     //         encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
335 
336     //         encodedIndex += 4;
337 
338     //         // If we are chunking, let's put a chunk separator down.
339     //         if (isChunked) {
340     //             // this assumes that CHUNK_SIZE % 4 == 0
341     //             if (encodedIndex == nextSeparatorIndex) {
342     //                 System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length);
343     //                 chunksSoFar++;
344     //                 nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length);
345     //                 encodedIndex += CHUNK_SEPARATOR.length;
346     //             }
347     //         }
348     //     }
349 
350     //     // form integral number of 6-bit groups
351     //     dataIndex = i * 3;
352 
353     //     if (fewerThan24bits == EIGHTBIT) {
354     //         b1 = binaryData[dataIndex];
355     //         k = (byte) (b1 & 0x03);
356     //         // log.debug("b1=" ~ b1);
357     //         // log.debug("b1<<2 = " ~ (b1>>2) );
358     //         byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
359     //         encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
360     //         encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
361     //         encodedData[encodedIndex + 2] = PAD;
362     //         encodedData[encodedIndex + 3] = PAD;
363     //     } else if (fewerThan24bits == SIXTEENBIT) {
364 
365     //         b1 = binaryData[dataIndex];
366     //         b2 = binaryData[dataIndex + 1];
367     //         l = (byte) (b2 & 0x0f);
368     //         k = (byte) (b1 & 0x03);
369 
370     //         byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
371     //         byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
372 
373     //         encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
374     //         encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
375     //         encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
376     //         encodedData[encodedIndex + 3] = PAD;
377     //     }
378 
379     //     if (isChunked) {
380     //         // we also add a separator to the end of the final chunk.
381     //         if (chunksSoFar < chunckCount) {
382     //             System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length,
383     //                     CHUNK_SEPARATOR.length);
384     //         }
385     //     }
386 
387     //     return encodedData;
388     }
389 
390     // /**
391     //  * Converts the specified UTF-8 Base64 encoded string and decodes it to a resultant UTF-8 encoded string.
392     //  *
393     //  * @param base64Encoded a UTF-8 Base64 encoded string
394     //  * @return the decoded string, UTF-8 encoded.
395     //  */
396     // static string decodeToString(string base64Encoded) {
397     //     byte[] encodedBytes = CodecSupport.toBytes(base64Encoded);
398     //     return decodeToString(encodedBytes);
399     // }
400 
401     // /**
402     //  * Decodes the specified Base64 encoded byte array and returns the decoded result as a UTF-8 encoded.
403     //  *
404     //  * @param base64Encoded a Base64 encoded byte array
405     //  * @return the decoded string, UTF-8 encoded.
406     //  */
407     // static string decodeToString(byte[] base64Encoded) {
408     //     byte[] decoded = decode(base64Encoded);
409     //     return CodecSupport.toString(decoded);
410     // }
411 
412     /**
413      * Converts the specified UTF-8 Base64 encoded string and decodes it to a raw Base64 decoded byte array.
414      *
415      * @param base64Encoded a UTF-8 Base64 encoded string
416      * @return the raw Base64 decoded byte array.
417      */
418     static byte[] decode(string base64Encoded) {
419         return decode(cast(byte[])base64Encoded);
420     }
421 
422     /**
423      * Decodes Base64 data into octets
424      *
425      * @param base64Data Byte array containing Base64 data
426      * @return Array containing decoded data.
427      */
428     static byte[] decode(byte[] base64Data) {
429         implementationMissing(false);
430         return null;
431         // // RFC 2045 requires that we discard ALL non-Base64 characters
432         // base64Data = discardNonBase64(base64Data);
433 
434         // // handle the edge case, so we don't have to worry about it later
435         // if (base64Data.length == 0) {
436         //     return new byte[0];
437         // }
438 
439         // int numberQuadruple = base64Data.length / FOURBYTE;
440         // byte decodedData[];
441         // byte b1, b2, b3, b4, marker0, marker1;
442 
443         // // Throw away anything not in base64Data
444 
445         // int encodedIndex = 0;
446         // int dataIndex;
447         // {
448         //     // this sizes the output array properly - rlw
449         //     int lastData = base64Data.length;
450         //     // ignore the '=' padding
451         //     while (base64Data[lastData - 1] == PAD) {
452         //         if (--lastData == 0) {
453         //             return new byte[0];
454         //         }
455         //     }
456         //     decodedData = new byte[lastData - numberQuadruple];
457         // }
458 
459         // for (int i = 0; i < numberQuadruple; i++) {
460         //     dataIndex = i * 4;
461         //     marker0 = base64Data[dataIndex + 2];
462         //     marker1 = base64Data[dataIndex + 3];
463 
464         //     b1 = base64Alphabet[base64Data[dataIndex]];
465         //     b2 = base64Alphabet[base64Data[dataIndex + 1]];
466 
467         //     if (marker0 != PAD && marker1 != PAD) {
468         //         // No PAD e.g 3cQl
469         //         b3 = base64Alphabet[marker0];
470         //         b4 = base64Alphabet[marker1];
471 
472         //         decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
473         //         decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
474         //         decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
475         //     } else if (marker0 == PAD) {
476         //         // Two PAD e.g. 3c[Pad][Pad]
477         //         decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
478         //     } else {
479         //         // One PAD e.g. 3cQ[Pad]
480         //         b3 = base64Alphabet[marker0];
481         //         decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
482         //         decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
483         //     }
484         //     encodedIndex += 3;
485         // }
486         // return decodedData;
487     }
488 
489     // /**
490     //  * Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any
491     //  * characters outside of the base64 alphabet are to be ignored in base64 encoded data."
492     //  *
493     //  * @param data The base-64 encoded data to groom
494     //  * @return The data, less non-base64 characters (see RFC 2045).
495     //  */
496     // static byte[] discardNonBase64(byte[] data) {
497     //     byte groomedData[] = new byte[data.length];
498     //     int bytesCopied = 0;
499 
500     //     for (byte aByte : data) {
501     //         if (isBase64(aByte)) {
502     //             groomedData[bytesCopied++] = aByte;
503     //         }
504     //     }
505 
506     //     byte packedData[] = new byte[bytesCopied];
507 
508     //     System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
509 
510     //     return packedData;
511     // }
512 
513 }