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