1 // Written in the D programming language.
4 * Support for Base64 encoding and decoding.
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.
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.
19 * ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e];
21 * const(char)[] encoded = Base64.encode(data);
22 * assert(encoded == "FPucA9l+");
24 * ubyte[] decoded = Base64.decode("FPucA9l+");
25 * assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
28 * The range API is supported for both encoding and decoding:
32 * // Create MIME Base64 with CRLF, per line 76.
33 * File f = File("./text.txt", "r");
34 * scope(exit) f.close();
36 * Appender!string mime64 = appender!string;
38 * foreach (encoded; Base64.encoder(f.byChunk(57)))
40 * mime64.put(encoded);
44 * writeln(mime64.data);
48 * $(LINK2 https://tools.ietf.org/html/rfc4648, RFC 4648 - The Base16, Base32, and Base64
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)
56 * LREF2=<a href="#$1">$(D $2)</a>
60 import std
.exception
; // enforce
61 import std
.range
.primitives
; // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength
62 import std
.traits
; // isArray
64 // Make sure module header code examples work correctly.
67 ubyte[] data
= [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e];
69 const(char)[] encoded
= Base64
.encode(data
);
70 assert(encoded
== "FPucA9l+");
72 ubyte[] decoded
= Base64
.decode("FPucA9l+");
73 assert(decoded
== [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
77 * Implementation of standard _Base64 encoding.
79 * See $(LREF Base64Impl) for a description of available methods.
81 alias Base64
= Base64Impl
!('+', '/');
86 ubyte[] data
= [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
87 assert(Base64
.encode(data
) == "g9cwegE/");
88 assert(Base64
.decode("g9cwegE/") == data
);
93 * Variation of Base64 encoding that is safe for use in URLs and filenames.
95 * See $(LREF Base64Impl) for a description of available methods.
97 alias Base64URL
= Base64Impl
!('-', '_');
102 ubyte[] data
= [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
103 assert(Base64URL
.encode(data
) == "g9cwegE_");
104 assert(Base64URL
.decode("g9cwegE_") == data
);
108 * Unpadded variation of Base64 encoding that is safe for use in URLs and
109 * filenames, as used in RFCs 4648 and 7515 (JWS/JWT/JWE).
111 * See $(LREF Base64Impl) for a description of available methods.
113 alias Base64URLNoPadding
= Base64Impl
!('-', '_', Base64
.NoPadding
);
118 ubyte[] data
= [0x83, 0xd7, 0x30, 0x7b, 0xef];
119 assert(Base64URLNoPadding
.encode(data
) == "g9cwe-8");
120 assert(Base64URLNoPadding
.decode("g9cwe-8") == data
);
124 * Template for implementing Base64 encoding and decoding.
126 * For most purposes, direct usage of this template is not necessary; instead,
127 * this module provides default implementations: $(LREF Base64), implementing
128 * basic Base64 encoding, and $(LREF Base64URL) and $(LREF Base64URLNoPadding),
129 * that implement the Base64 variant for use in URLs and filenames, with
130 * and without padding, respectively.
132 * Customized Base64 encoding schemes can be implemented by instantiating this
133 * template with the appropriate arguments. For example:
136 * // Non-standard Base64 format for embedding in regular expressions.
137 * alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding);
141 * Encoded strings will not have any padding if the $(D Padding) parameter is
142 * set to $(D NoPadding).
144 template Base64Impl(char Map62th
, char Map63th
, char Padding
= '=')
146 enum NoPadding
= '\0'; /// represents no-padding encoding
149 // Verify Base64 characters
150 static assert(Map62th
< 'A' || Map62th
> 'Z', "Character '" ~ Map62th
~ "' cannot be used twice");
151 static assert(Map63th
< 'A' || Map63th
> 'Z', "Character '" ~ Map63th
~ "' cannot be used twice");
152 static assert(Padding
< 'A' || Padding
> 'Z', "Character '" ~ Padding
~ "' cannot be used twice");
153 static assert(Map62th
< 'a' || Map62th
> 'z', "Character '" ~ Map62th
~ "' cannot be used twice");
154 static assert(Map63th
< 'a' || Map63th
> 'z', "Character '" ~ Map63th
~ "' cannot be used twice");
155 static assert(Padding
< 'a' || Padding
> 'z', "Character '" ~ Padding
~ "' cannot be used twice");
156 static assert(Map62th
< '0' || Map62th
> '9', "Character '" ~ Map62th
~ "' cannot be used twice");
157 static assert(Map63th
< '0' || Map63th
> '9', "Character '" ~ Map63th
~ "' cannot be used twice");
158 static assert(Padding
< '0' || Padding
> '9', "Character '" ~ Padding
~ "' cannot be used twice");
159 static assert(Map62th
!= Map63th
, "Character '" ~ Map63th
~ "' cannot be used twice");
160 static assert(Map62th
!= Padding
, "Character '" ~ Padding
~ "' cannot be used twice");
161 static assert(Map63th
!= Padding
, "Character '" ~ Padding
~ "' cannot be used twice");
162 static assert(Map62th
!= NoPadding
, "'\\0' is not a valid Base64character");
163 static assert(Map63th
!= NoPadding
, "'\\0' is not a valid Base64character");
166 /* Encode functions */
169 private immutable EncodeMap
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ~ Map62th
~ Map63th
;
173 * Calculates the length needed to store the encoded string corresponding
174 * to an input of the given length.
177 * sourceLength = Length of the source array.
180 * The length of a Base64 encoding of an array of the given length.
183 pure nothrow size_t
encodeLength(in size_t sourceLength
)
185 static if (Padding
== NoPadding
)
186 return (sourceLength
/ 3) * 4 + (sourceLength
% 3 == 0 ?
0 : sourceLength
% 3 == 1 ?
2 : 3);
188 return (sourceLength
/ 3 + (sourceLength
% 3 ?
1 : 0)) * 4;
194 ubyte[] data
= [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
196 // Allocate a buffer large enough to hold the encoded string.
197 auto buf
= new char[Base64
.encodeLength(data
.length
)];
199 Base64
.encode(data
, buf
);
200 assert(buf
== "Gis8TV1u");
208 * Encode $(D_PARAM source) into a $(D char[]) buffer using Base64
212 * source = The $(LINK2 std_range_primitives.html#isInputRange, input
214 * buffer = The $(D char[]) buffer to store the encoded result.
217 * The slice of $(D_PARAM buffer) that contains the encoded string.
220 pure char[] encode(R1
, R2
)(in R1 source
, R2 buffer
) if (isArray
!R1
&& is(ElementType
!R1
: ubyte) &&
224 assert(buffer
.length
>= encodeLength(source
.length
), "Insufficient buffer for encoding");
228 assert(result
.length
== encodeLength(source
.length
), "The length of result is different from Base64");
232 immutable srcLen
= source
.length
;
236 immutable blocks
= srcLen
/ 3;
237 immutable remain
= srcLen
% 3;
238 auto bufptr
= buffer
.ptr
;
239 auto srcptr
= source
.ptr
;
241 foreach (Unused
; 0 .. blocks
)
243 immutable val
= srcptr
[0] << 16 | srcptr
[1] << 8 | srcptr
[2];
244 *bufptr
++ = EncodeMap
[val
>> 18 ];
245 *bufptr
++ = EncodeMap
[val
>> 12 & 0x3f];
246 *bufptr
++ = EncodeMap
[val
>> 6 & 0x3f];
247 *bufptr
++ = EncodeMap
[val
& 0x3f];
253 immutable val
= srcptr
[0] << 16 |
(remain
== 2 ? srcptr
[1] << 8 : 0);
254 *bufptr
++ = EncodeMap
[val
>> 18 ];
255 *bufptr
++ = EncodeMap
[val
>> 12 & 0x3f];
257 final switch (remain
)
260 *bufptr
++ = EncodeMap
[val
>> 6 & 0x3f];
261 static if (Padding
!= NoPadding
)
265 static if (Padding
!= NoPadding
)
274 // encode method can't assume buffer length. So, slice needed.
275 return buffer
[0 .. bufptr
- buffer
.ptr
];
281 ubyte[] data
= [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
282 char[32] buffer
; // much bigger than necessary
284 // Just to be sure...
285 auto encodedLength
= Base64
.encodeLength(data
.length
);
286 assert(buffer
.length
>= encodedLength
);
288 // encode() returns a slice to the provided buffer.
289 auto encoded
= Base64
.encode(data
, buffer
[]);
290 assert(encoded
is buffer
[0 .. encodedLength
]);
291 assert(encoded
== "g9cwegE/");
295 // InputRange to char[]
301 char[] encode(R1
, R2
)(R1 source
, R2 buffer
) if (!isArray
!R1
&& isInputRange
!R1
&&
302 is(ElementType
!R1
: ubyte) && hasLength
!R1
&&
306 assert(buffer
.length
>= encodeLength(source
.length
), "Insufficient buffer for encoding");
310 // @@@BUG@@@ D's DbC can't caputre an argument of function and store the result of precondition.
311 //assert(result.length == encodeLength(source.length), "The length of result is different from Base64");
315 immutable srcLen
= source
.length
;
319 immutable blocks
= srcLen
/ 3;
320 immutable remain
= srcLen
% 3;
321 auto bufptr
= buffer
.ptr
;
323 foreach (Unused
; 0 .. blocks
)
325 immutable v1
= source
.front
; source
.popFront();
326 immutable v2
= source
.front
; source
.popFront();
327 immutable v3
= source
.front
; source
.popFront();
328 immutable val
= v1
<< 16 | v2
<< 8 | v3
;
329 *bufptr
++ = EncodeMap
[val
>> 18 ];
330 *bufptr
++ = EncodeMap
[val
>> 12 & 0x3f];
331 *bufptr
++ = EncodeMap
[val
>> 6 & 0x3f];
332 *bufptr
++ = EncodeMap
[val
& 0x3f];
337 size_t val
= source
.front
<< 16;
341 val |
= source
.front
<< 8;
344 *bufptr
++ = EncodeMap
[val
>> 18 ];
345 *bufptr
++ = EncodeMap
[val
>> 12 & 0x3f];
347 final switch (remain
)
350 *bufptr
++ = EncodeMap
[val
>> 6 & 0x3f];
351 static if (Padding
!= NoPadding
)
355 static if (Padding
!= NoPadding
)
364 // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'.
367 bufptr
- buffer
.ptr
== encodeLength(srcLen
),
368 "The length of result is different from Base64"
371 // encode method can't assume buffer length. So, slice needed.
372 return buffer
[0 .. bufptr
- buffer
.ptr
];
376 // ubyte[] to OutputRange
380 * Encodes $(D_PARAM source) into an
381 * $(LINK2 std_range_primitives.html#isOutputRange, output range) using
385 * source = The $(LINK2 std_range_primitives.html#isInputRange, input
387 * range = The $(LINK2 std_range_primitives.html#isOutputRange, output
388 * range) to store the encoded result.
391 * The number of times the output range's $(D put) method was invoked.
393 size_t
encode(R1
, R2
)(in R1 source
, auto ref R2 range
)
394 if (isArray
!R1
&& is(ElementType
!R1
: ubyte) &&
395 !is(R2
== char[]) && isOutputRange
!(R2
, char))
398 assert(result
== encodeLength(source
.length
), "The number of put is different from the length of Base64");
402 immutable srcLen
= source
.length
;
406 immutable blocks
= srcLen
/ 3;
407 immutable remain
= srcLen
% 3;
408 auto srcptr
= source
.ptr
;
411 foreach (Unused
; 0 .. blocks
)
413 immutable val
= srcptr
[0] << 16 | srcptr
[1] << 8 | srcptr
[2];
414 put(range
, EncodeMap
[val
>> 18 ]);
415 put(range
, EncodeMap
[val
>> 12 & 0x3f]);
416 put(range
, EncodeMap
[val
>> 6 & 0x3f]);
417 put(range
, EncodeMap
[val
& 0x3f]);
424 immutable val
= srcptr
[0] << 16 |
(remain
== 2 ? srcptr
[1] << 8 : 0);
425 put(range
, EncodeMap
[val
>> 18 ]);
426 put(range
, EncodeMap
[val
>> 12 & 0x3f]);
429 final switch (remain
)
432 put(range
, EncodeMap
[val
>> 6 & 0x3f]);
435 static if (Padding
!= NoPadding
)
442 static if (Padding
!= NoPadding
)
458 // @system because encode for OutputRange is @system
462 void put(const(char) ch
) @safe { result
~= ch
; }
465 ubyte[] data
= [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
467 // This overload of encode() returns the number of calls to the output
468 // range's put method.
470 assert(Base64
.encode(data
, output
) == 8);
471 assert(output
.result
== "Gis8TV1u");
475 // InputRange to OutputRange
481 size_t
encode(R1
, R2
)(R1 source
, auto ref R2 range
)
482 if (!isArray
!R1
&& isInputRange
!R1
&& is(ElementType
!R1
: ubyte) &&
483 hasLength
!R1
&& !is(R2
== char[]) && isOutputRange
!(R2
, char))
486 // @@@BUG@@@ Workaround for DbC problem.
487 //assert(result == encodeLength(source.length), "The number of put is different from the length of Base64");
491 immutable srcLen
= source
.length
;
495 immutable blocks
= srcLen
/ 3;
496 immutable remain
= srcLen
% 3;
499 foreach (Unused
; 0 .. blocks
)
501 immutable v1
= source
.front
; source
.popFront();
502 immutable v2
= source
.front
; source
.popFront();
503 immutable v3
= source
.front
; source
.popFront();
504 immutable val
= v1
<< 16 | v2
<< 8 | v3
;
505 put(range
, EncodeMap
[val
>> 18 ]);
506 put(range
, EncodeMap
[val
>> 12 & 0x3f]);
507 put(range
, EncodeMap
[val
>> 6 & 0x3f]);
508 put(range
, EncodeMap
[val
& 0x3f]);
514 size_t val
= source
.front
<< 16;
518 val |
= source
.front
<< 8;
521 put(range
, EncodeMap
[val
>> 18 ]);
522 put(range
, EncodeMap
[val
>> 12 & 0x3f]);
525 final switch (remain
)
528 put(range
, EncodeMap
[val
>> 6 & 0x3f]);
531 static if (Padding
!= NoPadding
)
538 static if (Padding
!= NoPadding
)
548 // @@@BUG@@@ Workaround for DbC problem.
551 pcount
== encodeLength(srcLen
),
552 "The number of put is different from the length of Base64"
560 * Encodes $(D_PARAM source) to newly-allocated buffer.
562 * This convenience method alleviates the need to manually manage output
566 * source = The $(LINK2 std_range_primitives.html#isInputRange, input
570 * A newly-allocated $(D char[]) buffer containing the encoded string.
573 pure char[] encode(Range
)(Range source
) if (isArray
!Range
&& is(ElementType
!Range
: ubyte))
575 return encode(source
, new char[encodeLength(source
.length
)]);
581 ubyte[] data
= [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
582 assert(Base64
.encode(data
) == "Gis8TV1u");
589 char[] encode(Range
)(Range source
) if (!isArray
!Range
&& isInputRange
!Range
&&
590 is(ElementType
!Range
: ubyte) && hasLength
!Range
)
592 return encode(source
, new char[encodeLength(source
.length
)]);
597 * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
598 * iterates over the respective Base64 encodings of a range of data items.
600 * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
601 * forward range) if the underlying data source is at least a forward
604 * Note: This struct is not intended to be created in user code directly;
605 * use the $(LREF encoder) function instead.
607 struct Encoder(Range
) if (isInputRange
!Range
&& (is(ElementType
!Range
: const(ubyte)[]) ||
608 is(ElementType
!Range
: const(char)[])))
612 char[] buffer_
, encoded_
;
625 * true if there is no more encoded data left.
635 * Returns: The current chunk of encoded data.
638 nothrow char[] front()
645 * Advance the range to the next chunk of encoded data.
648 * $(D Base64Exception) If invoked when
649 * $(LREF2 .Base64Impl.Encoder.empty, empty) returns $(D true).
653 enforce(!empty
, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
658 * This check is very ugly. I think this is a Range's flaw.
659 * I very strongly want the Range guideline for unified implementation.
661 * In this case, Encoder becomes a beautiful implementation if 'front' performs Base64 encoding.
668 static if (isForwardRange
!Range
)
671 * Save the current iteration state of the range.
673 * This method is only available if the underlying range is a
674 * $(LINK2 std_range_primitives.html#isForwardRange, forward
678 * A copy of $(D this).
683 typeof(return) encoder
;
685 encoder
.range_
= range_
.save
;
686 encoder
.buffer_
= buffer_
.dup
;
687 encoder
.encoded_
= encoder
.buffer_
[0 .. encoded_
.length
];
697 auto data
= cast(const(ubyte)[])range_
.front
;
698 auto size
= encodeLength(data
.length
);
699 if (size
> buffer_
.length
)
700 buffer_
.length
= size
;
702 encoded_
= encode(data
, buffer_
);
708 * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
709 * iterates over the encoded bytes of the given source data.
711 * It will be a $(LINK2 std_range_primitives.html#isForwardRange, forward
712 * range) if the underlying data source is at least a forward range.
714 * Note: This struct is not intended to be created in user code directly;
715 * use the $(LREF encoder) function instead.
717 struct Encoder(Range
) if (isInputRange
!Range
&& is(ElementType
!Range
: ubyte))
729 static if (isForwardRange
!Range
)
730 range_
= range_
.save
;
741 * true if there are no more encoded characters to be iterated.
744 nothrow bool empty() const
746 static if (Padding
== NoPadding
)
749 return pos
< 0 && !padding
;
754 * Returns: The current encoded character.
757 nothrow ubyte front()
764 * Advance to the next encoded character.
767 * $(D Base64Exception) If invoked when $(LREF2 .Base64Impl.Encoder.empty.2,
768 * empty) returns $(D true).
772 enforce(!empty
, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
774 static if (Padding
!= NoPadding
)
792 first
= EncodeMap
[range_
.front
>> 2];
795 immutable t
= (range_
.front
& 0b11) << 4;
800 first
= EncodeMap
[t
];
805 first
= EncodeMap
[t |
(range_
.front
>> 4)];
809 immutable t
= (range_
.front
& 0b1111) << 2;
814 first
= EncodeMap
[t
];
819 first
= EncodeMap
[t |
(range_
.front
>> 6)];
823 first
= EncodeMap
[range_
.front
& 0b111111];
832 static if (isForwardRange
!Range
)
835 * Save the current iteration state of the range.
837 * This method is only available if the underlying range is a
838 * $(LINK2 std_range_primitives.html#isForwardRange, forward
842 * A copy of $(D this).
848 encoder
.range_
= encoder
.range_
.save
;
856 * Construct an $(D Encoder) that iterates over the Base64 encoding of the
857 * given $(LINK2 std_range_primitives.html#isInputRange, input range).
860 * range = An $(LINK2 std_range_primitives.html#isInputRange, input
861 * range) over the data to be encoded.
864 * If $(D_PARAM range) is a range of bytes, an $(D Encoder) that iterates
865 * over the bytes of the corresponding Base64 encoding.
867 * If $(D_PARAM range) is a range of ranges of bytes, an $(D Encoder) that
868 * iterates over the Base64 encoded strings of each element of the range.
870 * In both cases, the returned $(D Encoder) will be a
871 * $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the
872 * given $(D range) is at least a forward range, otherwise it will be only
876 * This example encodes the input one line at a time.
878 * File f = File("text.txt", "r");
879 * scope(exit) f.close();
882 * foreach (encoded; Base64.encoder(f.byLine()))
884 * writeln(++line, ". ", encoded);
889 * This example encodes the input data one byte at a time.
891 * ubyte[] data = cast(ubyte[]) "0123456789";
893 * // The ElementType of data is not aggregation type
894 * foreach (encoded; Base64.encoder(data))
900 Encoder
!(Range
) encoder(Range
)(Range range
) if (isInputRange
!Range
)
902 return typeof(return)(range
);
906 /* Decode functions */
909 private immutable int[char.max
+ 1] DecodeMap
= [
910 'A':0b000000, 'B':0b000001, 'C':0b000010, 'D':0b000011, 'E':0b000100,
911 'F':0b000101, 'G':0b000110, 'H':0b000111, 'I':0b001000, 'J':0b001001,
912 'K':0b001010, 'L':0b001011, 'M':0b001100, 'N':0b001101, 'O':0b001110,
913 'P':0b001111, 'Q':0b010000, 'R':0b010001, 'S':0b010010, 'T':0b010011,
914 'U':0b010100, 'V':0b010101, 'W':0b010110, 'X':0b010111, 'Y':0b011000,
915 'Z':0b011001, 'a':0b011010, 'b':0b011011, 'c':0b011100, 'd':0b011101,
916 'e':0b011110, 'f':0b011111, 'g':0b100000, 'h':0b100001, 'i':0b100010,
917 'j':0b100011, 'k':0b100100, 'l':0b100101, 'm':0b100110, 'n':0b100111,
918 'o':0b101000, 'p':0b101001, 'q':0b101010, 'r':0b101011, 's':0b101100,
919 't':0b101101, 'u':0b101110, 'v':0b101111, 'w':0b110000, 'x':0b110001,
920 'y':0b110010, 'z':0b110011, '0':0b110100, '1':0b110101, '2':0b110110,
921 '3':0b110111, '4':0b111000, '5':0b111001, '6':0b111010, '7':0b111011,
922 '8':0b111100, '9':0b111101, Map62th
:0b111110, Map63th
:0b111111, Padding
:-1
927 * Given a Base64 encoded string, calculates the length of the decoded
931 * sourceLength = The length of the Base64 encoding.
934 * The length of the decoded string corresponding to a Base64 encoding of
935 * length $(D_PARAM sourceLength).
938 pure nothrow size_t
decodeLength(in size_t sourceLength
)
940 static if (Padding
== NoPadding
)
941 return (sourceLength
/ 4) * 3 + (sourceLength
% 4 < 2 ?
0 : sourceLength
% 4 == 2 ?
1 : 2);
943 return (sourceLength
/ 4) * 3;
949 auto encoded
= "Gis8TV1u";
951 // Allocate a sufficiently large buffer to hold to decoded result.
952 auto buffer
= new ubyte[Base64
.decodeLength(encoded
.length
)];
954 Base64
.decode(encoded
, buffer
);
955 assert(buffer
== [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
959 // Used in decode contracts. Calculates the actual size the decoded
960 // result should have, taking into account trailing padding.
962 pure nothrow private size_t
realDecodeLength(R
)(R source
)
964 auto expect
= decodeLength(source
.length
);
965 static if (Padding
!= NoPadding
)
967 if (source
.length
% 4 == 0)
969 expect
-= source
.length
== 0 ?
0 :
970 source
[$ - 2] == Padding ?
2 :
971 source
[$ - 1] == Padding ?
1 : 0;
982 * Decodes $(D_PARAM source) into the given buffer.
985 * source = The $(LINK2 std_range_primitives.html#isInputRange, input
987 * buffer = The buffer to store decoded result.
990 * The slice of $(D_PARAM buffer) containing the decoded result.
993 * $(D Base64Exception) if $(D_PARAM source) contains characters outside the
994 * base alphabet of the current Base64 encoding scheme.
997 pure ubyte[] decode(R1
, R2
)(in R1 source
, R2 buffer
) if (isArray
!R1
&& is(ElementType
!R1
: dchar) &&
998 is(R2
== ubyte[]) && isOutputRange
!(R2
, ubyte))
1001 assert(buffer
.length
>= realDecodeLength(source
), "Insufficient buffer for decoding");
1005 immutable expect
= realDecodeLength(source
);
1006 assert(result
.length
== expect
, "The length of result is different from the expected length");
1010 immutable srcLen
= source
.length
;
1013 static if (Padding
!= NoPadding
)
1014 enforce(srcLen
% 4 == 0, new Base64Exception("Invalid length of encoded data"));
1016 immutable blocks
= srcLen
/ 4;
1017 auto srcptr
= source
.ptr
;
1018 auto bufptr
= buffer
.ptr
;
1020 foreach (Unused
; 0 .. blocks
)
1022 immutable v1
= decodeChar(*srcptr
++);
1023 immutable v2
= decodeChar(*srcptr
++);
1025 *bufptr
++ = cast(ubyte)(v1
<< 2 | v2
>> 4);
1027 immutable v3
= decodeChar(*srcptr
++);
1031 *bufptr
++ = cast(ubyte)((v2
<< 4 | v3
>> 2) & 0xff);
1033 immutable v4
= decodeChar(*srcptr
++);
1037 *bufptr
++ = cast(ubyte)((v3
<< 6 | v4
) & 0xff);
1040 static if (Padding
== NoPadding
)
1042 immutable remain
= srcLen
% 4;
1046 immutable v1
= decodeChar(*srcptr
++);
1047 immutable v2
= decodeChar(*srcptr
++);
1049 *bufptr
++ = cast(ubyte)(v1
<< 2 | v2
>> 4);
1052 *bufptr
++ = cast(ubyte)((v2
<< 4 |
decodeChar(*srcptr
++) >> 2) & 0xff);
1056 return buffer
[0 .. bufptr
- buffer
.ptr
];
1062 auto encoded
= "Gis8TV1u";
1063 ubyte[32] buffer
; // much bigger than necessary
1065 // Just to be sure...
1066 auto decodedLength
= Base64
.decodeLength(encoded
.length
);
1067 assert(buffer
.length
>= decodedLength
);
1069 // decode() returns a slice of the given buffer.
1070 auto decoded
= Base64
.decode(encoded
, buffer
[]);
1071 assert(decoded
is buffer
[0 .. decodedLength
]);
1072 assert(decoded
== [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
1075 // InputRange to ubyte[]
1081 ubyte[] decode(R1
, R2
)(R1 source
, R2 buffer
) if (!isArray
!R1
&& isInputRange
!R1
&&
1082 is(ElementType
!R1
: dchar) && hasLength
!R1
&&
1083 is(R2
== ubyte[]) && isOutputRange
!(R2
, ubyte))
1086 assert(buffer
.length
>= decodeLength(source
.length
), "Insufficient buffer for decoding");
1090 // @@@BUG@@@ Workaround for DbC problem.
1091 //immutable expect = decodeLength(source.length) - 2;
1092 //assert(result.length >= expect, "The length of result is smaller than expected length");
1096 immutable srcLen
= source
.length
;
1099 static if (Padding
!= NoPadding
)
1100 enforce(srcLen
% 4 == 0, new Base64Exception("Invalid length of encoded data"));
1102 immutable blocks
= srcLen
/ 4;
1103 auto bufptr
= buffer
.ptr
;
1105 foreach (Unused
; 0 .. blocks
)
1107 immutable v1
= decodeChar(source
.front
); source
.popFront();
1108 immutable v2
= decodeChar(source
.front
); source
.popFront();
1110 *bufptr
++ = cast(ubyte)(v1
<< 2 | v2
>> 4);
1112 immutable v3
= decodeChar(source
.front
);
1116 *bufptr
++ = cast(ubyte)((v2
<< 4 | v3
>> 2) & 0xff);
1119 immutable v4
= decodeChar(source
.front
);
1123 *bufptr
++ = cast(ubyte)((v3
<< 6 | v4
) & 0xff);
1127 static if (Padding
== NoPadding
)
1129 immutable remain
= srcLen
% 4;
1133 immutable v1
= decodeChar(source
.front
); source
.popFront();
1134 immutable v2
= decodeChar(source
.front
);
1136 *bufptr
++ = cast(ubyte)(v1
<< 2 | v2
>> 4);
1141 *bufptr
++ = cast(ubyte)((v2
<< 4 |
decodeChar(source
.front
) >> 2) & 0xff);
1146 // @@@BUG@@@ Workaround for DbC problem.
1149 (bufptr
- buffer
.ptr
) >= (decodeLength(srcLen
) - 2),
1150 "The length of result is smaller than expected length"
1153 return buffer
[0 .. bufptr
- buffer
.ptr
];
1157 // char[] to OutputRange
1161 * Decodes $(D_PARAM source) into a given
1162 * $(LINK2 std_range_primitives.html#isOutputRange, output range).
1165 * source = The $(LINK2 std_range_primitives.html#isInputRange, input
1166 * range) to _decode.
1167 * range = The $(LINK2 std_range_primitives.html#isOutputRange, output
1168 * range) to store the decoded result.
1171 * The number of times the output range's $(D put) method was invoked.
1174 * $(D Base64Exception) if $(D_PARAM source) contains characters outside the
1175 * base alphabet of the current Base64 encoding scheme.
1177 size_t
decode(R1
, R2
)(in R1 source
, auto ref R2 range
)
1178 if (isArray
!R1
&& is(ElementType
!R1
: dchar) &&
1179 !is(R2
== ubyte[]) && isOutputRange
!(R2
, ubyte))
1182 immutable expect
= realDecodeLength(source
);
1183 assert(result
== expect
, "The result of decode is different from the expected");
1187 immutable srcLen
= source
.length
;
1190 static if (Padding
!= NoPadding
)
1191 enforce(srcLen
% 4 == 0, new Base64Exception("Invalid length of encoded data"));
1193 immutable blocks
= srcLen
/ 4;
1194 auto srcptr
= source
.ptr
;
1197 foreach (Unused
; 0 .. blocks
)
1199 immutable v1
= decodeChar(*srcptr
++);
1200 immutable v2
= decodeChar(*srcptr
++);
1202 put(range
, cast(ubyte)(v1
<< 2 | v2
>> 4));
1205 immutable v3
= decodeChar(*srcptr
++);
1209 put(range
, cast(ubyte)((v2
<< 4 | v3
>> 2) & 0xff));
1212 immutable v4
= decodeChar(*srcptr
++);
1216 put(range
, cast(ubyte)((v3
<< 6 | v4
) & 0xff));
1220 static if (Padding
== NoPadding
)
1222 immutable remain
= srcLen
% 4;
1226 immutable v1
= decodeChar(*srcptr
++);
1227 immutable v2
= decodeChar(*srcptr
++);
1229 put(range
, cast(ubyte)(v1
<< 2 | v2
>> 4));
1234 put(range
, cast(ubyte)((v2
<< 4 |
decodeChar(*srcptr
++) >> 2) & 0xff));
1249 void put(ubyte b
) { result
~= b
; }
1253 // This overload of decode() returns the number of calls to put().
1254 assert(Base64
.decode("Gis8TV1u", output
) == 6);
1255 assert(output
.result
== [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
1259 // InputRange to OutputRange
1265 size_t
decode(R1
, R2
)(R1 source
, auto ref R2 range
)
1266 if (!isArray
!R1
&& isInputRange
!R1
&& is(ElementType
!R1
: dchar) &&
1267 hasLength
!R1
&& !is(R2
== ubyte[]) && isOutputRange
!(R2
, ubyte))
1270 // @@@BUG@@@ Workaround for DbC problem.
1271 //immutable expect = decodeLength(source.length) - 2;
1272 //assert(result >= expect, "The length of result is smaller than expected length");
1276 immutable srcLen
= source
.length
;
1279 static if (Padding
!= NoPadding
)
1280 enforce(srcLen
% 4 == 0, new Base64Exception("Invalid length of encoded data"));
1282 immutable blocks
= srcLen
/ 4;
1285 foreach (Unused
; 0 .. blocks
)
1287 immutable v1
= decodeChar(source
.front
); source
.popFront();
1288 immutable v2
= decodeChar(source
.front
); source
.popFront();
1290 put(range
, cast(ubyte)(v1
<< 2 | v2
>> 4));
1293 immutable v3
= decodeChar(source
.front
);
1297 put(range
, cast(ubyte)((v2
<< 4 | v3
>> 2) & 0xff));
1301 immutable v4
= decodeChar(source
.front
);
1305 put(range
, cast(ubyte)((v3
<< 6 | v4
) & 0xff));
1310 static if (Padding
== NoPadding
)
1312 immutable remain
= srcLen
% 4;
1316 immutable v1
= decodeChar(source
.front
); source
.popFront();
1317 immutable v2
= decodeChar(source
.front
);
1319 put(range
, cast(ubyte)(v1
<< 2 | v2
>> 4));
1325 put(range
, cast(ubyte)((v2
<< 4 |
decodeChar(source
.front
) >> 2) & 0xff));
1331 // @@@BUG@@@ Workaround for DbC problem.
1334 pcount
>= (decodeLength(srcLen
) - 2),
1335 "The length of result is smaller than expected length"
1343 * Decodes $(D_PARAM source) into newly-allocated buffer.
1345 * This convenience method alleviates the need to manually manage decoding
1349 * source = The $(LINK2 std_range_primitives.html#isInputRange, input
1350 * range) to _decode.
1353 * A newly-allocated $(D ubyte[]) buffer containing the decoded string.
1356 pure ubyte[] decode(Range
)(Range source
) if (isArray
!Range
&& is(ElementType
!Range
: dchar))
1358 return decode(source
, new ubyte[decodeLength(source
.length
)]);
1364 auto data
= "Gis8TV1u";
1365 assert(Base64
.decode(data
) == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
1372 ubyte[] decode(Range
)(Range source
) if (!isArray
!Range
&& isInputRange
!Range
&&
1373 is(ElementType
!Range
: dchar) && hasLength
!Range
)
1375 return decode(source
, new ubyte[decodeLength(source
.length
)]);
1380 * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
1381 * iterates over the decoded data of a range of Base64 encodings.
1383 * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
1384 * forward range) if the underlying data source is at least a forward
1387 * Note: This struct is not intended to be created in user code directly;
1388 * use the $(LREF decoder) function instead.
1390 struct Decoder(Range
) if (isInputRange
!Range
&& (is(ElementType
!Range
: const(char)[]) ||
1391 is(ElementType
!Range
: const(ubyte)[])))
1395 ubyte[] buffer_
, decoded_
;
1408 * true if there are no more elements to be iterated.
1413 return range_
.empty
;
1418 * Returns: The decoding of the current element in the input.
1421 nothrow ubyte[] front()
1428 * Advance to the next element in the input to be decoded.
1431 * $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty,
1432 * empty) returns $(D true).
1436 enforce(!empty
, new Base64Exception("Cannot call popFront on Decoder with no data remaining."));
1441 * I mentioned Encoder's popFront.
1448 static if (isForwardRange
!Range
)
1451 * Saves the current iteration state.
1453 * This method is only available if the underlying range is a
1454 * $(LINK2 std_range_primitives.html#isForwardRange, forward
1457 * Returns: A copy of $(D this).
1462 typeof(return) decoder
;
1464 decoder
.range_
= range_
.save
;
1465 decoder
.buffer_
= buffer_
.dup
;
1466 decoder
.decoded_
= decoder
.buffer_
[0 .. decoded_
.length
];
1476 auto data
= cast(const(char)[])range_
.front
;
1478 static if (Padding
== NoPadding
)
1480 while (data
.length
% 4 == 1)
1483 data
~= cast(const(char)[])range_
.front
;
1488 while (data
.length
% 4 != 0)
1491 data
~= cast(const(char)[])range_
.front
;
1495 auto size
= decodeLength(data
.length
);
1496 if (size
> buffer_
.length
)
1497 buffer_
.length
= size
;
1499 decoded_
= decode(data
, buffer_
);
1505 * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
1506 * iterates over the bytes of data decoded from a Base64 encoded string.
1508 * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
1509 * forward range) if the underlying data source is at least a forward
1512 * Note: This struct is not intended to be created in user code directly;
1513 * use the $(LREF decoder) function instead.
1515 struct Decoder(Range
) if (isInputRange
!Range
&& is(ElementType
!Range
: char))
1527 static if (isForwardRange
!Range
)
1528 range_
= range_
.save
;
1530 static if (Padding
!= NoPadding
&& hasLength
!Range
)
1531 enforce(range_
.length
% 4 == 0, new Base64Exception("Invalid length of encoded data"));
1542 * true if there are no more elements to be iterated.
1545 nothrow bool empty() const
1552 * Returns: The current decoded byte.
1555 nothrow ubyte front()
1562 * Advance to the next decoded byte.
1565 * $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty,
1566 * empty) returns $(D true).
1570 enforce(!empty
, new Base64Exception("Cannot call popFront on Decoder with no data remaining"));
1572 static if (Padding
== NoPadding
)
1576 return range_
.empty
;
1583 enforce(!range_
.empty
, new Base64Exception("Missing padding"));
1584 return range_
.front
== Padding
;
1588 if (range_
.empty || range_
.front
== Padding
)
1597 enforce(!endCondition(), new Base64Exception("Premature end of data found"));
1599 immutable t
= DecodeMap
[range_
.front
] << 2;
1602 enforce(!endCondition(), new Base64Exception("Premature end of data found"));
1603 first
= cast(ubyte)(t |
(DecodeMap
[range_
.front
] >> 4));
1606 immutable t
= (DecodeMap
[range_
.front
] & 0b1111) << 4;
1616 first
= cast(ubyte)(t |
(DecodeMap
[range_
.front
] >> 2));
1620 immutable t
= (DecodeMap
[range_
.front
] & 0b11) << 6;
1630 first
= cast(ubyte)(t | DecodeMap
[range_
.front
]);
1641 static if (isForwardRange
!Range
)
1644 * Saves the current iteration state.
1646 * This method is only available if the underlying range is a
1647 * $(LINK2 std_range_primitives.html#isForwardRange, forward
1650 * Returns: A copy of $(D this).
1655 auto decoder
= this;
1656 decoder
.range_
= decoder
.range_
.save
;
1664 * Construct a $(D Decoder) that iterates over the decoding of the given
1665 * Base64 encoded data.
1668 * range = An $(LINK2 std_range_primitives.html#isInputRange, input
1669 * range) over the data to be decoded.
1672 * If $(D_PARAM range) is a range of characters, a $(D Decoder) that
1673 * iterates over the bytes of the corresponding Base64 decoding.
1675 * If $(D_PARAM range) is a range of ranges of characters, a $(D Decoder)
1676 * that iterates over the decoded strings corresponding to each element of
1677 * the range. In this case, the length of each subrange must be a multiple
1678 * of 4; the returned _decoder does not keep track of Base64 decoding
1679 * state across subrange boundaries.
1681 * In both cases, the returned $(D Decoder) will be a
1682 * $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the
1683 * given $(D range) is at least a forward range, otherwise it will be only
1686 * If the input data contains characters not found in the base alphabet of
1687 * the current Base64 encoding scheme, the returned range may throw a
1688 * $(D Base64Exception).
1691 * This example shows decoding over a range of input data lines.
1693 * foreach (decoded; Base64.decoder(stdin.byLine()))
1700 * This example shows decoding one byte at a time.
1702 * auto encoded = Base64.encoder(cast(ubyte[])"0123456789");
1703 * foreach (n; map!q{a - '0'}(Base64.decoder(encoded)))
1709 Decoder
!(Range
) decoder(Range
)(Range range
) if (isInputRange
!Range
)
1711 return typeof(return)(range
);
1717 pure int decodeChar()(char chr
)
1719 immutable val
= DecodeMap
[chr
];
1721 // enforce can't be a pure function, so I use trivial check.
1722 if (val
== 0 && chr
!= 'A')
1723 throw new Base64Exception("Invalid character: " ~ chr
);
1730 pure int decodeChar()(dchar chr
)
1732 // See above comment.
1734 throw new Base64Exception("Base64-encoded character must be a single byte");
1736 return decodeChar(cast(char) chr
);
1743 import std
.string
: representation
;
1745 // pre-defined: alias Base64 = Base64Impl!('+', '/');
1747 assert(Base64
.encode(emptyArr
) == "");
1748 assert(Base64
.encode("f".representation
) == "Zg==");
1749 assert(Base64
.encode("foo".representation
) == "Zm9v");
1751 alias Base64Re
= Base64Impl
!('!', '=', Base64
.NoPadding
);
1752 assert(Base64Re
.encode("f".representation
) == "Zg");
1753 assert(Base64Re
.encode("foo".representation
) == "Zm9v");
1757 * Exception thrown upon encountering Base64 encoding or decoding errors.
1759 class Base64Exception
: Exception
1762 this(string s
, string fn
= __FILE__
, size_t ln
= __LINE__
)
1771 import std
.exception
: assertThrown
;
1772 assertThrown
!Base64Exception(Base64
.decode("ab|c"));
1778 import std
.algorithm
.comparison
: equal
;
1779 import std
.algorithm
.sorting
: sort
;
1784 alias Base64Re
= Base64Impl
!('!', '=', Base64
.NoPadding
);
1786 // Test vectors from RFC 4648
1787 ubyte[][string
] tv
= [
1788 "" :cast(ubyte[])"",
1789 "f" :cast(ubyte[])"f",
1790 "fo" :cast(ubyte[])"fo",
1791 "foo" :cast(ubyte[])"foo",
1792 "foob" :cast(ubyte[])"foob",
1793 "fooba" :cast(ubyte[])"fooba",
1794 "foobar":cast(ubyte[])"foobar"
1799 assert(Base64
.encodeLength(tv
[""].length
) == 0);
1800 assert(Base64
.encodeLength(tv
["f"].length
) == 4);
1801 assert(Base64
.encodeLength(tv
["fo"].length
) == 4);
1802 assert(Base64
.encodeLength(tv
["foo"].length
) == 4);
1803 assert(Base64
.encodeLength(tv
["foob"].length
) == 8);
1804 assert(Base64
.encodeLength(tv
["fooba"].length
) == 8);
1805 assert(Base64
.encodeLength(tv
["foobar"].length
) == 8);
1807 assert(Base64
.encode(tv
[""]) == "");
1808 assert(Base64
.encode(tv
["f"]) == "Zg==");
1809 assert(Base64
.encode(tv
["fo"]) == "Zm8=");
1810 assert(Base64
.encode(tv
["foo"]) == "Zm9v");
1811 assert(Base64
.encode(tv
["foob"]) == "Zm9vYg==");
1812 assert(Base64
.encode(tv
["fooba"]) == "Zm9vYmE=");
1813 assert(Base64
.encode(tv
["foobar"]) == "Zm9vYmFy");
1816 assert(Base64
.decodeLength(Base64
.encode(tv
[""]).length
) == 0);
1817 assert(Base64
.decodeLength(Base64
.encode(tv
["f"]).length
) == 3);
1818 assert(Base64
.decodeLength(Base64
.encode(tv
["fo"]).length
) == 3);
1819 assert(Base64
.decodeLength(Base64
.encode(tv
["foo"]).length
) == 3);
1820 assert(Base64
.decodeLength(Base64
.encode(tv
["foob"]).length
) == 6);
1821 assert(Base64
.decodeLength(Base64
.encode(tv
["fooba"]).length
) == 6);
1822 assert(Base64
.decodeLength(Base64
.encode(tv
["foobar"]).length
) == 6);
1824 assert(Base64
.decode(Base64
.encode(tv
[""])) == tv
[""]);
1825 assert(Base64
.decode(Base64
.encode(tv
["f"])) == tv
["f"]);
1826 assert(Base64
.decode(Base64
.encode(tv
["fo"])) == tv
["fo"]);
1827 assert(Base64
.decode(Base64
.encode(tv
["foo"])) == tv
["foo"]);
1828 assert(Base64
.decode(Base64
.encode(tv
["foob"])) == tv
["foob"]);
1829 assert(Base64
.decode(Base64
.encode(tv
["fooba"])) == tv
["fooba"]);
1830 assert(Base64
.decode(Base64
.encode(tv
["foobar"])) == tv
["foobar"]);
1832 assertThrown
!Base64Exception(Base64
.decode("ab|c"));
1834 // Test decoding incomplete strings. RFC does not specify the correct
1835 // behavior, but the code should never throw Errors on invalid input.
1837 // decodeLength is nothrow
1838 assert(Base64
.decodeLength(1) == 0);
1839 assert(Base64
.decodeLength(2) <= 1);
1840 assert(Base64
.decodeLength(3) <= 2);
1842 // may throw Exceptions, may not throw Errors
1843 assertThrown
!Base64Exception(Base64
.decode("Zg"));
1844 assertThrown
!Base64Exception(Base64
.decode("Zg="));
1845 assertThrown
!Base64Exception(Base64
.decode("Zm8"));
1846 assertThrown
!Base64Exception(Base64
.decode("Zg==;"));
1851 assert(Base64Re
.encodeLength(tv
[""].length
) == 0);
1852 assert(Base64Re
.encodeLength(tv
["f"].length
) == 2);
1853 assert(Base64Re
.encodeLength(tv
["fo"].length
) == 3);
1854 assert(Base64Re
.encodeLength(tv
["foo"].length
) == 4);
1855 assert(Base64Re
.encodeLength(tv
["foob"].length
) == 6);
1856 assert(Base64Re
.encodeLength(tv
["fooba"].length
) == 7);
1857 assert(Base64Re
.encodeLength(tv
["foobar"].length
) == 8);
1859 assert(Base64Re
.encode(tv
[""]) == "");
1860 assert(Base64Re
.encode(tv
["f"]) == "Zg");
1861 assert(Base64Re
.encode(tv
["fo"]) == "Zm8");
1862 assert(Base64Re
.encode(tv
["foo"]) == "Zm9v");
1863 assert(Base64Re
.encode(tv
["foob"]) == "Zm9vYg");
1864 assert(Base64Re
.encode(tv
["fooba"]) == "Zm9vYmE");
1865 assert(Base64Re
.encode(tv
["foobar"]) == "Zm9vYmFy");
1868 assert(Base64Re
.decodeLength(Base64Re
.encode(tv
[""]).length
) == 0);
1869 assert(Base64Re
.decodeLength(Base64Re
.encode(tv
["f"]).length
) == 1);
1870 assert(Base64Re
.decodeLength(Base64Re
.encode(tv
["fo"]).length
) == 2);
1871 assert(Base64Re
.decodeLength(Base64Re
.encode(tv
["foo"]).length
) == 3);
1872 assert(Base64Re
.decodeLength(Base64Re
.encode(tv
["foob"]).length
) == 4);
1873 assert(Base64Re
.decodeLength(Base64Re
.encode(tv
["fooba"]).length
) == 5);
1874 assert(Base64Re
.decodeLength(Base64Re
.encode(tv
["foobar"]).length
) == 6);
1876 assert(Base64Re
.decode(Base64Re
.encode(tv
[""])) == tv
[""]);
1877 assert(Base64Re
.decode(Base64Re
.encode(tv
["f"])) == tv
["f"]);
1878 assert(Base64Re
.decode(Base64Re
.encode(tv
["fo"])) == tv
["fo"]);
1879 assert(Base64Re
.decode(Base64Re
.encode(tv
["foo"])) == tv
["foo"]);
1880 assert(Base64Re
.decode(Base64Re
.encode(tv
["foob"])) == tv
["foob"]);
1881 assert(Base64Re
.decode(Base64Re
.encode(tv
["fooba"])) == tv
["fooba"]);
1882 assert(Base64Re
.decode(Base64Re
.encode(tv
["foobar"])) == tv
["foobar"]);
1884 // decodeLength is nothrow
1885 assert(Base64
.decodeLength(1) == 0);
1888 { // with OutputRange
1891 auto a
= Appender
!(char[])([]);
1892 auto b
= Appender
!(ubyte[])([]);
1894 assert(Base64
.encode(tv
[""], a
) == 0);
1895 assert(Base64
.decode(a
.data
, b
) == 0);
1896 assert(tv
[""] == b
.data
); a
.clear(); b
.clear();
1898 assert(Base64
.encode(tv
["f"], a
) == 4);
1899 assert(Base64
.decode(a
.data
, b
) == 1);
1900 assert(tv
["f"] == b
.data
); a
.clear(); b
.clear();
1902 assert(Base64
.encode(tv
["fo"], a
) == 4);
1903 assert(Base64
.decode(a
.data
, b
) == 2);
1904 assert(tv
["fo"] == b
.data
); a
.clear(); b
.clear();
1906 assert(Base64
.encode(tv
["foo"], a
) == 4);
1907 assert(Base64
.decode(a
.data
, b
) == 3);
1908 assert(tv
["foo"] == b
.data
); a
.clear(); b
.clear();
1910 assert(Base64
.encode(tv
["foob"], a
) == 8);
1911 assert(Base64
.decode(a
.data
, b
) == 4);
1912 assert(tv
["foob"] == b
.data
); a
.clear(); b
.clear();
1914 assert(Base64
.encode(tv
["fooba"], a
) == 8);
1915 assert(Base64
.decode(a
.data
, b
) == 5);
1916 assert(tv
["fooba"] == b
.data
); a
.clear(); b
.clear();
1918 assert(Base64
.encode(tv
["foobar"], a
) == 8);
1919 assert(Base64
.decode(a
.data
, b
) == 6);
1920 assert(tv
["foobar"] == b
.data
); a
.clear(); b
.clear();
1923 // @@@9543@@@ These tests were disabled because they actually relied on the input range having length.
1924 // The implementation (currently) doesn't support encoding/decoding from a length-less source.
1926 { // with InputRange
1927 // InputRange to ubyte[] or char[]
1928 auto encoded
= Base64
.encode(map
!(to
!(ubyte))(["20", "251", "156", "3", "217", "126"]));
1929 assert(encoded
== "FPucA9l+");
1930 assert(Base64
.decode(map
!q
{a
}(encoded
)) == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
1932 // InputRange to OutputRange
1933 auto a
= Appender
!(char[])([]);
1934 auto b
= Appender
!(ubyte[])([]);
1935 assert(Base64
.encode(map
!(to
!(ubyte))(["20", "251", "156", "3", "217", "126"]), a
) == 8);
1936 assert(a
.data
== "FPucA9l+");
1937 assert(Base64
.decode(map
!q
{a
}(a
.data
), b
) == 6);
1938 assert(b
.data
== [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
1941 { // Encoder and Decoder
1943 string encode_file
= std
.file
.deleteme
~ "-testingEncoder";
1944 std
.file
.write(encode_file
, "\nf\nfo\nfoo\nfoob\nfooba\nfoobar");
1946 auto witness
= ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"];
1947 auto f
= File(encode_file
);
1952 std
.file
.remove(encode_file
);
1956 foreach (encoded
; Base64
.encoder(f
.byLine()))
1957 assert(encoded
== witness
[i
++]);
1959 assert(i
== witness
.length
);
1963 string decode_file
= std
.file
.deleteme
~ "-testingDecoder";
1964 std
.file
.write(decode_file
, "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy");
1966 auto witness
= sort(tv
.keys
);
1967 auto f
= File(decode_file
);
1972 std
.file
.remove(decode_file
);
1976 foreach (decoded
; Base64
.decoder(f
.byLine()))
1977 assert(decoded
== witness
[i
++]);
1979 assert(i
== witness
.length
);
1984 auto encoder
= Base64
.encoder(sort(tv
.values
));
1985 auto witness
= ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"];
1988 assert(encoder
.front
== witness
[i
++]); encoder
.popFront();
1989 assert(encoder
.front
== witness
[i
++]); encoder
.popFront();
1990 assert(encoder
.front
== witness
[i
++]); encoder
.popFront();
1992 foreach (encoded
; encoder
.save
)
1993 assert(encoded
== witness
[i
++]);
1997 auto decoder
= Base64
.decoder(["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]);
1998 auto witness
= sort(tv
.values
);
2001 assert(decoder
.front
== witness
[i
++]); decoder
.popFront();
2002 assert(decoder
.front
== witness
[i
++]); decoder
.popFront();
2003 assert(decoder
.front
== witness
[i
++]); decoder
.popFront();
2005 foreach (decoded
; decoder
.save
)
2006 assert(decoded
== witness
[i
++]);
2011 { // Encoder and Decoder for single character encoding and decoding
2012 alias Base64NoPadding
= Base64Impl
!('+', '/', Base64
.NoPadding
);
2015 "" : ["", "", "", ""],
2016 "f" : ["Zg==", "Zg==", "Zg", "Zg"],
2017 "fo" : ["Zm8=", "Zm8=", "Zm8", "Zm8"],
2018 "foo" : ["Zm9v", "Zm9v", "Zm9v", "Zm9v"],
2019 "foob" : ["Zm9vYg==", "Zm9vYg==", "Zm9vYg", "Zm9vYg"],
2020 "fooba" : ["Zm9vYmE=", "Zm9vYmE=", "Zm9vYmE", "Zm9vYmE"],
2021 "foobar" : ["Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy"],
2024 foreach (u
, e
; tests
)
2026 assert(equal(Base64
.encoder(cast(ubyte[]) u
), e
[0]));
2027 assert(equal(Base64
.decoder(Base64
.encoder(cast(ubyte[]) u
)), u
));
2029 assert(equal(Base64URL
.encoder(cast(ubyte[]) u
), e
[1]));
2030 assert(equal(Base64URL
.decoder(Base64URL
.encoder(cast(ubyte[]) u
)), u
));
2032 assert(equal(Base64NoPadding
.encoder(cast(ubyte[]) u
), e
[2]));
2033 assert(equal(Base64NoPadding
.decoder(Base64NoPadding
.encoder(cast(ubyte[]) u
)), u
));
2035 assert(equal(Base64Re
.encoder(cast(ubyte[]) u
), e
[3]));
2036 assert(equal(Base64Re
.decoder(Base64Re
.encoder(cast(ubyte[]) u
)), u
));
2041 // Regression control for the output range ref bug in encode.
2046 ubyte[] impl
= [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
2047 @property bool empty() { return impl
.length
== 0; }
2048 @property ubyte front() { return impl
[0]; }
2049 void popFront() { impl
= impl
[1 .. $]; }
2050 @property size_t
length() { return impl
.length
; }
2056 void put(char b
) { result
~= b
; }
2061 assert(Base64
.encode(ir
, or) == 8);
2062 assert(or.result
== "Gis8TV1u");
2064 // Verify that any existing workaround that uses & still works.
2067 assert(Base64
.encode(ir2
, &or2
) == 8);
2068 assert(or2
.result
== "Gis8TV1u");
2071 // Regression control for the output range ref bug in decode.
2076 const(char)[] impl
= "Gis8TV1u";
2077 @property bool empty() { return impl
.length
== 0; }
2078 @property dchar front() { return impl
[0]; }
2079 void popFront() { impl
= impl
[1 .. $]; }
2080 @property size_t
length() { return impl
.length
; }
2086 void put(ubyte b
) { result
~= b
; }
2091 assert(Base64
.decode(ir
, or) == 6);
2092 assert(or.result
== [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
2094 // Verify that any existing workaround that uses & still works.
2097 assert(Base64
.decode(ir2
, &or2
) == 6);
2098 assert(or2
.result
== [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);