1 // Written in the D programming language.
2 
3 /**
4  * Support for Base64 encoding and decoding.
5  *
6  * This module provides two default implementations of Base64 encoding,
7  * $(LREF Base64) with a standard encoding alphabet, and a variant
8  * $(LREF Base64URL) that has a modified encoding alphabet designed to be
9  * safe for embedding in URLs and filenames.
10  *
11  * Both variants are implemented as instantiations of the template
12  * $(LREF Base64Impl). Most users will not need to use this template
13  * directly; however, it can be used to create customized Base64 encodings,
14  * such as one that omits padding characters, or one that is safe to embed
15  * inside a regular expression.
16  *
17  * Example:
18  * -----
19  * ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e];
20  *
21  * const(char)[] encoded = Base64.encode(data);
22  * assert(encoded == "FPucA9l+");
23  *
24  * ubyte[] decoded = Base64.decode("FPucA9l+");
25  * assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
26  * -----
27  *
28  * The range API is supported for both encoding and decoding:
29  *
30  * Example:
31  * -----
32  * // Create MIME Base64 with CRLF, per line 76.
33  * File f = File("./text.txt", "r");
34  * scope(exit) f.close();
35  *
36  * Appender!string mime64 = appender!string;
37  *
38  * foreach (encoded; Base64.encoder(f.byChunk(57)))
39  * {
40  *     mime64.put(encoded);
41  *     mime64.put("\r\n");
42  * }
43  *
44  * writeln(mime64.data);
45  * -----
46  *
47  * References:
48  * $(LINK2 https://tools.ietf.org/html/rfc4648, RFC 4648 - The Base16, Base32, and Base64
49  * Data Encodings)
50  *
51  * Copyright: Masahiro Nakagawa 2010-.
52  * License:   $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
53  * Authors:   Masahiro Nakagawa, Daniel Murphy (Single value Encoder and Decoder)
54  * Source:    $(PHOBOSSRC std/_base64.d)
55  * Macros:
56  *      LREF2=<a href="#$1">$(D $2)</a>
57  */
58 module std.base64;
59 
60 import std.exception;  // enforce
61 import std.range.primitives;      // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength
62 import std.traits;     // isArray
63 
64 // Make sure module header code examples work correctly.
65 @safe unittest
66 {
67     ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e];
68 
69     const(char)[] encoded = Base64.encode(data);
70     assert(encoded == "FPucA9l+");
71 
72     ubyte[] decoded = Base64.decode("FPucA9l+");
73     assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
74 }
75 
76 /**
77  * Implementation of standard _Base64 encoding.
78  *
79  * See $(LREF Base64Impl) for a description of available methods.
80  */
81 alias Base64 = Base64Impl!('+', '/');
82 
83 ///
84 @safe unittest
85 {
86     ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
87     assert(Base64.encode(data) == "g9cwegE/");
88     assert(Base64.decode("g9cwegE/") == data);
89 }
90 
91 
92 /**
93  * Variation of Base64 encoding that is safe for use in URLs and filenames.
94  *
95  * See $(LREF Base64Impl) for a description of available methods.
96  */
97 alias Base64URL = Base64Impl!('-', '_');
98 
99 ///
100 @safe unittest
101 {
102     ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
103     assert(Base64URL.encode(data) == "g9cwegE_");
104     assert(Base64URL.decode("g9cwegE_") == data);
105 }
106 
107 /**
108  * Unpadded variation of Base64 encoding that is safe for use in URLs and
109  * filenames, as used in RFCs 4648 and 7515 (JWS/JWT/JWE).
110  *
111  * See $(LREF Base64Impl) for a description of available methods.
112  */
113 alias Base64URLNoPadding = Base64Impl!('-', '_', Base64.NoPadding);
114 
115 ///
116 @safe unittest
117 {
118     ubyte[] data = [0x83, 0xd7, 0x30, 0x7b, 0xef];
119     assert(Base64URLNoPadding.encode(data) == "g9cwe-8");
120     assert(Base64URLNoPadding.decode("g9cwe-8") == data);
121 }
122 
123 /**
124  * Template for implementing Base64 encoding and decoding.
125  *
126  * For most purposes, direct usage of this template is not necessary; instead,
127  * this module provides default implementations: $(LREF Base64), implementing
128  * basic Base64 encoding, and $(LREF Base64URL) and $(LREF Base64URLNoPadding),
129  * that implement the Base64 variant for use in URLs and filenames, with
130  * and without padding, respectively.
131  *
132  * Customized Base64 encoding schemes can be implemented by instantiating this
133  * template with the appropriate arguments. For example:
134  *
135  * -----
136  * // Non-standard Base64 format for embedding in regular expressions.
137  * alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding);
138  * -----
139  *
140  * NOTE:
141  * Encoded strings will not have any padding if the $(D Padding) parameter is
142  * set to $(D NoPadding).
143  */
144 template Base64Impl(char Map62th, char Map63th, char Padding = '=')
145 {
146     enum NoPadding = '\0';  /// represents no-padding encoding
147 
148 
149     // Verify Base64 characters
150     static assert(Map62th < 'A' || Map62th > 'Z', "Character '" ~ Map62th ~ "' cannot be used twice");
151     static assert(Map63th < 'A' || Map63th > 'Z', "Character '" ~ Map63th ~ "' cannot be used twice");
152     static assert(Padding < 'A' || Padding > 'Z', "Character '" ~ Padding ~ "' cannot be used twice");
153     static assert(Map62th < 'a' || Map62th > 'z', "Character '" ~ Map62th ~ "' cannot be used twice");
154     static assert(Map63th < 'a' || Map63th > 'z', "Character '" ~ Map63th ~ "' cannot be used twice");
155     static assert(Padding < 'a' || Padding > 'z', "Character '" ~ Padding ~ "' cannot be used twice");
156     static assert(Map62th < '0' || Map62th > '9', "Character '" ~ Map62th ~ "' cannot be used twice");
157     static assert(Map63th < '0' || Map63th > '9', "Character '" ~ Map63th ~ "' cannot be used twice");
158     static assert(Padding < '0' || Padding > '9', "Character '" ~ Padding ~ "' cannot be used twice");
159     static assert(Map62th != Map63th, "Character '" ~ Map63th ~ "' cannot be used twice");
160     static assert(Map62th != Padding, "Character '" ~ Padding ~ "' cannot be used twice");
161     static assert(Map63th != Padding, "Character '" ~ Padding ~ "' cannot be used twice");
162     static assert(Map62th != NoPadding, "'\\0' is not a valid Base64character");
163     static assert(Map63th != NoPadding, "'\\0' is not a valid Base64character");
164 
165 
166     /* Encode functions */
167 
168 
169     private immutable EncodeMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ~ Map62th ~ Map63th;
170 
171 
172     /**
173      * Calculates the length needed to store the encoded string corresponding
174      * to an input of the given length.
175      *
176      * Params:
177      *  sourceLength = Length of the source array.
178      *
179      * Returns:
180      *  The length of a Base64 encoding of an array of the given length.
181      */
182     @safe
encodeLength(in size_t sourceLength)183     pure nothrow size_t encodeLength(in size_t sourceLength)
184     {
185         static if (Padding == NoPadding)
186             return (sourceLength / 3) * 4 + (sourceLength % 3 == 0 ? 0 : sourceLength % 3 == 1 ? 2 : 3);
187         else
188             return (sourceLength / 3 + (sourceLength % 3 ? 1 : 0)) * 4;
189     }
190 
191     ///
192     @safe unittest
193     {
194         ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
195 
196         // Allocate a buffer large enough to hold the encoded string.
197         auto buf = new char[Base64.encodeLength(data.length)];
198 
199         Base64.encode(data, buf);
200         assert(buf == "Gis8TV1u");
201     }
202 
203 
204     // ubyte[] to char[]
205 
206 
207     /**
208      * Encode $(D_PARAM source) into a $(D char[]) buffer using Base64
209      * encoding.
210      *
211      * Params:
212      *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
213      *           range) to _encode.
214      *  buffer = The $(D char[]) buffer to store the encoded result.
215      *
216      * Returns:
217      *  The slice of $(D_PARAM buffer) that contains the encoded string.
218      */
219     @trusted
220     pure char[] encode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : ubyte) &&
221                                                             is(R2 == char[]))
222     in
223     {
224         assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding");
225     }
out(result)226     out(result)
227     {
228         assert(result.length == encodeLength(source.length), "The length of result is different from Base64");
229     }
230     body
231     {
232         immutable srcLen = source.length;
233         if (srcLen == 0)
234             return [];
235 
236         immutable blocks = srcLen / 3;
237         immutable remain = srcLen % 3;
238         auto      bufptr = buffer.ptr;
239         auto      srcptr = source.ptr;
240 
241         foreach (Unused; 0 .. blocks)
242         {
243             immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2];
244             *bufptr++ = EncodeMap[val >> 18       ];
245             *bufptr++ = EncodeMap[val >> 12 & 0x3f];
246             *bufptr++ = EncodeMap[val >>  6 & 0x3f];
247             *bufptr++ = EncodeMap[val       & 0x3f];
248             srcptr += 3;
249         }
250 
251         if (remain)
252         {
253             immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0);
254             *bufptr++ = EncodeMap[val >> 18       ];
255             *bufptr++ = EncodeMap[val >> 12 & 0x3f];
256 
257             final switch (remain)
258             {
259             case 2:
260                 *bufptr++ = EncodeMap[val >> 6 & 0x3f];
261                 static if (Padding != NoPadding)
262                     *bufptr++ = Padding;
263                 break;
264             case 1:
265                 static if (Padding != NoPadding)
266                 {
267                     *bufptr++ = Padding;
268                     *bufptr++ = Padding;
269                 }
270                 break;
271             }
272         }
273 
274         // encode method can't assume buffer length. So, slice needed.
275         return buffer[0 .. bufptr - buffer.ptr];
276     }
277 
278     ///
279     @safe unittest
280     {
281         ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
282         char[32] buffer;    // much bigger than necessary
283 
284         // Just to be sure...
285         auto encodedLength = Base64.encodeLength(data.length);
286         assert(buffer.length >= encodedLength);
287 
288         // encode() returns a slice to the provided buffer.
289         auto encoded = Base64.encode(data, buffer[]);
290         assert(encoded is buffer[0 .. encodedLength]);
291         assert(encoded == "g9cwegE/");
292     }
293 
294 
295     // InputRange to char[]
296 
297 
298     /**
299      * ditto
300      */
301     char[] encode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 &&
302                                                     is(ElementType!R1 : ubyte) && hasLength!R1 &&
303                                                     is(R2 == char[]))
304     in
305     {
306         assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding");
307     }
out(result)308     out(result)
309     {
310         // @@@BUG@@@ D's DbC can't caputre an argument of function and store the result of precondition.
311         //assert(result.length == encodeLength(source.length), "The length of result is different from Base64");
312     }
313     body
314     {
315         immutable srcLen = source.length;
316         if (srcLen == 0)
317             return [];
318 
319         immutable blocks = srcLen / 3;
320         immutable remain = srcLen % 3;
321         auto      bufptr = buffer.ptr;
322 
323         foreach (Unused; 0 .. blocks)
324         {
325             immutable v1 = source.front; source.popFront();
326             immutable v2 = source.front; source.popFront();
327             immutable v3 = source.front; source.popFront();
328             immutable val = v1 << 16 | v2 << 8 | v3;
329             *bufptr++ = EncodeMap[val >> 18       ];
330             *bufptr++ = EncodeMap[val >> 12 & 0x3f];
331             *bufptr++ = EncodeMap[val >>  6 & 0x3f];
332             *bufptr++ = EncodeMap[val       & 0x3f];
333         }
334 
335         if (remain)
336         {
337             size_t val = source.front << 16;
338             if (remain == 2)
339             {
340                 source.popFront();
341                 val |= source.front << 8;
342             }
343 
344             *bufptr++ = EncodeMap[val >> 18       ];
345             *bufptr++ = EncodeMap[val >> 12 & 0x3f];
346 
347             final switch (remain)
348             {
349             case 2:
350                 *bufptr++ = EncodeMap[val >> 6 & 0x3f];
351                 static if (Padding != NoPadding)
352                     *bufptr++ = Padding;
353                 break;
354             case 1:
355                 static if (Padding != NoPadding)
356                 {
357                     *bufptr++ = Padding;
358                     *bufptr++ = Padding;
359                 }
360                 break;
361             }
362         }
363 
364         // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'.
365         version (unittest)
366             assert(
367                 bufptr - buffer.ptr == encodeLength(srcLen),
368                 "The length of result is different from Base64"
369             );
370 
371         // encode method can't assume buffer length. So, slice needed.
372         return buffer[0 .. bufptr - buffer.ptr];
373     }
374 
375 
376     // ubyte[] to OutputRange
377 
378 
379     /**
380      * Encodes $(D_PARAM source) into an
381      * $(LINK2 std_range_primitives.html#isOutputRange, output range) using
382      * Base64 encoding.
383      *
384      * Params:
385      *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
386      *           range) to _encode.
387      *  range  = The $(LINK2 std_range_primitives.html#isOutputRange, output
388      *           range) to store the encoded result.
389      *
390      * Returns:
391      *  The number of times the output range's $(D put) method was invoked.
392      */
393     size_t encode(R1, R2)(in R1 source, auto ref R2 range)
394         if (isArray!R1 && is(ElementType!R1 : ubyte) &&
395             !is(R2 == char[]) && isOutputRange!(R2, char))
out(result)396     out(result)
397     {
398         assert(result == encodeLength(source.length), "The number of put is different from the length of Base64");
399     }
400     body
401     {
402         immutable srcLen = source.length;
403         if (srcLen == 0)
404             return 0;
405 
406         immutable blocks = srcLen / 3;
407         immutable remain = srcLen % 3;
408         auto      srcptr = source.ptr;
409         size_t    pcount;
410 
411         foreach (Unused; 0 .. blocks)
412         {
413             immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2];
414             put(range, EncodeMap[val >> 18       ]);
415             put(range, EncodeMap[val >> 12 & 0x3f]);
416             put(range, EncodeMap[val >>  6 & 0x3f]);
417             put(range, EncodeMap[val       & 0x3f]);
418             srcptr += 3;
419             pcount += 4;
420         }
421 
422         if (remain)
423         {
424             immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0);
425             put(range, EncodeMap[val >> 18       ]);
426             put(range, EncodeMap[val >> 12 & 0x3f]);
427             pcount += 2;
428 
429             final switch (remain)
430             {
431             case 2:
432                 put(range, EncodeMap[val >> 6 & 0x3f]);
433                 pcount++;
434 
435                 static if (Padding != NoPadding)
436                 {
437                     put(range, Padding);
438                     pcount++;
439                 }
440                 break;
441             case 1:
442                 static if (Padding != NoPadding)
443                 {
444                     put(range, Padding);
445                     put(range, Padding);
446                     pcount += 2;
447                 }
448                 break;
449             }
450         }
451 
452         return pcount;
453     }
454 
455     ///
456     @system unittest
457     {
458         // @system because encode for OutputRange is @system
459         struct OutputRange
460         {
461             char[] result;
putOutputRange462             void put(const(char) ch) @safe { result ~= ch; }
463         }
464 
465         ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
466 
467         // This overload of encode() returns the number of calls to the output
468         // range's put method.
469         OutputRange output;
470         assert(Base64.encode(data, output) == 8);
471         assert(output.result == "Gis8TV1u");
472     }
473 
474 
475     // InputRange to OutputRange
476 
477 
478     /**
479      * ditto
480      */
481     size_t encode(R1, R2)(R1 source, auto ref R2 range)
482         if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : ubyte) &&
483             hasLength!R1 && !is(R2 == char[]) && isOutputRange!(R2, char))
out(result)484     out(result)
485     {
486         // @@@BUG@@@ Workaround for DbC problem.
487         //assert(result == encodeLength(source.length), "The number of put is different from the length of Base64");
488     }
489     body
490     {
491         immutable srcLen = source.length;
492         if (srcLen == 0)
493             return 0;
494 
495         immutable blocks = srcLen / 3;
496         immutable remain = srcLen % 3;
497         size_t    pcount;
498 
499         foreach (Unused; 0 .. blocks)
500         {
501             immutable v1 = source.front; source.popFront();
502             immutable v2 = source.front; source.popFront();
503             immutable v3 = source.front; source.popFront();
504             immutable val = v1 << 16 | v2 << 8 | v3;
505             put(range, EncodeMap[val >> 18       ]);
506             put(range, EncodeMap[val >> 12 & 0x3f]);
507             put(range, EncodeMap[val >>  6 & 0x3f]);
508             put(range, EncodeMap[val       & 0x3f]);
509             pcount += 4;
510         }
511 
512         if (remain)
513         {
514             size_t val = source.front << 16;
515             if (remain == 2)
516             {
517                 source.popFront();
518                 val |= source.front << 8;
519             }
520 
521             put(range, EncodeMap[val >> 18       ]);
522             put(range, EncodeMap[val >> 12 & 0x3f]);
523             pcount += 2;
524 
525             final switch (remain)
526             {
527             case 2:
528                 put(range, EncodeMap[val >> 6 & 0x3f]);
529                 pcount++;
530 
531                 static if (Padding != NoPadding)
532                 {
533                     put(range, Padding);
534                     pcount++;
535                 }
536                 break;
537             case 1:
538                 static if (Padding != NoPadding)
539                 {
540                     put(range, Padding);
541                     put(range, Padding);
542                     pcount += 2;
543                 }
544                 break;
545             }
546         }
547 
548         // @@@BUG@@@ Workaround for DbC problem.
549         version (unittest)
550             assert(
551                 pcount == encodeLength(srcLen),
552                 "The number of put is different from the length of Base64"
553             );
554 
555         return pcount;
556     }
557 
558 
559     /**
560      * Encodes $(D_PARAM source) to newly-allocated buffer.
561      *
562      * This convenience method alleviates the need to manually manage output
563      * buffers.
564      *
565      * Params:
566      *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
567      *           range) to _encode.
568      *
569      * Returns:
570      *  A newly-allocated $(D char[]) buffer containing the encoded string.
571      */
572     @safe
573     pure char[] encode(Range)(Range source) if (isArray!Range && is(ElementType!Range : ubyte))
574     {
575         return encode(source, new char[encodeLength(source.length)]);
576     }
577 
578     ///
579     @safe unittest
580     {
581         ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
582         assert(Base64.encode(data) == "Gis8TV1u");
583     }
584 
585 
586     /**
587      * ditto
588      */
589     char[] encode(Range)(Range source) if (!isArray!Range && isInputRange!Range &&
590                                            is(ElementType!Range : ubyte) && hasLength!Range)
591     {
592         return encode(source, new char[encodeLength(source.length)]);
593     }
594 
595 
596     /**
597      * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
598      * iterates over the respective Base64 encodings of a range of data items.
599      *
600      * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
601      * forward range) if the underlying data source is at least a forward
602      * range.
603      *
604      * Note: This struct is not intended to be created in user code directly;
605      * use the $(LREF encoder) function instead.
606      */
607     struct Encoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(ubyte)[]) ||
608                                                      is(ElementType!Range : const(char)[])))
609     {
610       private:
611         Range  range_;
612         char[] buffer_, encoded_;
613 
614 
615       public:
this(Range range)616         this(Range range)
617         {
618             range_ = range;
619             doEncoding();
620         }
621 
622 
623         /**
624          * Returns:
625          *  true if there is no more encoded data left.
626          */
627         @property @trusted
empty()628         bool empty()
629         {
630             return range_.empty;
631         }
632 
633 
634         /**
635          * Returns: The current chunk of encoded data.
636          */
637         @property @safe
front()638         nothrow char[] front()
639         {
640             return encoded_;
641         }
642 
643 
644         /**
645          * Advance the range to the next chunk of encoded data.
646          *
647          * Throws:
648          *  $(D Base64Exception) If invoked when
649          *  $(LREF2 .Base64Impl.Encoder.empty, empty) returns $(D true).
650          */
popFront()651         void popFront()
652         {
653             enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
654 
655             range_.popFront();
656 
657             /*
658              * This check is very ugly. I think this is a Range's flaw.
659              * I very strongly want the Range guideline for unified implementation.
660              *
661              * In this case, Encoder becomes a beautiful implementation if 'front' performs Base64 encoding.
662              */
663             if (!empty)
664                 doEncoding();
665         }
666 
667 
668         static if (isForwardRange!Range)
669         {
670             /**
671              * Save the current iteration state of the range.
672              *
673              * This method is only available if the underlying range is a
674              * $(LINK2 std_range_primitives.html#isForwardRange, forward
675              * range).
676              *
677              * Returns:
678              *  A copy of $(D this).
679              */
680             @property
typeof(this)681             typeof(this) save()
682             {
683                 typeof(return) encoder;
684 
685                 encoder.range_   = range_.save;
686                 encoder.buffer_  = buffer_.dup;
687                 encoder.encoded_ = encoder.buffer_[0 .. encoded_.length];
688 
689                 return encoder;
690             }
691         }
692 
693 
694       private:
doEncoding()695         void doEncoding()
696         {
697             auto data = cast(const(ubyte)[])range_.front;
698             auto size = encodeLength(data.length);
699             if (size > buffer_.length)
700                 buffer_.length = size;
701 
702             encoded_ = encode(data, buffer_);
703         }
704     }
705 
706 
707     /**
708      * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
709      * iterates over the encoded bytes of the given source data.
710      *
711      * It will be a $(LINK2 std_range_primitives.html#isForwardRange, forward
712      * range) if the underlying data source is at least a forward range.
713      *
714      * Note: This struct is not intended to be created in user code directly;
715      * use the $(LREF encoder) function instead.
716      */
717     struct Encoder(Range) if (isInputRange!Range && is(ElementType!Range : ubyte))
718     {
719       private:
720         Range range_;
721         ubyte first;
722         int   pos, padding;
723 
724 
725       public:
this(Range range)726         this(Range range)
727         {
728             range_ = range;
729             static if (isForwardRange!Range)
730                 range_ = range_.save;
731 
732             if (range_.empty)
733                 pos = -1;
734             else
735                 popFront();
736         }
737 
738 
739         /**
740          * Returns:
741          *  true if there are no more encoded characters to be iterated.
742          */
743         @property @safe
empty()744         nothrow bool empty() const
745         {
746             static if (Padding == NoPadding)
747                 return pos < 0;
748             else
749                 return pos < 0 && !padding;
750         }
751 
752 
753         /**
754          * Returns: The current encoded character.
755          */
756         @property @safe
front()757         nothrow ubyte front()
758         {
759             return first;
760         }
761 
762 
763         /**
764          * Advance to the next encoded character.
765          *
766          * Throws:
767          *  $(D Base64Exception) If invoked when $(LREF2 .Base64Impl.Encoder.empty.2,
768          *  empty) returns $(D true).
769          */
popFront()770         void popFront()
771         {
772             enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
773 
774             static if (Padding != NoPadding)
775                 if (padding)
776                 {
777                     first = Padding;
778                     pos   = -1;
779                     padding--;
780                     return;
781                 }
782 
783             if (range_.empty)
784             {
785                 pos = -1;
786                 return;
787             }
788 
789             final switch (pos)
790             {
791             case 0:
792                 first = EncodeMap[range_.front >> 2];
793                 break;
794             case 1:
795                 immutable t = (range_.front & 0b11) << 4;
796                 range_.popFront();
797 
798                 if (range_.empty)
799                 {
800                     first   = EncodeMap[t];
801                     padding = 3;
802                 }
803                 else
804                 {
805                     first = EncodeMap[t | (range_.front >> 4)];
806                 }
807                 break;
808             case 2:
809                 immutable t = (range_.front & 0b1111) << 2;
810                 range_.popFront();
811 
812                 if (range_.empty)
813                 {
814                     first   = EncodeMap[t];
815                     padding = 2;
816                 }
817                 else
818                 {
819                     first = EncodeMap[t | (range_.front >> 6)];
820                 }
821                 break;
822             case 3:
823                 first = EncodeMap[range_.front & 0b111111];
824                 range_.popFront();
825                 break;
826             }
827 
828             ++pos %= 4;
829         }
830 
831 
832         static if (isForwardRange!Range)
833         {
834             /**
835              * Save the current iteration state of the range.
836              *
837              * This method is only available if the underlying range is a
838              * $(LINK2 std_range_primitives.html#isForwardRange, forward
839              * range).
840              *
841              * Returns:
842              *  A copy of $(D this).
843              */
844             @property
typeof(this)845             typeof(this) save()
846             {
847                 auto encoder = this;
848                 encoder.range_ = encoder.range_.save;
849                 return encoder;
850             }
851         }
852     }
853 
854 
855     /**
856      * Construct an $(D Encoder) that iterates over the Base64 encoding of the
857      * given $(LINK2 std_range_primitives.html#isInputRange, input range).
858      *
859      * Params:
860      *  range = An $(LINK2 std_range_primitives.html#isInputRange, input
861      *      range) over the data to be encoded.
862      *
863      * Returns:
864      *  If $(D_PARAM range) is a range of bytes, an $(D Encoder) that iterates
865      *  over the bytes of the corresponding Base64 encoding.
866      *
867      *  If $(D_PARAM range) is a range of ranges of bytes, an $(D Encoder) that
868      *  iterates over the Base64 encoded strings of each element of the range.
869      *
870      *  In both cases, the returned $(D Encoder) will be a
871      *  $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the
872      *  given $(D range) is at least a forward range, otherwise it will be only
873      *  an input range.
874      *
875      * Example:
876      * This example encodes the input one line at a time.
877      * -----
878      * File f = File("text.txt", "r");
879      * scope(exit) f.close();
880      *
881      * uint line = 0;
882      * foreach (encoded; Base64.encoder(f.byLine()))
883      * {
884      *     writeln(++line, ". ", encoded);
885      * }
886      * -----
887      *
888      * Example:
889      * This example encodes the input data one byte at a time.
890      * -----
891      * ubyte[] data = cast(ubyte[]) "0123456789";
892      *
893      * // The ElementType of data is not aggregation type
894      * foreach (encoded; Base64.encoder(data))
895      * {
896      *     writeln(encoded);
897      * }
898      * -----
899      */
900     Encoder!(Range) encoder(Range)(Range range) if (isInputRange!Range)
901     {
902         return typeof(return)(range);
903     }
904 
905 
906     /* Decode functions */
907 
908 
909     private immutable int[char.max + 1] DecodeMap = [
910         'A':0b000000, 'B':0b000001, 'C':0b000010, 'D':0b000011, 'E':0b000100,
911         'F':0b000101, 'G':0b000110, 'H':0b000111, 'I':0b001000, 'J':0b001001,
912         'K':0b001010, 'L':0b001011, 'M':0b001100, 'N':0b001101, 'O':0b001110,
913         'P':0b001111, 'Q':0b010000, 'R':0b010001, 'S':0b010010, 'T':0b010011,
914         'U':0b010100, 'V':0b010101, 'W':0b010110, 'X':0b010111, 'Y':0b011000,
915         'Z':0b011001, 'a':0b011010, 'b':0b011011, 'c':0b011100, 'd':0b011101,
916         'e':0b011110, 'f':0b011111, 'g':0b100000, 'h':0b100001, 'i':0b100010,
917         'j':0b100011, 'k':0b100100, 'l':0b100101, 'm':0b100110, 'n':0b100111,
918         'o':0b101000, 'p':0b101001, 'q':0b101010, 'r':0b101011, 's':0b101100,
919         't':0b101101, 'u':0b101110, 'v':0b101111, 'w':0b110000, 'x':0b110001,
920         'y':0b110010, 'z':0b110011, '0':0b110100, '1':0b110101, '2':0b110110,
921         '3':0b110111, '4':0b111000, '5':0b111001, '6':0b111010, '7':0b111011,
922         '8':0b111100, '9':0b111101, Map62th:0b111110, Map63th:0b111111, Padding:-1
923     ];
924 
925 
926     /**
927      * Given a Base64 encoded string, calculates the length of the decoded
928      * string.
929      *
930      * Params:
931      *  sourceLength = The length of the Base64 encoding.
932      *
933      * Returns:
934      *  The length of the decoded string corresponding to a Base64 encoding of
935      *  length $(D_PARAM sourceLength).
936      */
937     @safe
decodeLength(in size_t sourceLength)938     pure nothrow size_t decodeLength(in size_t sourceLength)
939     {
940         static if (Padding == NoPadding)
941             return (sourceLength / 4) * 3 + (sourceLength % 4 < 2 ? 0 : sourceLength % 4 == 2 ? 1 : 2);
942         else
943             return (sourceLength / 4) * 3;
944     }
945 
946     ///
947     @safe unittest
948     {
949         auto encoded = "Gis8TV1u";
950 
951         // Allocate a sufficiently large buffer to hold to decoded result.
952         auto buffer = new ubyte[Base64.decodeLength(encoded.length)];
953 
954         Base64.decode(encoded, buffer);
955         assert(buffer == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
956     }
957 
958 
959     // Used in decode contracts. Calculates the actual size the decoded
960     // result should have, taking into account trailing padding.
961     @safe
realDecodeLength(R)962     pure nothrow private size_t realDecodeLength(R)(R source)
963     {
964         auto expect = decodeLength(source.length);
965         static if (Padding != NoPadding)
966         {
967             if (source.length % 4 == 0)
968             {
969                 expect -= source.length == 0       ? 0 :
970                           source[$ - 2] == Padding ? 2 :
971                           source[$ - 1] == Padding ? 1 : 0;
972             }
973         }
974         return expect;
975     }
976 
977 
978     // char[] to ubyte[]
979 
980 
981     /**
982      * Decodes $(D_PARAM source) into the given buffer.
983      *
984      * Params:
985      *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
986      *      range) to _decode.
987      *  buffer = The buffer to store decoded result.
988      *
989      * Returns:
990      *  The slice of $(D_PARAM buffer) containing the decoded result.
991      *
992      * Throws:
993      *  $(D Base64Exception) if $(D_PARAM source) contains characters outside the
994      *  base alphabet of the current Base64 encoding scheme.
995      */
996     @trusted
997     pure ubyte[] decode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : dchar) &&
998                                                              is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
999     in
1000     {
1001         assert(buffer.length >= realDecodeLength(source), "Insufficient buffer for decoding");
1002     }
out(result)1003     out(result)
1004     {
1005         immutable expect = realDecodeLength(source);
1006         assert(result.length == expect, "The length of result is different from the expected length");
1007     }
1008     body
1009     {
1010         immutable srcLen = source.length;
1011         if (srcLen == 0)
1012             return [];
1013         static if (Padding != NoPadding)
1014             enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
1015 
1016         immutable blocks = srcLen / 4;
1017         auto      srcptr = source.ptr;
1018         auto      bufptr = buffer.ptr;
1019 
1020         foreach (Unused; 0 .. blocks)
1021         {
1022             immutable v1 = decodeChar(*srcptr++);
1023             immutable v2 = decodeChar(*srcptr++);
1024 
1025             *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
1026 
1027             immutable v3 = decodeChar(*srcptr++);
1028             if (v3 == -1)
1029                 break;
1030 
1031             *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff);
1032 
1033             immutable v4 = decodeChar(*srcptr++);
1034             if (v4 == -1)
1035                 break;
1036 
1037             *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff);
1038         }
1039 
1040         static if (Padding == NoPadding)
1041         {
1042             immutable remain = srcLen % 4;
1043 
1044             if (remain)
1045             {
1046                 immutable v1 = decodeChar(*srcptr++);
1047                 immutable v2 = decodeChar(*srcptr++);
1048 
1049                 *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
1050 
1051                 if (remain == 3)
1052                     *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff);
1053             }
1054         }
1055 
1056         return buffer[0 .. bufptr - buffer.ptr];
1057     }
1058 
1059     ///
1060     @safe unittest
1061     {
1062         auto encoded = "Gis8TV1u";
1063         ubyte[32] buffer;   // much bigger than necessary
1064 
1065         // Just to be sure...
1066         auto decodedLength = Base64.decodeLength(encoded.length);
1067         assert(buffer.length >= decodedLength);
1068 
1069         // decode() returns a slice of the given buffer.
1070         auto decoded = Base64.decode(encoded, buffer[]);
1071         assert(decoded is buffer[0 .. decodedLength]);
1072         assert(decoded == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
1073     }
1074 
1075     // InputRange to ubyte[]
1076 
1077 
1078     /**
1079      * ditto
1080      */
1081     ubyte[] decode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 &&
1082                                                      is(ElementType!R1 : dchar) && hasLength!R1 &&
1083                                                      is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
1084     in
1085     {
1086         assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding");
1087     }
out(result)1088     out(result)
1089     {
1090         // @@@BUG@@@ Workaround for DbC problem.
1091         //immutable expect = decodeLength(source.length) - 2;
1092         //assert(result.length >= expect, "The length of result is smaller than expected length");
1093     }
1094     body
1095     {
1096         immutable srcLen = source.length;
1097         if (srcLen == 0)
1098             return [];
1099         static if (Padding != NoPadding)
1100             enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
1101 
1102         immutable blocks = srcLen / 4;
1103         auto      bufptr = buffer.ptr;
1104 
1105         foreach (Unused; 0 .. blocks)
1106         {
1107             immutable v1 = decodeChar(source.front); source.popFront();
1108             immutable v2 = decodeChar(source.front); source.popFront();
1109 
1110             *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
1111 
1112             immutable v3 = decodeChar(source.front);
1113             if (v3 == -1)
1114                 break;
1115 
1116             *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff);
1117             source.popFront();
1118 
1119             immutable v4 = decodeChar(source.front);
1120             if (v4 == -1)
1121                 break;
1122 
1123             *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff);
1124             source.popFront();
1125         }
1126 
1127         static if (Padding == NoPadding)
1128         {
1129             immutable remain = srcLen % 4;
1130 
1131             if (remain)
1132             {
1133                 immutable v1 = decodeChar(source.front); source.popFront();
1134                 immutable v2 = decodeChar(source.front);
1135 
1136                 *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
1137 
1138                 if (remain == 3)
1139                 {
1140                     source.popFront();
1141                     *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff);
1142                 }
1143             }
1144         }
1145 
1146         // @@@BUG@@@ Workaround for DbC problem.
1147         version (unittest)
1148             assert(
1149                 (bufptr - buffer.ptr) >= (decodeLength(srcLen) - 2),
1150                 "The length of result is smaller than expected length"
1151             );
1152 
1153         return buffer[0 .. bufptr - buffer.ptr];
1154     }
1155 
1156 
1157     // char[] to OutputRange
1158 
1159 
1160     /**
1161      * Decodes $(D_PARAM source) into a given
1162      * $(LINK2 std_range_primitives.html#isOutputRange, output range).
1163      *
1164      * Params:
1165      *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
1166      *           range) to _decode.
1167      *  range  = The $(LINK2 std_range_primitives.html#isOutputRange, output
1168      *           range) to store the decoded result.
1169      *
1170      * Returns:
1171      *  The number of times the output range's $(D put) method was invoked.
1172      *
1173      * Throws:
1174      *  $(D Base64Exception) if $(D_PARAM source) contains characters outside the
1175      *  base alphabet of the current Base64 encoding scheme.
1176      */
1177     size_t decode(R1, R2)(in R1 source, auto ref R2 range)
1178         if (isArray!R1 && is(ElementType!R1 : dchar) &&
1179             !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
out(result)1180     out(result)
1181     {
1182         immutable expect = realDecodeLength(source);
1183         assert(result == expect, "The result of decode is different from the expected");
1184     }
1185     body
1186     {
1187         immutable srcLen = source.length;
1188         if (srcLen == 0)
1189             return 0;
1190         static if (Padding != NoPadding)
1191             enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
1192 
1193         immutable blocks = srcLen / 4;
1194         auto      srcptr = source.ptr;
1195         size_t    pcount;
1196 
1197         foreach (Unused; 0 .. blocks)
1198         {
1199             immutable v1 = decodeChar(*srcptr++);
1200             immutable v2 = decodeChar(*srcptr++);
1201 
1202             put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
1203             pcount++;
1204 
1205             immutable v3 = decodeChar(*srcptr++);
1206             if (v3 == -1)
1207                 break;
1208 
1209             put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff));
1210             pcount++;
1211 
1212             immutable v4 = decodeChar(*srcptr++);
1213             if (v4 == -1)
1214                 break;
1215 
1216             put(range, cast(ubyte)((v3 << 6 | v4) & 0xff));
1217             pcount++;
1218         }
1219 
1220         static if (Padding == NoPadding)
1221         {
1222             immutable remain = srcLen % 4;
1223 
1224             if (remain)
1225             {
1226                 immutable v1 = decodeChar(*srcptr++);
1227                 immutable v2 = decodeChar(*srcptr++);
1228 
1229                 put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
1230                 pcount++;
1231 
1232                 if (remain == 3)
1233                 {
1234                     put(range, cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff));
1235                     pcount++;
1236                 }
1237             }
1238         }
1239 
1240         return pcount;
1241     }
1242 
1243     ///
1244     @system unittest
1245     {
1246         struct OutputRange
1247         {
1248             ubyte[] result;
putOutputRange1249             void put(ubyte b) { result ~= b; }
1250         }
1251         OutputRange output;
1252 
1253         // This overload of decode() returns the number of calls to put().
1254         assert(Base64.decode("Gis8TV1u", output) == 6);
1255         assert(output.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
1256     }
1257 
1258 
1259     // InputRange to OutputRange
1260 
1261 
1262     /**
1263      * ditto
1264      */
1265     size_t decode(R1, R2)(R1 source, auto ref R2 range)
1266         if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : dchar) &&
1267             hasLength!R1 && !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
out(result)1268     out(result)
1269     {
1270         // @@@BUG@@@ Workaround for DbC problem.
1271         //immutable expect = decodeLength(source.length) - 2;
1272         //assert(result >= expect, "The length of result is smaller than expected length");
1273     }
1274     body
1275     {
1276         immutable srcLen = source.length;
1277         if (srcLen == 0)
1278             return 0;
1279         static if (Padding != NoPadding)
1280             enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
1281 
1282         immutable blocks = srcLen / 4;
1283         size_t    pcount;
1284 
1285         foreach (Unused; 0 .. blocks)
1286         {
1287             immutable v1 = decodeChar(source.front); source.popFront();
1288             immutable v2 = decodeChar(source.front); source.popFront();
1289 
1290             put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
1291             pcount++;
1292 
1293             immutable v3 = decodeChar(source.front);
1294             if (v3 == -1)
1295                 break;
1296 
1297             put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff));
1298             source.popFront();
1299             pcount++;
1300 
1301             immutable v4 = decodeChar(source.front);
1302             if (v4 == -1)
1303                 break;
1304 
1305             put(range, cast(ubyte)((v3 << 6 | v4) & 0xff));
1306             source.popFront();
1307             pcount++;
1308         }
1309 
1310         static if (Padding == NoPadding)
1311         {
1312             immutable remain = srcLen % 4;
1313 
1314             if (remain)
1315             {
1316                 immutable v1 = decodeChar(source.front); source.popFront();
1317                 immutable v2 = decodeChar(source.front);
1318 
1319                 put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
1320                 pcount++;
1321 
1322                 if (remain == 3)
1323                 {
1324                     source.popFront();
1325                     put(range, cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff));
1326                     pcount++;
1327                 }
1328             }
1329         }
1330 
1331         // @@@BUG@@@ Workaround for DbC problem.
1332         version (unittest)
1333             assert(
1334                 pcount >= (decodeLength(srcLen) - 2),
1335                 "The length of result is smaller than expected length"
1336             );
1337 
1338         return pcount;
1339     }
1340 
1341 
1342     /**
1343      * Decodes $(D_PARAM source) into newly-allocated buffer.
1344      *
1345      * This convenience method alleviates the need to manually manage decoding
1346      * buffers.
1347      *
1348      * Params:
1349      *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
1350      *           range) to _decode.
1351      *
1352      * Returns:
1353      *  A newly-allocated $(D ubyte[]) buffer containing the decoded string.
1354      */
1355     @safe
1356     pure ubyte[] decode(Range)(Range source) if (isArray!Range && is(ElementType!Range : dchar))
1357     {
1358         return decode(source, new ubyte[decodeLength(source.length)]);
1359     }
1360 
1361     ///
1362     @safe unittest
1363     {
1364         auto data = "Gis8TV1u";
1365         assert(Base64.decode(data) == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
1366     }
1367 
1368 
1369     /**
1370      * ditto
1371      */
1372     ubyte[] decode(Range)(Range source) if (!isArray!Range && isInputRange!Range &&
1373                                             is(ElementType!Range : dchar) && hasLength!Range)
1374     {
1375         return decode(source, new ubyte[decodeLength(source.length)]);
1376     }
1377 
1378 
1379     /**
1380      * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
1381      * iterates over the decoded data of a range of Base64 encodings.
1382      *
1383      * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
1384      * forward range) if the underlying data source is at least a forward
1385      * range.
1386      *
1387      * Note: This struct is not intended to be created in user code directly;
1388      * use the $(LREF decoder) function instead.
1389      */
1390     struct Decoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(char)[]) ||
1391                                                      is(ElementType!Range : const(ubyte)[])))
1392     {
1393       private:
1394         Range   range_;
1395         ubyte[] buffer_, decoded_;
1396 
1397 
1398       public:
this(Range range)1399         this(Range range)
1400         {
1401             range_ = range;
1402             doDecoding();
1403         }
1404 
1405 
1406         /**
1407          * Returns:
1408          *  true if there are no more elements to be iterated.
1409          */
1410         @property @trusted
empty()1411         bool empty()
1412         {
1413             return range_.empty;
1414         }
1415 
1416 
1417         /**
1418          * Returns: The decoding of the current element in the input.
1419          */
1420         @property @safe
front()1421         nothrow ubyte[] front()
1422         {
1423             return decoded_;
1424         }
1425 
1426 
1427         /**
1428          * Advance to the next element in the input to be decoded.
1429          *
1430          * Throws:
1431          *  $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty,
1432          *  empty) returns $(D true).
1433          */
popFront()1434         void popFront()
1435         {
1436             enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining."));
1437 
1438             range_.popFront();
1439 
1440             /*
1441              * I mentioned Encoder's popFront.
1442              */
1443             if (!empty)
1444                 doDecoding();
1445         }
1446 
1447 
1448         static if (isForwardRange!Range)
1449         {
1450             /**
1451              * Saves the current iteration state.
1452              *
1453              * This method is only available if the underlying range is a
1454              * $(LINK2 std_range_primitives.html#isForwardRange, forward
1455              * range).
1456              *
1457              * Returns: A copy of $(D this).
1458              */
1459             @property
typeof(this)1460             typeof(this) save()
1461             {
1462                 typeof(return) decoder;
1463 
1464                 decoder.range_   = range_.save;
1465                 decoder.buffer_  = buffer_.dup;
1466                 decoder.decoded_ = decoder.buffer_[0 .. decoded_.length];
1467 
1468                 return decoder;
1469             }
1470         }
1471 
1472 
1473       private:
doDecoding()1474         void doDecoding()
1475         {
1476             auto data = cast(const(char)[])range_.front;
1477 
1478             static if (Padding == NoPadding)
1479             {
1480                 while (data.length % 4 == 1)
1481                 {
1482                     range_.popFront();
1483                     data ~= cast(const(char)[])range_.front;
1484                 }
1485             }
1486             else
1487             {
1488                 while (data.length % 4 != 0)
1489                 {
1490                     range_.popFront();
1491                     data ~= cast(const(char)[])range_.front;
1492                 }
1493             }
1494 
1495             auto size = decodeLength(data.length);
1496             if (size > buffer_.length)
1497                 buffer_.length = size;
1498 
1499             decoded_ = decode(data, buffer_);
1500         }
1501     }
1502 
1503 
1504     /**
1505      * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
1506      * iterates over the bytes of data decoded from a Base64 encoded string.
1507      *
1508      * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
1509      * forward range) if the underlying data source is at least a forward
1510      * range.
1511      *
1512      * Note: This struct is not intended to be created in user code directly;
1513      * use the $(LREF decoder) function instead.
1514      */
1515     struct Decoder(Range) if (isInputRange!Range && is(ElementType!Range : char))
1516     {
1517       private:
1518         Range range_;
1519         ubyte first;
1520         int   pos;
1521 
1522 
1523       public:
this(Range range)1524         this(Range range)
1525         {
1526             range_ = range;
1527             static if (isForwardRange!Range)
1528                 range_ = range_.save;
1529 
1530             static if (Padding != NoPadding && hasLength!Range)
1531                 enforce(range_.length % 4 == 0, new Base64Exception("Invalid length of encoded data"));
1532 
1533             if (range_.empty)
1534                 pos = -1;
1535             else
1536                 popFront();
1537         }
1538 
1539 
1540         /**
1541          * Returns:
1542          *  true if there are no more elements to be iterated.
1543          */
1544         @property @safe
empty()1545         nothrow bool empty() const
1546         {
1547             return pos < 0;
1548         }
1549 
1550 
1551         /**
1552          * Returns: The current decoded byte.
1553          */
1554         @property @safe
front()1555         nothrow ubyte front()
1556         {
1557             return first;
1558         }
1559 
1560 
1561         /**
1562          * Advance to the next decoded byte.
1563          *
1564          * Throws:
1565          *  $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty,
1566          *  empty) returns $(D true).
1567          */
popFront()1568         void popFront()
1569         {
1570             enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining"));
1571 
1572             static if (Padding == NoPadding)
1573             {
1574                 bool endCondition()
1575                 {
1576                     return range_.empty;
1577                 }
1578             }
1579             else
1580             {
1581                 bool endCondition()
1582                 {
1583                     enforce(!range_.empty, new Base64Exception("Missing padding"));
1584                     return range_.front == Padding;
1585                 }
1586             }
1587 
1588             if (range_.empty || range_.front == Padding)
1589             {
1590                 pos = -1;
1591                 return;
1592             }
1593 
1594             final switch (pos)
1595             {
1596             case 0:
1597                 enforce(!endCondition(), new Base64Exception("Premature end of data found"));
1598 
1599                 immutable t = DecodeMap[range_.front] << 2;
1600                 range_.popFront();
1601 
1602                 enforce(!endCondition(), new Base64Exception("Premature end of data found"));
1603                 first = cast(ubyte)(t | (DecodeMap[range_.front] >> 4));
1604                 break;
1605             case 1:
1606                 immutable t = (DecodeMap[range_.front] & 0b1111) << 4;
1607                 range_.popFront();
1608 
1609                 if (endCondition())
1610                 {
1611                     pos = -1;
1612                     return;
1613                 }
1614                 else
1615                 {
1616                     first = cast(ubyte)(t | (DecodeMap[range_.front] >> 2));
1617                 }
1618                 break;
1619             case 2:
1620                 immutable t = (DecodeMap[range_.front] & 0b11) << 6;
1621                 range_.popFront();
1622 
1623                 if (endCondition())
1624                 {
1625                     pos = -1;
1626                     return;
1627                 }
1628                 else
1629                 {
1630                     first = cast(ubyte)(t | DecodeMap[range_.front]);
1631                 }
1632 
1633                 range_.popFront();
1634                 break;
1635             }
1636 
1637             ++pos %= 3;
1638         }
1639 
1640 
1641         static if (isForwardRange!Range)
1642         {
1643             /**
1644              * Saves the current iteration state.
1645              *
1646              * This method is only available if the underlying range is a
1647              * $(LINK2 std_range_primitives.html#isForwardRange, forward
1648              * range).
1649              *
1650              * Returns: A copy of $(D this).
1651              */
1652             @property
typeof(this)1653             typeof(this) save()
1654             {
1655                 auto decoder = this;
1656                 decoder.range_ = decoder.range_.save;
1657                 return decoder;
1658             }
1659         }
1660     }
1661 
1662 
1663     /**
1664      * Construct a $(D Decoder) that iterates over the decoding of the given
1665      * Base64 encoded data.
1666      *
1667      * Params:
1668      *  range = An $(LINK2 std_range_primitives.html#isInputRange, input
1669      *      range) over the data to be decoded.
1670      *
1671      * Returns:
1672      *  If $(D_PARAM range) is a range of characters, a $(D Decoder) that
1673      *  iterates over the bytes of the corresponding Base64 decoding.
1674      *
1675      *  If $(D_PARAM range) is a range of ranges of characters, a $(D Decoder)
1676      *  that iterates over the decoded strings corresponding to each element of
1677      *  the range. In this case, the length of each subrange must be a multiple
1678      *  of 4; the returned _decoder does not keep track of Base64 decoding
1679      *  state across subrange boundaries.
1680      *
1681      *  In both cases, the returned $(D Decoder) will be a
1682      *  $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the
1683      *  given $(D range) is at least a forward range, otherwise it will be only
1684      *  an input range.
1685      *
1686      * If the input data contains characters not found in the base alphabet of
1687      * the current Base64 encoding scheme, the returned range may throw a
1688      * $(D Base64Exception).
1689      *
1690      * Example:
1691      * This example shows decoding over a range of input data lines.
1692      * -----
1693      * foreach (decoded; Base64.decoder(stdin.byLine()))
1694      * {
1695      *     writeln(decoded);
1696      * }
1697      * -----
1698      *
1699      * Example:
1700      * This example shows decoding one byte at a time.
1701      * -----
1702      * auto encoded = Base64.encoder(cast(ubyte[])"0123456789");
1703      * foreach (n; map!q{a - '0'}(Base64.decoder(encoded)))
1704      * {
1705      *     writeln(n);
1706      * }
1707      * -----
1708      */
1709     Decoder!(Range) decoder(Range)(Range range) if (isInputRange!Range)
1710     {
1711         return typeof(return)(range);
1712     }
1713 
1714 
1715   private:
1716     @safe
decodeChar()1717     pure int decodeChar()(char chr)
1718     {
1719         immutable val = DecodeMap[chr];
1720 
1721         // enforce can't be a pure function, so I use trivial check.
1722         if (val == 0 && chr != 'A')
1723             throw new Base64Exception("Invalid character: " ~ chr);
1724 
1725         return val;
1726     }
1727 
1728 
1729     @safe
decodeChar()1730     pure int decodeChar()(dchar chr)
1731     {
1732         // See above comment.
1733         if (chr > 0x7f)
1734             throw new Base64Exception("Base64-encoded character must be a single byte");
1735 
1736         return decodeChar(cast(char) chr);
1737     }
1738 }
1739 
1740 ///
1741 @safe unittest
1742 {
1743     import std.string : representation;
1744 
1745     // pre-defined: alias Base64 = Base64Impl!('+', '/');
1746     ubyte[] emptyArr;
1747     assert(Base64.encode(emptyArr) == "");
1748     assert(Base64.encode("f".representation) == "Zg==");
1749     assert(Base64.encode("foo".representation) == "Zm9v");
1750 
1751     alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding);
1752     assert(Base64Re.encode("f".representation) == "Zg");
1753     assert(Base64Re.encode("foo".representation) == "Zm9v");
1754 }
1755 
1756 /**
1757  * Exception thrown upon encountering Base64 encoding or decoding errors.
1758  */
1759 class Base64Exception : Exception
1760 {
1761     @safe pure nothrow
1762     this(string s, string fn = __FILE__, size_t ln = __LINE__)
1763     {
1764         super(s, fn, ln);
1765     }
1766 }
1767 
1768 ///
1769 @system unittest
1770 {
1771     import std.exception : assertThrown;
1772     assertThrown!Base64Exception(Base64.decode("ab|c"));
1773 }
1774 
1775 
1776 @system unittest
1777 {
1778     import std.algorithm.comparison : equal;
1779     import std.algorithm.sorting : sort;
1780     import std.conv;
1781     import std.file;
1782     import std.stdio;
1783 
1784     alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding);
1785 
1786     // Test vectors from RFC 4648
1787     ubyte[][string] tv = [
1788          ""      :cast(ubyte[])"",
1789          "f"     :cast(ubyte[])"f",
1790          "fo"    :cast(ubyte[])"fo",
1791          "foo"   :cast(ubyte[])"foo",
1792          "foob"  :cast(ubyte[])"foob",
1793          "fooba" :cast(ubyte[])"fooba",
1794          "foobar":cast(ubyte[])"foobar"
1795     ];
1796 
1797     { // Base64
1798         // encode
1799         assert(Base64.encodeLength(tv[""].length)       == 0);
1800         assert(Base64.encodeLength(tv["f"].length)      == 4);
1801         assert(Base64.encodeLength(tv["fo"].length)     == 4);
1802         assert(Base64.encodeLength(tv["foo"].length)    == 4);
1803         assert(Base64.encodeLength(tv["foob"].length)   == 8);
1804         assert(Base64.encodeLength(tv["fooba"].length)  == 8);
1805         assert(Base64.encodeLength(tv["foobar"].length) == 8);
1806 
1807         assert(Base64.encode(tv[""])       == "");
1808         assert(Base64.encode(tv["f"])      == "Zg==");
1809         assert(Base64.encode(tv["fo"])     == "Zm8=");
1810         assert(Base64.encode(tv["foo"])    == "Zm9v");
1811         assert(Base64.encode(tv["foob"])   == "Zm9vYg==");
1812         assert(Base64.encode(tv["fooba"])  == "Zm9vYmE=");
1813         assert(Base64.encode(tv["foobar"]) == "Zm9vYmFy");
1814 
1815         // decode
1816         assert(Base64.decodeLength(Base64.encode(tv[""]).length)       == 0);
1817         assert(Base64.decodeLength(Base64.encode(tv["f"]).length)      == 3);
1818         assert(Base64.decodeLength(Base64.encode(tv["fo"]).length)     == 3);
1819         assert(Base64.decodeLength(Base64.encode(tv["foo"]).length)    == 3);
1820         assert(Base64.decodeLength(Base64.encode(tv["foob"]).length)   == 6);
1821         assert(Base64.decodeLength(Base64.encode(tv["fooba"]).length)  == 6);
1822         assert(Base64.decodeLength(Base64.encode(tv["foobar"]).length) == 6);
1823 
1824         assert(Base64.decode(Base64.encode(tv[""]))       == tv[""]);
1825         assert(Base64.decode(Base64.encode(tv["f"]))      == tv["f"]);
1826         assert(Base64.decode(Base64.encode(tv["fo"]))     == tv["fo"]);
1827         assert(Base64.decode(Base64.encode(tv["foo"]))    == tv["foo"]);
1828         assert(Base64.decode(Base64.encode(tv["foob"]))   == tv["foob"]);
1829         assert(Base64.decode(Base64.encode(tv["fooba"]))  == tv["fooba"]);
1830         assert(Base64.decode(Base64.encode(tv["foobar"])) == tv["foobar"]);
1831 
1832         assertThrown!Base64Exception(Base64.decode("ab|c"));
1833 
1834         // Test decoding incomplete strings. RFC does not specify the correct
1835         // behavior, but the code should never throw Errors on invalid input.
1836 
1837         // decodeLength is nothrow
1838         assert(Base64.decodeLength(1) == 0);
1839         assert(Base64.decodeLength(2) <= 1);
1840         assert(Base64.decodeLength(3) <= 2);
1841 
1842         // may throw Exceptions, may not throw Errors
1843         assertThrown!Base64Exception(Base64.decode("Zg"));
1844         assertThrown!Base64Exception(Base64.decode("Zg="));
1845         assertThrown!Base64Exception(Base64.decode("Zm8"));
1846         assertThrown!Base64Exception(Base64.decode("Zg==;"));
1847     }
1848 
1849     { // No padding
1850         // encode
1851         assert(Base64Re.encodeLength(tv[""].length)       == 0);
1852         assert(Base64Re.encodeLength(tv["f"].length)      == 2);
1853         assert(Base64Re.encodeLength(tv["fo"].length)     == 3);
1854         assert(Base64Re.encodeLength(tv["foo"].length)    == 4);
1855         assert(Base64Re.encodeLength(tv["foob"].length)   == 6);
1856         assert(Base64Re.encodeLength(tv["fooba"].length)  == 7);
1857         assert(Base64Re.encodeLength(tv["foobar"].length) == 8);
1858 
1859         assert(Base64Re.encode(tv[""])       == "");
1860         assert(Base64Re.encode(tv["f"])      == "Zg");
1861         assert(Base64Re.encode(tv["fo"])     == "Zm8");
1862         assert(Base64Re.encode(tv["foo"])    == "Zm9v");
1863         assert(Base64Re.encode(tv["foob"])   == "Zm9vYg");
1864         assert(Base64Re.encode(tv["fooba"])  == "Zm9vYmE");
1865         assert(Base64Re.encode(tv["foobar"]) == "Zm9vYmFy");
1866 
1867         // decode
1868         assert(Base64Re.decodeLength(Base64Re.encode(tv[""]).length)       == 0);
1869         assert(Base64Re.decodeLength(Base64Re.encode(tv["f"]).length)      == 1);
1870         assert(Base64Re.decodeLength(Base64Re.encode(tv["fo"]).length)     == 2);
1871         assert(Base64Re.decodeLength(Base64Re.encode(tv["foo"]).length)    == 3);
1872         assert(Base64Re.decodeLength(Base64Re.encode(tv["foob"]).length)   == 4);
1873         assert(Base64Re.decodeLength(Base64Re.encode(tv["fooba"]).length)  == 5);
1874         assert(Base64Re.decodeLength(Base64Re.encode(tv["foobar"]).length) == 6);
1875 
1876         assert(Base64Re.decode(Base64Re.encode(tv[""]))       == tv[""]);
1877         assert(Base64Re.decode(Base64Re.encode(tv["f"]))      == tv["f"]);
1878         assert(Base64Re.decode(Base64Re.encode(tv["fo"]))     == tv["fo"]);
1879         assert(Base64Re.decode(Base64Re.encode(tv["foo"]))    == tv["foo"]);
1880         assert(Base64Re.decode(Base64Re.encode(tv["foob"]))   == tv["foob"]);
1881         assert(Base64Re.decode(Base64Re.encode(tv["fooba"]))  == tv["fooba"]);
1882         assert(Base64Re.decode(Base64Re.encode(tv["foobar"])) == tv["foobar"]);
1883 
1884         // decodeLength is nothrow
1885         assert(Base64.decodeLength(1) == 0);
1886     }
1887 
1888     { // with OutputRange
1889         import std.array;
1890 
1891         auto a = Appender!(char[])([]);
1892         auto b = Appender!(ubyte[])([]);
1893 
1894         assert(Base64.encode(tv[""], a) == 0);
1895         assert(Base64.decode(a.data, b) == 0);
1896         assert(tv[""] == b.data); a.clear(); b.clear();
1897 
1898         assert(Base64.encode(tv["f"], a) == 4);
1899         assert(Base64.decode(a.data,  b) == 1);
1900         assert(tv["f"] == b.data); a.clear(); b.clear();
1901 
1902         assert(Base64.encode(tv["fo"], a) == 4);
1903         assert(Base64.decode(a.data,   b) == 2);
1904         assert(tv["fo"] == b.data); a.clear(); b.clear();
1905 
1906         assert(Base64.encode(tv["foo"], a) == 4);
1907         assert(Base64.decode(a.data,    b) == 3);
1908         assert(tv["foo"] == b.data); a.clear(); b.clear();
1909 
1910         assert(Base64.encode(tv["foob"], a) == 8);
1911         assert(Base64.decode(a.data,     b) == 4);
1912         assert(tv["foob"] == b.data); a.clear(); b.clear();
1913 
1914         assert(Base64.encode(tv["fooba"], a) == 8);
1915         assert(Base64.decode(a.data, b)      == 5);
1916         assert(tv["fooba"] == b.data); a.clear(); b.clear();
1917 
1918         assert(Base64.encode(tv["foobar"], a) == 8);
1919         assert(Base64.decode(a.data, b)       == 6);
1920         assert(tv["foobar"] == b.data); a.clear(); b.clear();
1921     }
1922 
1923     // @@@9543@@@ These tests were disabled because they actually relied on the input range having length.
1924     // The implementation (currently) doesn't support encoding/decoding from a length-less source.
version(none)1925     version (none)
1926     { // with InputRange
1927         // InputRange to ubyte[] or char[]
1928         auto encoded = Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]));
1929         assert(encoded == "FPucA9l+");
1930         assert(Base64.decode(map!q{a}(encoded)) == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
1931 
1932         // InputRange to OutputRange
1933         auto a = Appender!(char[])([]);
1934         auto b = Appender!(ubyte[])([]);
1935         assert(Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]), a) == 8);
1936         assert(a.data == "FPucA9l+");
1937         assert(Base64.decode(map!q{a}(a.data), b) == 6);
1938         assert(b.data == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
1939     }
1940 
1941     { // Encoder and Decoder
1942         {
1943             string encode_file = std.file.deleteme ~ "-testingEncoder";
1944             std.file.write(encode_file, "\nf\nfo\nfoo\nfoob\nfooba\nfoobar");
1945 
1946             auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"];
1947             auto f = File(encode_file);
scope(exit)1948             scope(exit)
1949             {
1950                 f.close();
1951                 assert(!f.isOpen);
1952                 std.file.remove(encode_file);
1953             }
1954 
1955             size_t i;
1956             foreach (encoded; Base64.encoder(f.byLine()))
1957                 assert(encoded == witness[i++]);
1958 
1959             assert(i == witness.length);
1960         }
1961 
1962         {
1963             string decode_file = std.file.deleteme ~ "-testingDecoder";
1964             std.file.write(decode_file, "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy");
1965 
1966             auto witness = sort(tv.keys);
1967             auto f = File(decode_file);
scope(exit)1968             scope(exit)
1969             {
1970                 f.close();
1971                 assert(!f.isOpen);
1972                 std.file.remove(decode_file);
1973             }
1974 
1975             size_t i;
1976             foreach (decoded; Base64.decoder(f.byLine()))
1977                 assert(decoded == witness[i++]);
1978 
1979             assert(i == witness.length);
1980         }
1981 
1982         { // ForwardRange
1983             {
1984                 auto encoder = Base64.encoder(sort(tv.values));
1985                 auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"];
1986                 size_t i;
1987 
1988                 assert(encoder.front == witness[i++]); encoder.popFront();
1989                 assert(encoder.front == witness[i++]); encoder.popFront();
1990                 assert(encoder.front == witness[i++]); encoder.popFront();
1991 
1992                 foreach (encoded; encoder.save)
1993                     assert(encoded == witness[i++]);
1994             }
1995 
1996             {
1997                 auto decoder = Base64.decoder(["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]);
1998                 auto witness = sort(tv.values);
1999                 size_t i;
2000 
2001                 assert(decoder.front == witness[i++]); decoder.popFront();
2002                 assert(decoder.front == witness[i++]); decoder.popFront();
2003                 assert(decoder.front == witness[i++]); decoder.popFront();
2004 
2005                 foreach (decoded; decoder.save)
2006                     assert(decoded == witness[i++]);
2007             }
2008         }
2009     }
2010 
2011     { // Encoder and Decoder for single character encoding and decoding
2012         alias Base64NoPadding = Base64Impl!('+', '/', Base64.NoPadding);
2013 
2014         auto tests = [
2015             ""       : ["", "", "", ""],
2016             "f"      : ["Zg==", "Zg==", "Zg", "Zg"],
2017             "fo"     : ["Zm8=", "Zm8=", "Zm8", "Zm8"],
2018             "foo"    : ["Zm9v", "Zm9v", "Zm9v", "Zm9v"],
2019             "foob"   : ["Zm9vYg==", "Zm9vYg==", "Zm9vYg", "Zm9vYg"],
2020             "fooba"  : ["Zm9vYmE=", "Zm9vYmE=", "Zm9vYmE", "Zm9vYmE"],
2021             "foobar" : ["Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy"],
2022         ];
2023 
foreach(u,e;tests)2024         foreach (u, e; tests)
2025         {
2026             assert(equal(Base64.encoder(cast(ubyte[]) u), e[0]));
2027             assert(equal(Base64.decoder(Base64.encoder(cast(ubyte[]) u)), u));
2028 
2029             assert(equal(Base64URL.encoder(cast(ubyte[]) u), e[1]));
2030             assert(equal(Base64URL.decoder(Base64URL.encoder(cast(ubyte[]) u)), u));
2031 
2032             assert(equal(Base64NoPadding.encoder(cast(ubyte[]) u), e[2]));
2033             assert(equal(Base64NoPadding.decoder(Base64NoPadding.encoder(cast(ubyte[]) u)), u));
2034 
2035             assert(equal(Base64Re.encoder(cast(ubyte[]) u), e[3]));
2036             assert(equal(Base64Re.decoder(Base64Re.encoder(cast(ubyte[]) u)), u));
2037         }
2038     }
2039 }
2040 
2041 // Regression control for the output range ref bug in encode.
2042 @system unittest
2043 {
2044     struct InputRange
2045     {
2046         ubyte[] impl = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
emptyInputRange2047         @property bool empty() { return impl.length == 0; }
frontInputRange2048         @property ubyte front() { return impl[0]; }
popFrontInputRange2049         void popFront() { impl = impl[1 .. $]; }
lengthInputRange2050         @property size_t length() { return impl.length; }
2051     }
2052 
2053     struct OutputRange
2054     {
2055         char[] result;
put(char b)2056         void put(char b) { result ~= b; }
2057     }
2058 
2059     InputRange ir;
2060     OutputRange or;
2061     assert(Base64.encode(ir, or) == 8);
2062     assert(or.result == "Gis8TV1u");
2063 
2064     // Verify that any existing workaround that uses & still works.
2065     InputRange ir2;
2066     OutputRange or2;
2067     assert(Base64.encode(ir2, &or2) == 8);
2068     assert(or2.result == "Gis8TV1u");
2069 }
2070 
2071 // Regression control for the output range ref bug in decode.
2072 @system unittest
2073 {
2074     struct InputRange
2075     {
2076         const(char)[] impl = "Gis8TV1u";
emptyInputRange2077         @property bool empty() { return impl.length == 0; }
frontInputRange2078         @property dchar front() { return impl[0]; }
popFrontInputRange2079         void popFront() { impl = impl[1 .. $]; }
lengthInputRange2080         @property size_t length() { return impl.length; }
2081     }
2082 
2083     struct OutputRange
2084     {
2085         ubyte[] result;
put(ubyte b)2086         void put(ubyte b) { result ~= b; }
2087     }
2088 
2089     InputRange ir;
2090     OutputRange or;
2091     assert(Base64.decode(ir, or) == 6);
2092     assert(or.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
2093 
2094     // Verify that any existing workaround that uses & still works.
2095     InputRange ir2;
2096     OutputRange or2;
2097     assert(Base64.decode(ir2, &or2) == 6);
2098     assert(or2.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
2099 }
2100