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