|  | // Written in the D programming language. | 
|  |  | 
|  | /** | 
|  | * Support for Base64 encoding and decoding. | 
|  | * | 
|  | * This module provides two default implementations of Base64 encoding, | 
|  | * $(LREF Base64) with a standard encoding alphabet, and a variant | 
|  | * $(LREF Base64URL) that has a modified encoding alphabet designed to be | 
|  | * safe for embedding in URLs and filenames. | 
|  | * | 
|  | * Both variants are implemented as instantiations of the template | 
|  | * $(LREF Base64Impl). Most users will not need to use this template | 
|  | * directly; however, it can be used to create customized Base64 encodings, | 
|  | * such as one that omits padding characters, or one that is safe to embed | 
|  | * inside a regular expression. | 
|  | * | 
|  | * Example: | 
|  | * ----- | 
|  | * ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]; | 
|  | * | 
|  | * const(char)[] encoded = Base64.encode(data); | 
|  | * assert(encoded == "FPucA9l+"); | 
|  | * | 
|  | * ubyte[] decoded = Base64.decode("FPucA9l+"); | 
|  | * assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); | 
|  | * ----- | 
|  | * | 
|  | * The range API is supported for both encoding and decoding: | 
|  | * | 
|  | * Example: | 
|  | * ----- | 
|  | * // Create MIME Base64 with CRLF, per line 76. | 
|  | * File f = File("./text.txt", "r"); | 
|  | * scope(exit) f.close(); | 
|  | * | 
|  | * Appender!string mime64 = appender!string; | 
|  | * | 
|  | * foreach (encoded; Base64.encoder(f.byChunk(57))) | 
|  | * { | 
|  | *     mime64.put(encoded); | 
|  | *     mime64.put("\r\n"); | 
|  | * } | 
|  | * | 
|  | * writeln(mime64.data); | 
|  | * ----- | 
|  | * | 
|  | * References: | 
|  | * $(LINK2 https://tools.ietf.org/html/rfc4648, RFC 4648 - The Base16, Base32, and Base64 | 
|  | * Data Encodings) | 
|  | * | 
|  | * Copyright: Masahiro Nakagawa 2010-. | 
|  | * License:   $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). | 
|  | * Authors:   Masahiro Nakagawa, Daniel Murphy (Single value Encoder and Decoder) | 
|  | * Source:    $(PHOBOSSRC std/_base64.d) | 
|  | * Macros: | 
|  | *      LREF2=<a href="#$1">$(D $2)</a> | 
|  | */ | 
|  | module std.base64; | 
|  |  | 
|  | import std.exception;  // enforce | 
|  | import std.range.primitives;      // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength | 
|  | import std.traits;     // isArray | 
|  |  | 
|  | // Make sure module header code examples work correctly. | 
|  | @safe unittest | 
|  | { | 
|  | ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]; | 
|  |  | 
|  | const(char)[] encoded = Base64.encode(data); | 
|  | assert(encoded == "FPucA9l+"); | 
|  |  | 
|  | ubyte[] decoded = Base64.decode("FPucA9l+"); | 
|  | assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Implementation of standard _Base64 encoding. | 
|  | * | 
|  | * See $(LREF Base64Impl) for a description of available methods. | 
|  | */ | 
|  | alias Base64 = Base64Impl!('+', '/'); | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; | 
|  | assert(Base64.encode(data) == "g9cwegE/"); | 
|  | assert(Base64.decode("g9cwegE/") == data); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Variation of Base64 encoding that is safe for use in URLs and filenames. | 
|  | * | 
|  | * See $(LREF Base64Impl) for a description of available methods. | 
|  | */ | 
|  | alias Base64URL = Base64Impl!('-', '_'); | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; | 
|  | assert(Base64URL.encode(data) == "g9cwegE_"); | 
|  | assert(Base64URL.decode("g9cwegE_") == data); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Unpadded variation of Base64 encoding that is safe for use in URLs and | 
|  | * filenames, as used in RFCs 4648 and 7515 (JWS/JWT/JWE). | 
|  | * | 
|  | * See $(LREF Base64Impl) for a description of available methods. | 
|  | */ | 
|  | alias Base64URLNoPadding = Base64Impl!('-', '_', Base64.NoPadding); | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | ubyte[] data = [0x83, 0xd7, 0x30, 0x7b, 0xef]; | 
|  | assert(Base64URLNoPadding.encode(data) == "g9cwe-8"); | 
|  | assert(Base64URLNoPadding.decode("g9cwe-8") == data); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Template for implementing Base64 encoding and decoding. | 
|  | * | 
|  | * For most purposes, direct usage of this template is not necessary; instead, | 
|  | * this module provides default implementations: $(LREF Base64), implementing | 
|  | * basic Base64 encoding, and $(LREF Base64URL) and $(LREF Base64URLNoPadding), | 
|  | * that implement the Base64 variant for use in URLs and filenames, with | 
|  | * and without padding, respectively. | 
|  | * | 
|  | * Customized Base64 encoding schemes can be implemented by instantiating this | 
|  | * template with the appropriate arguments. For example: | 
|  | * | 
|  | * ----- | 
|  | * // Non-standard Base64 format for embedding in regular expressions. | 
|  | * alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); | 
|  | * ----- | 
|  | * | 
|  | * NOTE: | 
|  | * Encoded strings will not have any padding if the $(D Padding) parameter is | 
|  | * set to $(D NoPadding). | 
|  | */ | 
|  | template Base64Impl(char Map62th, char Map63th, char Padding = '=') | 
|  | { | 
|  | enum NoPadding = '\0';  /// represents no-padding encoding | 
|  |  | 
|  |  | 
|  | // Verify Base64 characters | 
|  | static assert(Map62th < 'A' || Map62th > 'Z', "Character '" ~ Map62th ~ "' cannot be used twice"); | 
|  | static assert(Map63th < 'A' || Map63th > 'Z', "Character '" ~ Map63th ~ "' cannot be used twice"); | 
|  | static assert(Padding < 'A' || Padding > 'Z', "Character '" ~ Padding ~ "' cannot be used twice"); | 
|  | static assert(Map62th < 'a' || Map62th > 'z', "Character '" ~ Map62th ~ "' cannot be used twice"); | 
|  | static assert(Map63th < 'a' || Map63th > 'z', "Character '" ~ Map63th ~ "' cannot be used twice"); | 
|  | static assert(Padding < 'a' || Padding > 'z', "Character '" ~ Padding ~ "' cannot be used twice"); | 
|  | static assert(Map62th < '0' || Map62th > '9', "Character '" ~ Map62th ~ "' cannot be used twice"); | 
|  | static assert(Map63th < '0' || Map63th > '9', "Character '" ~ Map63th ~ "' cannot be used twice"); | 
|  | static assert(Padding < '0' || Padding > '9', "Character '" ~ Padding ~ "' cannot be used twice"); | 
|  | static assert(Map62th != Map63th, "Character '" ~ Map63th ~ "' cannot be used twice"); | 
|  | static assert(Map62th != Padding, "Character '" ~ Padding ~ "' cannot be used twice"); | 
|  | static assert(Map63th != Padding, "Character '" ~ Padding ~ "' cannot be used twice"); | 
|  | static assert(Map62th != NoPadding, "'\\0' is not a valid Base64character"); | 
|  | static assert(Map63th != NoPadding, "'\\0' is not a valid Base64character"); | 
|  |  | 
|  |  | 
|  | /* Encode functions */ | 
|  |  | 
|  |  | 
|  | private immutable EncodeMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ~ Map62th ~ Map63th; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Calculates the length needed to store the encoded string corresponding | 
|  | * to an input of the given length. | 
|  | * | 
|  | * Params: | 
|  | *  sourceLength = Length of the source array. | 
|  | * | 
|  | * Returns: | 
|  | *  The length of a Base64 encoding of an array of the given length. | 
|  | */ | 
|  | @safe | 
|  | pure nothrow size_t encodeLength(in size_t sourceLength) | 
|  | { | 
|  | static if (Padding == NoPadding) | 
|  | return (sourceLength / 3) * 4 + (sourceLength % 3 == 0 ? 0 : sourceLength % 3 == 1 ? 2 : 3); | 
|  | else | 
|  | return (sourceLength / 3 + (sourceLength % 3 ? 1 : 0)) * 4; | 
|  | } | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; | 
|  |  | 
|  | // Allocate a buffer large enough to hold the encoded string. | 
|  | auto buf = new char[Base64.encodeLength(data.length)]; | 
|  |  | 
|  | Base64.encode(data, buf); | 
|  | assert(buf == "Gis8TV1u"); | 
|  | } | 
|  |  | 
|  |  | 
|  | // ubyte[] to char[] | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Encode $(D_PARAM source) into a $(D char[]) buffer using Base64 | 
|  | * encoding. | 
|  | * | 
|  | * Params: | 
|  | *  source = The $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *           range) to _encode. | 
|  | *  buffer = The $(D char[]) buffer to store the encoded result. | 
|  | * | 
|  | * Returns: | 
|  | *  The slice of $(D_PARAM buffer) that contains the encoded string. | 
|  | */ | 
|  | @trusted | 
|  | pure char[] encode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : ubyte) && | 
|  | is(R2 == char[])) | 
|  | in | 
|  | { | 
|  | assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding"); | 
|  | } | 
|  | out(result) | 
|  | { | 
|  | assert(result.length == encodeLength(source.length), "The length of result is different from Base64"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return []; | 
|  |  | 
|  | immutable blocks = srcLen / 3; | 
|  | immutable remain = srcLen % 3; | 
|  | auto      bufptr = buffer.ptr; | 
|  | auto      srcptr = source.ptr; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2]; | 
|  | *bufptr++ = EncodeMap[val >> 18       ]; | 
|  | *bufptr++ = EncodeMap[val >> 12 & 0x3f]; | 
|  | *bufptr++ = EncodeMap[val >>  6 & 0x3f]; | 
|  | *bufptr++ = EncodeMap[val       & 0x3f]; | 
|  | srcptr += 3; | 
|  | } | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0); | 
|  | *bufptr++ = EncodeMap[val >> 18       ]; | 
|  | *bufptr++ = EncodeMap[val >> 12 & 0x3f]; | 
|  |  | 
|  | final switch (remain) | 
|  | { | 
|  | case 2: | 
|  | *bufptr++ = EncodeMap[val >> 6 & 0x3f]; | 
|  | static if (Padding != NoPadding) | 
|  | *bufptr++ = Padding; | 
|  | break; | 
|  | case 1: | 
|  | static if (Padding != NoPadding) | 
|  | { | 
|  | *bufptr++ = Padding; | 
|  | *bufptr++ = Padding; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // encode method can't assume buffer length. So, slice needed. | 
|  | return buffer[0 .. bufptr - buffer.ptr]; | 
|  | } | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; | 
|  | char[32] buffer;    // much bigger than necessary | 
|  |  | 
|  | // Just to be sure... | 
|  | auto encodedLength = Base64.encodeLength(data.length); | 
|  | assert(buffer.length >= encodedLength); | 
|  |  | 
|  | // encode() returns a slice to the provided buffer. | 
|  | auto encoded = Base64.encode(data, buffer[]); | 
|  | assert(encoded is buffer[0 .. encodedLength]); | 
|  | assert(encoded == "g9cwegE/"); | 
|  | } | 
|  |  | 
|  |  | 
|  | // InputRange to char[] | 
|  |  | 
|  |  | 
|  | /** | 
|  | * ditto | 
|  | */ | 
|  | char[] encode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 && | 
|  | is(ElementType!R1 : ubyte) && hasLength!R1 && | 
|  | is(R2 == char[])) | 
|  | in | 
|  | { | 
|  | assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding"); | 
|  | } | 
|  | out(result) | 
|  | { | 
|  | // @@@BUG@@@ D's DbC can't caputre an argument of function and store the result of precondition. | 
|  | //assert(result.length == encodeLength(source.length), "The length of result is different from Base64"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return []; | 
|  |  | 
|  | immutable blocks = srcLen / 3; | 
|  | immutable remain = srcLen % 3; | 
|  | auto      bufptr = buffer.ptr; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable v1 = source.front; source.popFront(); | 
|  | immutable v2 = source.front; source.popFront(); | 
|  | immutable v3 = source.front; source.popFront(); | 
|  | immutable val = v1 << 16 | v2 << 8 | v3; | 
|  | *bufptr++ = EncodeMap[val >> 18       ]; | 
|  | *bufptr++ = EncodeMap[val >> 12 & 0x3f]; | 
|  | *bufptr++ = EncodeMap[val >>  6 & 0x3f]; | 
|  | *bufptr++ = EncodeMap[val       & 0x3f]; | 
|  | } | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | size_t val = source.front << 16; | 
|  | if (remain == 2) | 
|  | { | 
|  | source.popFront(); | 
|  | val |= source.front << 8; | 
|  | } | 
|  |  | 
|  | *bufptr++ = EncodeMap[val >> 18       ]; | 
|  | *bufptr++ = EncodeMap[val >> 12 & 0x3f]; | 
|  |  | 
|  | final switch (remain) | 
|  | { | 
|  | case 2: | 
|  | *bufptr++ = EncodeMap[val >> 6 & 0x3f]; | 
|  | static if (Padding != NoPadding) | 
|  | *bufptr++ = Padding; | 
|  | break; | 
|  | case 1: | 
|  | static if (Padding != NoPadding) | 
|  | { | 
|  | *bufptr++ = Padding; | 
|  | *bufptr++ = Padding; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'. | 
|  | version (unittest) | 
|  | assert( | 
|  | bufptr - buffer.ptr == encodeLength(srcLen), | 
|  | "The length of result is different from Base64" | 
|  | ); | 
|  |  | 
|  | // encode method can't assume buffer length. So, slice needed. | 
|  | return buffer[0 .. bufptr - buffer.ptr]; | 
|  | } | 
|  |  | 
|  |  | 
|  | // ubyte[] to OutputRange | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Encodes $(D_PARAM source) into an | 
|  | * $(LINK2 std_range_primitives.html#isOutputRange, output range) using | 
|  | * Base64 encoding. | 
|  | * | 
|  | * Params: | 
|  | *  source = The $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *           range) to _encode. | 
|  | *  range  = The $(LINK2 std_range_primitives.html#isOutputRange, output | 
|  | *           range) to store the encoded result. | 
|  | * | 
|  | * Returns: | 
|  | *  The number of times the output range's $(D put) method was invoked. | 
|  | */ | 
|  | size_t encode(R1, R2)(in R1 source, auto ref R2 range) | 
|  | if (isArray!R1 && is(ElementType!R1 : ubyte) && | 
|  | !is(R2 == char[]) && isOutputRange!(R2, char)) | 
|  | out(result) | 
|  | { | 
|  | assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return 0; | 
|  |  | 
|  | immutable blocks = srcLen / 3; | 
|  | immutable remain = srcLen % 3; | 
|  | auto      srcptr = source.ptr; | 
|  | size_t    pcount; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2]; | 
|  | put(range, EncodeMap[val >> 18       ]); | 
|  | put(range, EncodeMap[val >> 12 & 0x3f]); | 
|  | put(range, EncodeMap[val >>  6 & 0x3f]); | 
|  | put(range, EncodeMap[val       & 0x3f]); | 
|  | srcptr += 3; | 
|  | pcount += 4; | 
|  | } | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0); | 
|  | put(range, EncodeMap[val >> 18       ]); | 
|  | put(range, EncodeMap[val >> 12 & 0x3f]); | 
|  | pcount += 2; | 
|  |  | 
|  | final switch (remain) | 
|  | { | 
|  | case 2: | 
|  | put(range, EncodeMap[val >> 6 & 0x3f]); | 
|  | pcount++; | 
|  |  | 
|  | static if (Padding != NoPadding) | 
|  | { | 
|  | put(range, Padding); | 
|  | pcount++; | 
|  | } | 
|  | break; | 
|  | case 1: | 
|  | static if (Padding != NoPadding) | 
|  | { | 
|  | put(range, Padding); | 
|  | put(range, Padding); | 
|  | pcount += 2; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return pcount; | 
|  | } | 
|  |  | 
|  | /// | 
|  | @system unittest | 
|  | { | 
|  | // @system because encode for OutputRange is @system | 
|  | struct OutputRange | 
|  | { | 
|  | char[] result; | 
|  | void put(const(char) ch) @safe { result ~= ch; } | 
|  | } | 
|  |  | 
|  | ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; | 
|  |  | 
|  | // This overload of encode() returns the number of calls to the output | 
|  | // range's put method. | 
|  | OutputRange output; | 
|  | assert(Base64.encode(data, output) == 8); | 
|  | assert(output.result == "Gis8TV1u"); | 
|  | } | 
|  |  | 
|  |  | 
|  | // InputRange to OutputRange | 
|  |  | 
|  |  | 
|  | /** | 
|  | * ditto | 
|  | */ | 
|  | size_t encode(R1, R2)(R1 source, auto ref R2 range) | 
|  | if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : ubyte) && | 
|  | hasLength!R1 && !is(R2 == char[]) && isOutputRange!(R2, char)) | 
|  | out(result) | 
|  | { | 
|  | // @@@BUG@@@ Workaround for DbC problem. | 
|  | //assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return 0; | 
|  |  | 
|  | immutable blocks = srcLen / 3; | 
|  | immutable remain = srcLen % 3; | 
|  | size_t    pcount; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable v1 = source.front; source.popFront(); | 
|  | immutable v2 = source.front; source.popFront(); | 
|  | immutable v3 = source.front; source.popFront(); | 
|  | immutable val = v1 << 16 | v2 << 8 | v3; | 
|  | put(range, EncodeMap[val >> 18       ]); | 
|  | put(range, EncodeMap[val >> 12 & 0x3f]); | 
|  | put(range, EncodeMap[val >>  6 & 0x3f]); | 
|  | put(range, EncodeMap[val       & 0x3f]); | 
|  | pcount += 4; | 
|  | } | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | size_t val = source.front << 16; | 
|  | if (remain == 2) | 
|  | { | 
|  | source.popFront(); | 
|  | val |= source.front << 8; | 
|  | } | 
|  |  | 
|  | put(range, EncodeMap[val >> 18       ]); | 
|  | put(range, EncodeMap[val >> 12 & 0x3f]); | 
|  | pcount += 2; | 
|  |  | 
|  | final switch (remain) | 
|  | { | 
|  | case 2: | 
|  | put(range, EncodeMap[val >> 6 & 0x3f]); | 
|  | pcount++; | 
|  |  | 
|  | static if (Padding != NoPadding) | 
|  | { | 
|  | put(range, Padding); | 
|  | pcount++; | 
|  | } | 
|  | break; | 
|  | case 1: | 
|  | static if (Padding != NoPadding) | 
|  | { | 
|  | put(range, Padding); | 
|  | put(range, Padding); | 
|  | pcount += 2; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // @@@BUG@@@ Workaround for DbC problem. | 
|  | version (unittest) | 
|  | assert( | 
|  | pcount == encodeLength(srcLen), | 
|  | "The number of put is different from the length of Base64" | 
|  | ); | 
|  |  | 
|  | return pcount; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Encodes $(D_PARAM source) to newly-allocated buffer. | 
|  | * | 
|  | * This convenience method alleviates the need to manually manage output | 
|  | * buffers. | 
|  | * | 
|  | * Params: | 
|  | *  source = The $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *           range) to _encode. | 
|  | * | 
|  | * Returns: | 
|  | *  A newly-allocated $(D char[]) buffer containing the encoded string. | 
|  | */ | 
|  | @safe | 
|  | pure char[] encode(Range)(Range source) if (isArray!Range && is(ElementType!Range : ubyte)) | 
|  | { | 
|  | return encode(source, new char[encodeLength(source.length)]); | 
|  | } | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; | 
|  | assert(Base64.encode(data) == "Gis8TV1u"); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * ditto | 
|  | */ | 
|  | char[] encode(Range)(Range source) if (!isArray!Range && isInputRange!Range && | 
|  | is(ElementType!Range : ubyte) && hasLength!Range) | 
|  | { | 
|  | return encode(source, new char[encodeLength(source.length)]); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * An $(LINK2 std_range_primitives.html#isInputRange, input range) that | 
|  | * iterates over the respective Base64 encodings of a range of data items. | 
|  | * | 
|  | * This range will be a $(LINK2 std_range_primitives.html#isForwardRange, | 
|  | * forward range) if the underlying data source is at least a forward | 
|  | * range. | 
|  | * | 
|  | * Note: This struct is not intended to be created in user code directly; | 
|  | * use the $(LREF encoder) function instead. | 
|  | */ | 
|  | struct Encoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(ubyte)[]) || | 
|  | is(ElementType!Range : const(char)[]))) | 
|  | { | 
|  | private: | 
|  | Range  range_; | 
|  | char[] buffer_, encoded_; | 
|  |  | 
|  |  | 
|  | public: | 
|  | this(Range range) | 
|  | { | 
|  | range_ = range; | 
|  | doEncoding(); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: | 
|  | *  true if there is no more encoded data left. | 
|  | */ | 
|  | @property @trusted | 
|  | bool empty() | 
|  | { | 
|  | return range_.empty; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: The current chunk of encoded data. | 
|  | */ | 
|  | @property @safe | 
|  | nothrow char[] front() | 
|  | { | 
|  | return encoded_; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Advance the range to the next chunk of encoded data. | 
|  | * | 
|  | * Throws: | 
|  | *  $(D Base64Exception) If invoked when | 
|  | *  $(LREF2 .Base64Impl.Encoder.empty, empty) returns $(D true). | 
|  | */ | 
|  | void popFront() | 
|  | { | 
|  | enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining")); | 
|  |  | 
|  | range_.popFront(); | 
|  |  | 
|  | /* | 
|  | * This check is very ugly. I think this is a Range's flaw. | 
|  | * I very strongly want the Range guideline for unified implementation. | 
|  | * | 
|  | * In this case, Encoder becomes a beautiful implementation if 'front' performs Base64 encoding. | 
|  | */ | 
|  | if (!empty) | 
|  | doEncoding(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static if (isForwardRange!Range) | 
|  | { | 
|  | /** | 
|  | * Save the current iteration state of the range. | 
|  | * | 
|  | * This method is only available if the underlying range is a | 
|  | * $(LINK2 std_range_primitives.html#isForwardRange, forward | 
|  | * range). | 
|  | * | 
|  | * Returns: | 
|  | *  A copy of $(D this). | 
|  | */ | 
|  | @property | 
|  | typeof(this) save() | 
|  | { | 
|  | typeof(return) encoder; | 
|  |  | 
|  | encoder.range_   = range_.save; | 
|  | encoder.buffer_  = buffer_.dup; | 
|  | encoder.encoded_ = encoder.buffer_[0 .. encoded_.length]; | 
|  |  | 
|  | return encoder; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | private: | 
|  | void doEncoding() | 
|  | { | 
|  | auto data = cast(const(ubyte)[])range_.front; | 
|  | auto size = encodeLength(data.length); | 
|  | if (size > buffer_.length) | 
|  | buffer_.length = size; | 
|  |  | 
|  | encoded_ = encode(data, buffer_); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * An $(LINK2 std_range_primitives.html#isInputRange, input range) that | 
|  | * iterates over the encoded bytes of the given source data. | 
|  | * | 
|  | * It will be a $(LINK2 std_range_primitives.html#isForwardRange, forward | 
|  | * range) if the underlying data source is at least a forward range. | 
|  | * | 
|  | * Note: This struct is not intended to be created in user code directly; | 
|  | * use the $(LREF encoder) function instead. | 
|  | */ | 
|  | struct Encoder(Range) if (isInputRange!Range && is(ElementType!Range : ubyte)) | 
|  | { | 
|  | private: | 
|  | Range range_; | 
|  | ubyte first; | 
|  | int   pos, padding; | 
|  |  | 
|  |  | 
|  | public: | 
|  | this(Range range) | 
|  | { | 
|  | range_ = range; | 
|  | static if (isForwardRange!Range) | 
|  | range_ = range_.save; | 
|  |  | 
|  | if (range_.empty) | 
|  | pos = -1; | 
|  | else | 
|  | popFront(); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: | 
|  | *  true if there are no more encoded characters to be iterated. | 
|  | */ | 
|  | @property @safe | 
|  | nothrow bool empty() const | 
|  | { | 
|  | static if (Padding == NoPadding) | 
|  | return pos < 0; | 
|  | else | 
|  | return pos < 0 && !padding; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: The current encoded character. | 
|  | */ | 
|  | @property @safe | 
|  | nothrow ubyte front() | 
|  | { | 
|  | return first; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Advance to the next encoded character. | 
|  | * | 
|  | * Throws: | 
|  | *  $(D Base64Exception) If invoked when $(LREF2 .Base64Impl.Encoder.empty.2, | 
|  | *  empty) returns $(D true). | 
|  | */ | 
|  | void popFront() | 
|  | { | 
|  | enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining")); | 
|  |  | 
|  | static if (Padding != NoPadding) | 
|  | if (padding) | 
|  | { | 
|  | first = Padding; | 
|  | pos   = -1; | 
|  | padding--; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (range_.empty) | 
|  | { | 
|  | pos = -1; | 
|  | return; | 
|  | } | 
|  |  | 
|  | final switch (pos) | 
|  | { | 
|  | case 0: | 
|  | first = EncodeMap[range_.front >> 2]; | 
|  | break; | 
|  | case 1: | 
|  | immutable t = (range_.front & 0b11) << 4; | 
|  | range_.popFront(); | 
|  |  | 
|  | if (range_.empty) | 
|  | { | 
|  | first   = EncodeMap[t]; | 
|  | padding = 3; | 
|  | } | 
|  | else | 
|  | { | 
|  | first = EncodeMap[t | (range_.front >> 4)]; | 
|  | } | 
|  | break; | 
|  | case 2: | 
|  | immutable t = (range_.front & 0b1111) << 2; | 
|  | range_.popFront(); | 
|  |  | 
|  | if (range_.empty) | 
|  | { | 
|  | first   = EncodeMap[t]; | 
|  | padding = 2; | 
|  | } | 
|  | else | 
|  | { | 
|  | first = EncodeMap[t | (range_.front >> 6)]; | 
|  | } | 
|  | break; | 
|  | case 3: | 
|  | first = EncodeMap[range_.front & 0b111111]; | 
|  | range_.popFront(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | ++pos %= 4; | 
|  | } | 
|  |  | 
|  |  | 
|  | static if (isForwardRange!Range) | 
|  | { | 
|  | /** | 
|  | * Save the current iteration state of the range. | 
|  | * | 
|  | * This method is only available if the underlying range is a | 
|  | * $(LINK2 std_range_primitives.html#isForwardRange, forward | 
|  | * range). | 
|  | * | 
|  | * Returns: | 
|  | *  A copy of $(D this). | 
|  | */ | 
|  | @property | 
|  | typeof(this) save() | 
|  | { | 
|  | auto encoder = this; | 
|  | encoder.range_ = encoder.range_.save; | 
|  | return encoder; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Construct an $(D Encoder) that iterates over the Base64 encoding of the | 
|  | * given $(LINK2 std_range_primitives.html#isInputRange, input range). | 
|  | * | 
|  | * Params: | 
|  | *  range = An $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *      range) over the data to be encoded. | 
|  | * | 
|  | * Returns: | 
|  | *  If $(D_PARAM range) is a range of bytes, an $(D Encoder) that iterates | 
|  | *  over the bytes of the corresponding Base64 encoding. | 
|  | * | 
|  | *  If $(D_PARAM range) is a range of ranges of bytes, an $(D Encoder) that | 
|  | *  iterates over the Base64 encoded strings of each element of the range. | 
|  | * | 
|  | *  In both cases, the returned $(D Encoder) will be a | 
|  | *  $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the | 
|  | *  given $(D range) is at least a forward range, otherwise it will be only | 
|  | *  an input range. | 
|  | * | 
|  | * Example: | 
|  | * This example encodes the input one line at a time. | 
|  | * ----- | 
|  | * File f = File("text.txt", "r"); | 
|  | * scope(exit) f.close(); | 
|  | * | 
|  | * uint line = 0; | 
|  | * foreach (encoded; Base64.encoder(f.byLine())) | 
|  | * { | 
|  | *     writeln(++line, ". ", encoded); | 
|  | * } | 
|  | * ----- | 
|  | * | 
|  | * Example: | 
|  | * This example encodes the input data one byte at a time. | 
|  | * ----- | 
|  | * ubyte[] data = cast(ubyte[]) "0123456789"; | 
|  | * | 
|  | * // The ElementType of data is not aggregation type | 
|  | * foreach (encoded; Base64.encoder(data)) | 
|  | * { | 
|  | *     writeln(encoded); | 
|  | * } | 
|  | * ----- | 
|  | */ | 
|  | Encoder!(Range) encoder(Range)(Range range) if (isInputRange!Range) | 
|  | { | 
|  | return typeof(return)(range); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Decode functions */ | 
|  |  | 
|  |  | 
|  | private immutable int[char.max + 1] DecodeMap = [ | 
|  | 'A':0b000000, 'B':0b000001, 'C':0b000010, 'D':0b000011, 'E':0b000100, | 
|  | 'F':0b000101, 'G':0b000110, 'H':0b000111, 'I':0b001000, 'J':0b001001, | 
|  | 'K':0b001010, 'L':0b001011, 'M':0b001100, 'N':0b001101, 'O':0b001110, | 
|  | 'P':0b001111, 'Q':0b010000, 'R':0b010001, 'S':0b010010, 'T':0b010011, | 
|  | 'U':0b010100, 'V':0b010101, 'W':0b010110, 'X':0b010111, 'Y':0b011000, | 
|  | 'Z':0b011001, 'a':0b011010, 'b':0b011011, 'c':0b011100, 'd':0b011101, | 
|  | 'e':0b011110, 'f':0b011111, 'g':0b100000, 'h':0b100001, 'i':0b100010, | 
|  | 'j':0b100011, 'k':0b100100, 'l':0b100101, 'm':0b100110, 'n':0b100111, | 
|  | 'o':0b101000, 'p':0b101001, 'q':0b101010, 'r':0b101011, 's':0b101100, | 
|  | 't':0b101101, 'u':0b101110, 'v':0b101111, 'w':0b110000, 'x':0b110001, | 
|  | 'y':0b110010, 'z':0b110011, '0':0b110100, '1':0b110101, '2':0b110110, | 
|  | '3':0b110111, '4':0b111000, '5':0b111001, '6':0b111010, '7':0b111011, | 
|  | '8':0b111100, '9':0b111101, Map62th:0b111110, Map63th:0b111111, Padding:-1 | 
|  | ]; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Given a Base64 encoded string, calculates the length of the decoded | 
|  | * string. | 
|  | * | 
|  | * Params: | 
|  | *  sourceLength = The length of the Base64 encoding. | 
|  | * | 
|  | * Returns: | 
|  | *  The length of the decoded string corresponding to a Base64 encoding of | 
|  | *  length $(D_PARAM sourceLength). | 
|  | */ | 
|  | @safe | 
|  | pure nothrow size_t decodeLength(in size_t sourceLength) | 
|  | { | 
|  | static if (Padding == NoPadding) | 
|  | return (sourceLength / 4) * 3 + (sourceLength % 4 < 2 ? 0 : sourceLength % 4 == 2 ? 1 : 2); | 
|  | else | 
|  | return (sourceLength / 4) * 3; | 
|  | } | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | auto encoded = "Gis8TV1u"; | 
|  |  | 
|  | // Allocate a sufficiently large buffer to hold to decoded result. | 
|  | auto buffer = new ubyte[Base64.decodeLength(encoded.length)]; | 
|  |  | 
|  | Base64.decode(encoded, buffer); | 
|  | assert(buffer == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Used in decode contracts. Calculates the actual size the decoded | 
|  | // result should have, taking into account trailing padding. | 
|  | @safe | 
|  | pure nothrow private size_t realDecodeLength(R)(R source) | 
|  | { | 
|  | auto expect = decodeLength(source.length); | 
|  | static if (Padding != NoPadding) | 
|  | { | 
|  | if (source.length % 4 == 0) | 
|  | { | 
|  | expect -= source.length == 0       ? 0 : | 
|  | source[$ - 2] == Padding ? 2 : | 
|  | source[$ - 1] == Padding ? 1 : 0; | 
|  | } | 
|  | } | 
|  | return expect; | 
|  | } | 
|  |  | 
|  |  | 
|  | // char[] to ubyte[] | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Decodes $(D_PARAM source) into the given buffer. | 
|  | * | 
|  | * Params: | 
|  | *  source = The $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *      range) to _decode. | 
|  | *  buffer = The buffer to store decoded result. | 
|  | * | 
|  | * Returns: | 
|  | *  The slice of $(D_PARAM buffer) containing the decoded result. | 
|  | * | 
|  | * Throws: | 
|  | *  $(D Base64Exception) if $(D_PARAM source) contains characters outside the | 
|  | *  base alphabet of the current Base64 encoding scheme. | 
|  | */ | 
|  | @trusted | 
|  | pure ubyte[] decode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : dchar) && | 
|  | is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) | 
|  | in | 
|  | { | 
|  | assert(buffer.length >= realDecodeLength(source), "Insufficient buffer for decoding"); | 
|  | } | 
|  | out(result) | 
|  | { | 
|  | immutable expect = realDecodeLength(source); | 
|  | assert(result.length == expect, "The length of result is different from the expected length"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return []; | 
|  | static if (Padding != NoPadding) | 
|  | enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); | 
|  |  | 
|  | immutable blocks = srcLen / 4; | 
|  | auto      srcptr = source.ptr; | 
|  | auto      bufptr = buffer.ptr; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable v1 = decodeChar(*srcptr++); | 
|  | immutable v2 = decodeChar(*srcptr++); | 
|  |  | 
|  | *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); | 
|  |  | 
|  | immutable v3 = decodeChar(*srcptr++); | 
|  | if (v3 == -1) | 
|  | break; | 
|  |  | 
|  | *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff); | 
|  |  | 
|  | immutable v4 = decodeChar(*srcptr++); | 
|  | if (v4 == -1) | 
|  | break; | 
|  |  | 
|  | *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff); | 
|  | } | 
|  |  | 
|  | static if (Padding == NoPadding) | 
|  | { | 
|  | immutable remain = srcLen % 4; | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | immutable v1 = decodeChar(*srcptr++); | 
|  | immutable v2 = decodeChar(*srcptr++); | 
|  |  | 
|  | *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); | 
|  |  | 
|  | if (remain == 3) | 
|  | *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff); | 
|  | } | 
|  | } | 
|  |  | 
|  | return buffer[0 .. bufptr - buffer.ptr]; | 
|  | } | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | auto encoded = "Gis8TV1u"; | 
|  | ubyte[32] buffer;   // much bigger than necessary | 
|  |  | 
|  | // Just to be sure... | 
|  | auto decodedLength = Base64.decodeLength(encoded.length); | 
|  | assert(buffer.length >= decodedLength); | 
|  |  | 
|  | // decode() returns a slice of the given buffer. | 
|  | auto decoded = Base64.decode(encoded, buffer[]); | 
|  | assert(decoded is buffer[0 .. decodedLength]); | 
|  | assert(decoded == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); | 
|  | } | 
|  |  | 
|  | // InputRange to ubyte[] | 
|  |  | 
|  |  | 
|  | /** | 
|  | * ditto | 
|  | */ | 
|  | ubyte[] decode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 && | 
|  | is(ElementType!R1 : dchar) && hasLength!R1 && | 
|  | is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) | 
|  | in | 
|  | { | 
|  | assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding"); | 
|  | } | 
|  | out(result) | 
|  | { | 
|  | // @@@BUG@@@ Workaround for DbC problem. | 
|  | //immutable expect = decodeLength(source.length) - 2; | 
|  | //assert(result.length >= expect, "The length of result is smaller than expected length"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return []; | 
|  | static if (Padding != NoPadding) | 
|  | enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); | 
|  |  | 
|  | immutable blocks = srcLen / 4; | 
|  | auto      bufptr = buffer.ptr; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable v1 = decodeChar(source.front); source.popFront(); | 
|  | immutable v2 = decodeChar(source.front); source.popFront(); | 
|  |  | 
|  | *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); | 
|  |  | 
|  | immutable v3 = decodeChar(source.front); | 
|  | if (v3 == -1) | 
|  | break; | 
|  |  | 
|  | *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff); | 
|  | source.popFront(); | 
|  |  | 
|  | immutable v4 = decodeChar(source.front); | 
|  | if (v4 == -1) | 
|  | break; | 
|  |  | 
|  | *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff); | 
|  | source.popFront(); | 
|  | } | 
|  |  | 
|  | static if (Padding == NoPadding) | 
|  | { | 
|  | immutable remain = srcLen % 4; | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | immutable v1 = decodeChar(source.front); source.popFront(); | 
|  | immutable v2 = decodeChar(source.front); | 
|  |  | 
|  | *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); | 
|  |  | 
|  | if (remain == 3) | 
|  | { | 
|  | source.popFront(); | 
|  | *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // @@@BUG@@@ Workaround for DbC problem. | 
|  | version (unittest) | 
|  | assert( | 
|  | (bufptr - buffer.ptr) >= (decodeLength(srcLen) - 2), | 
|  | "The length of result is smaller than expected length" | 
|  | ); | 
|  |  | 
|  | return buffer[0 .. bufptr - buffer.ptr]; | 
|  | } | 
|  |  | 
|  |  | 
|  | // char[] to OutputRange | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Decodes $(D_PARAM source) into a given | 
|  | * $(LINK2 std_range_primitives.html#isOutputRange, output range). | 
|  | * | 
|  | * Params: | 
|  | *  source = The $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *           range) to _decode. | 
|  | *  range  = The $(LINK2 std_range_primitives.html#isOutputRange, output | 
|  | *           range) to store the decoded result. | 
|  | * | 
|  | * Returns: | 
|  | *  The number of times the output range's $(D put) method was invoked. | 
|  | * | 
|  | * Throws: | 
|  | *  $(D Base64Exception) if $(D_PARAM source) contains characters outside the | 
|  | *  base alphabet of the current Base64 encoding scheme. | 
|  | */ | 
|  | size_t decode(R1, R2)(in R1 source, auto ref R2 range) | 
|  | if (isArray!R1 && is(ElementType!R1 : dchar) && | 
|  | !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) | 
|  | out(result) | 
|  | { | 
|  | immutable expect = realDecodeLength(source); | 
|  | assert(result == expect, "The result of decode is different from the expected"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return 0; | 
|  | static if (Padding != NoPadding) | 
|  | enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); | 
|  |  | 
|  | immutable blocks = srcLen / 4; | 
|  | auto      srcptr = source.ptr; | 
|  | size_t    pcount; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable v1 = decodeChar(*srcptr++); | 
|  | immutable v2 = decodeChar(*srcptr++); | 
|  |  | 
|  | put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); | 
|  | pcount++; | 
|  |  | 
|  | immutable v3 = decodeChar(*srcptr++); | 
|  | if (v3 == -1) | 
|  | break; | 
|  |  | 
|  | put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff)); | 
|  | pcount++; | 
|  |  | 
|  | immutable v4 = decodeChar(*srcptr++); | 
|  | if (v4 == -1) | 
|  | break; | 
|  |  | 
|  | put(range, cast(ubyte)((v3 << 6 | v4) & 0xff)); | 
|  | pcount++; | 
|  | } | 
|  |  | 
|  | static if (Padding == NoPadding) | 
|  | { | 
|  | immutable remain = srcLen % 4; | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | immutable v1 = decodeChar(*srcptr++); | 
|  | immutable v2 = decodeChar(*srcptr++); | 
|  |  | 
|  | put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); | 
|  | pcount++; | 
|  |  | 
|  | if (remain == 3) | 
|  | { | 
|  | put(range, cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff)); | 
|  | pcount++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return pcount; | 
|  | } | 
|  |  | 
|  | /// | 
|  | @system unittest | 
|  | { | 
|  | struct OutputRange | 
|  | { | 
|  | ubyte[] result; | 
|  | void put(ubyte b) { result ~= b; } | 
|  | } | 
|  | OutputRange output; | 
|  |  | 
|  | // This overload of decode() returns the number of calls to put(). | 
|  | assert(Base64.decode("Gis8TV1u", output) == 6); | 
|  | assert(output.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); | 
|  | } | 
|  |  | 
|  |  | 
|  | // InputRange to OutputRange | 
|  |  | 
|  |  | 
|  | /** | 
|  | * ditto | 
|  | */ | 
|  | size_t decode(R1, R2)(R1 source, auto ref R2 range) | 
|  | if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : dchar) && | 
|  | hasLength!R1 && !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) | 
|  | out(result) | 
|  | { | 
|  | // @@@BUG@@@ Workaround for DbC problem. | 
|  | //immutable expect = decodeLength(source.length) - 2; | 
|  | //assert(result >= expect, "The length of result is smaller than expected length"); | 
|  | } | 
|  | body | 
|  | { | 
|  | immutable srcLen = source.length; | 
|  | if (srcLen == 0) | 
|  | return 0; | 
|  | static if (Padding != NoPadding) | 
|  | enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); | 
|  |  | 
|  | immutable blocks = srcLen / 4; | 
|  | size_t    pcount; | 
|  |  | 
|  | foreach (Unused; 0 .. blocks) | 
|  | { | 
|  | immutable v1 = decodeChar(source.front); source.popFront(); | 
|  | immutable v2 = decodeChar(source.front); source.popFront(); | 
|  |  | 
|  | put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); | 
|  | pcount++; | 
|  |  | 
|  | immutable v3 = decodeChar(source.front); | 
|  | if (v3 == -1) | 
|  | break; | 
|  |  | 
|  | put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff)); | 
|  | source.popFront(); | 
|  | pcount++; | 
|  |  | 
|  | immutable v4 = decodeChar(source.front); | 
|  | if (v4 == -1) | 
|  | break; | 
|  |  | 
|  | put(range, cast(ubyte)((v3 << 6 | v4) & 0xff)); | 
|  | source.popFront(); | 
|  | pcount++; | 
|  | } | 
|  |  | 
|  | static if (Padding == NoPadding) | 
|  | { | 
|  | immutable remain = srcLen % 4; | 
|  |  | 
|  | if (remain) | 
|  | { | 
|  | immutable v1 = decodeChar(source.front); source.popFront(); | 
|  | immutable v2 = decodeChar(source.front); | 
|  |  | 
|  | put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); | 
|  | pcount++; | 
|  |  | 
|  | if (remain == 3) | 
|  | { | 
|  | source.popFront(); | 
|  | put(range, cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff)); | 
|  | pcount++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // @@@BUG@@@ Workaround for DbC problem. | 
|  | version (unittest) | 
|  | assert( | 
|  | pcount >= (decodeLength(srcLen) - 2), | 
|  | "The length of result is smaller than expected length" | 
|  | ); | 
|  |  | 
|  | return pcount; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Decodes $(D_PARAM source) into newly-allocated buffer. | 
|  | * | 
|  | * This convenience method alleviates the need to manually manage decoding | 
|  | * buffers. | 
|  | * | 
|  | * Params: | 
|  | *  source = The $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *           range) to _decode. | 
|  | * | 
|  | * Returns: | 
|  | *  A newly-allocated $(D ubyte[]) buffer containing the decoded string. | 
|  | */ | 
|  | @safe | 
|  | pure ubyte[] decode(Range)(Range source) if (isArray!Range && is(ElementType!Range : dchar)) | 
|  | { | 
|  | return decode(source, new ubyte[decodeLength(source.length)]); | 
|  | } | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | auto data = "Gis8TV1u"; | 
|  | assert(Base64.decode(data) == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * ditto | 
|  | */ | 
|  | ubyte[] decode(Range)(Range source) if (!isArray!Range && isInputRange!Range && | 
|  | is(ElementType!Range : dchar) && hasLength!Range) | 
|  | { | 
|  | return decode(source, new ubyte[decodeLength(source.length)]); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * An $(LINK2 std_range_primitives.html#isInputRange, input range) that | 
|  | * iterates over the decoded data of a range of Base64 encodings. | 
|  | * | 
|  | * This range will be a $(LINK2 std_range_primitives.html#isForwardRange, | 
|  | * forward range) if the underlying data source is at least a forward | 
|  | * range. | 
|  | * | 
|  | * Note: This struct is not intended to be created in user code directly; | 
|  | * use the $(LREF decoder) function instead. | 
|  | */ | 
|  | struct Decoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(char)[]) || | 
|  | is(ElementType!Range : const(ubyte)[]))) | 
|  | { | 
|  | private: | 
|  | Range   range_; | 
|  | ubyte[] buffer_, decoded_; | 
|  |  | 
|  |  | 
|  | public: | 
|  | this(Range range) | 
|  | { | 
|  | range_ = range; | 
|  | doDecoding(); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: | 
|  | *  true if there are no more elements to be iterated. | 
|  | */ | 
|  | @property @trusted | 
|  | bool empty() | 
|  | { | 
|  | return range_.empty; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: The decoding of the current element in the input. | 
|  | */ | 
|  | @property @safe | 
|  | nothrow ubyte[] front() | 
|  | { | 
|  | return decoded_; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Advance to the next element in the input to be decoded. | 
|  | * | 
|  | * Throws: | 
|  | *  $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty, | 
|  | *  empty) returns $(D true). | 
|  | */ | 
|  | void popFront() | 
|  | { | 
|  | enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining.")); | 
|  |  | 
|  | range_.popFront(); | 
|  |  | 
|  | /* | 
|  | * I mentioned Encoder's popFront. | 
|  | */ | 
|  | if (!empty) | 
|  | doDecoding(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static if (isForwardRange!Range) | 
|  | { | 
|  | /** | 
|  | * Saves the current iteration state. | 
|  | * | 
|  | * This method is only available if the underlying range is a | 
|  | * $(LINK2 std_range_primitives.html#isForwardRange, forward | 
|  | * range). | 
|  | * | 
|  | * Returns: A copy of $(D this). | 
|  | */ | 
|  | @property | 
|  | typeof(this) save() | 
|  | { | 
|  | typeof(return) decoder; | 
|  |  | 
|  | decoder.range_   = range_.save; | 
|  | decoder.buffer_  = buffer_.dup; | 
|  | decoder.decoded_ = decoder.buffer_[0 .. decoded_.length]; | 
|  |  | 
|  | return decoder; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | private: | 
|  | void doDecoding() | 
|  | { | 
|  | auto data = cast(const(char)[])range_.front; | 
|  |  | 
|  | static if (Padding == NoPadding) | 
|  | { | 
|  | while (data.length % 4 == 1) | 
|  | { | 
|  | range_.popFront(); | 
|  | data ~= cast(const(char)[])range_.front; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | while (data.length % 4 != 0) | 
|  | { | 
|  | range_.popFront(); | 
|  | data ~= cast(const(char)[])range_.front; | 
|  | } | 
|  | } | 
|  |  | 
|  | auto size = decodeLength(data.length); | 
|  | if (size > buffer_.length) | 
|  | buffer_.length = size; | 
|  |  | 
|  | decoded_ = decode(data, buffer_); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * An $(LINK2 std_range_primitives.html#isInputRange, input range) that | 
|  | * iterates over the bytes of data decoded from a Base64 encoded string. | 
|  | * | 
|  | * This range will be a $(LINK2 std_range_primitives.html#isForwardRange, | 
|  | * forward range) if the underlying data source is at least a forward | 
|  | * range. | 
|  | * | 
|  | * Note: This struct is not intended to be created in user code directly; | 
|  | * use the $(LREF decoder) function instead. | 
|  | */ | 
|  | struct Decoder(Range) if (isInputRange!Range && is(ElementType!Range : char)) | 
|  | { | 
|  | private: | 
|  | Range range_; | 
|  | ubyte first; | 
|  | int   pos; | 
|  |  | 
|  |  | 
|  | public: | 
|  | this(Range range) | 
|  | { | 
|  | range_ = range; | 
|  | static if (isForwardRange!Range) | 
|  | range_ = range_.save; | 
|  |  | 
|  | static if (Padding != NoPadding && hasLength!Range) | 
|  | enforce(range_.length % 4 == 0, new Base64Exception("Invalid length of encoded data")); | 
|  |  | 
|  | if (range_.empty) | 
|  | pos = -1; | 
|  | else | 
|  | popFront(); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: | 
|  | *  true if there are no more elements to be iterated. | 
|  | */ | 
|  | @property @safe | 
|  | nothrow bool empty() const | 
|  | { | 
|  | return pos < 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns: The current decoded byte. | 
|  | */ | 
|  | @property @safe | 
|  | nothrow ubyte front() | 
|  | { | 
|  | return first; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Advance to the next decoded byte. | 
|  | * | 
|  | * Throws: | 
|  | *  $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty, | 
|  | *  empty) returns $(D true). | 
|  | */ | 
|  | void popFront() | 
|  | { | 
|  | enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining")); | 
|  |  | 
|  | static if (Padding == NoPadding) | 
|  | { | 
|  | bool endCondition() | 
|  | { | 
|  | return range_.empty; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | bool endCondition() | 
|  | { | 
|  | enforce(!range_.empty, new Base64Exception("Missing padding")); | 
|  | return range_.front == Padding; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (range_.empty || range_.front == Padding) | 
|  | { | 
|  | pos = -1; | 
|  | return; | 
|  | } | 
|  |  | 
|  | final switch (pos) | 
|  | { | 
|  | case 0: | 
|  | enforce(!endCondition(), new Base64Exception("Premature end of data found")); | 
|  |  | 
|  | immutable t = DecodeMap[range_.front] << 2; | 
|  | range_.popFront(); | 
|  |  | 
|  | enforce(!endCondition(), new Base64Exception("Premature end of data found")); | 
|  | first = cast(ubyte)(t | (DecodeMap[range_.front] >> 4)); | 
|  | break; | 
|  | case 1: | 
|  | immutable t = (DecodeMap[range_.front] & 0b1111) << 4; | 
|  | range_.popFront(); | 
|  |  | 
|  | if (endCondition()) | 
|  | { | 
|  | pos = -1; | 
|  | return; | 
|  | } | 
|  | else | 
|  | { | 
|  | first = cast(ubyte)(t | (DecodeMap[range_.front] >> 2)); | 
|  | } | 
|  | break; | 
|  | case 2: | 
|  | immutable t = (DecodeMap[range_.front] & 0b11) << 6; | 
|  | range_.popFront(); | 
|  |  | 
|  | if (endCondition()) | 
|  | { | 
|  | pos = -1; | 
|  | return; | 
|  | } | 
|  | else | 
|  | { | 
|  | first = cast(ubyte)(t | DecodeMap[range_.front]); | 
|  | } | 
|  |  | 
|  | range_.popFront(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | ++pos %= 3; | 
|  | } | 
|  |  | 
|  |  | 
|  | static if (isForwardRange!Range) | 
|  | { | 
|  | /** | 
|  | * Saves the current iteration state. | 
|  | * | 
|  | * This method is only available if the underlying range is a | 
|  | * $(LINK2 std_range_primitives.html#isForwardRange, forward | 
|  | * range). | 
|  | * | 
|  | * Returns: A copy of $(D this). | 
|  | */ | 
|  | @property | 
|  | typeof(this) save() | 
|  | { | 
|  | auto decoder = this; | 
|  | decoder.range_ = decoder.range_.save; | 
|  | return decoder; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Construct a $(D Decoder) that iterates over the decoding of the given | 
|  | * Base64 encoded data. | 
|  | * | 
|  | * Params: | 
|  | *  range = An $(LINK2 std_range_primitives.html#isInputRange, input | 
|  | *      range) over the data to be decoded. | 
|  | * | 
|  | * Returns: | 
|  | *  If $(D_PARAM range) is a range of characters, a $(D Decoder) that | 
|  | *  iterates over the bytes of the corresponding Base64 decoding. | 
|  | * | 
|  | *  If $(D_PARAM range) is a range of ranges of characters, a $(D Decoder) | 
|  | *  that iterates over the decoded strings corresponding to each element of | 
|  | *  the range. In this case, the length of each subrange must be a multiple | 
|  | *  of 4; the returned _decoder does not keep track of Base64 decoding | 
|  | *  state across subrange boundaries. | 
|  | * | 
|  | *  In both cases, the returned $(D Decoder) will be a | 
|  | *  $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the | 
|  | *  given $(D range) is at least a forward range, otherwise it will be only | 
|  | *  an input range. | 
|  | * | 
|  | * If the input data contains characters not found in the base alphabet of | 
|  | * the current Base64 encoding scheme, the returned range may throw a | 
|  | * $(D Base64Exception). | 
|  | * | 
|  | * Example: | 
|  | * This example shows decoding over a range of input data lines. | 
|  | * ----- | 
|  | * foreach (decoded; Base64.decoder(stdin.byLine())) | 
|  | * { | 
|  | *     writeln(decoded); | 
|  | * } | 
|  | * ----- | 
|  | * | 
|  | * Example: | 
|  | * This example shows decoding one byte at a time. | 
|  | * ----- | 
|  | * auto encoded = Base64.encoder(cast(ubyte[])"0123456789"); | 
|  | * foreach (n; map!q{a - '0'}(Base64.decoder(encoded))) | 
|  | * { | 
|  | *     writeln(n); | 
|  | * } | 
|  | * ----- | 
|  | */ | 
|  | Decoder!(Range) decoder(Range)(Range range) if (isInputRange!Range) | 
|  | { | 
|  | return typeof(return)(range); | 
|  | } | 
|  |  | 
|  |  | 
|  | private: | 
|  | @safe | 
|  | pure int decodeChar()(char chr) | 
|  | { | 
|  | immutable val = DecodeMap[chr]; | 
|  |  | 
|  | // enforce can't be a pure function, so I use trivial check. | 
|  | if (val == 0 && chr != 'A') | 
|  | throw new Base64Exception("Invalid character: " ~ chr); | 
|  |  | 
|  | return val; | 
|  | } | 
|  |  | 
|  |  | 
|  | @safe | 
|  | pure int decodeChar()(dchar chr) | 
|  | { | 
|  | // See above comment. | 
|  | if (chr > 0x7f) | 
|  | throw new Base64Exception("Base64-encoded character must be a single byte"); | 
|  |  | 
|  | return decodeChar(cast(char) chr); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// | 
|  | @safe unittest | 
|  | { | 
|  | import std.string : representation; | 
|  |  | 
|  | // pre-defined: alias Base64 = Base64Impl!('+', '/'); | 
|  | ubyte[] emptyArr; | 
|  | assert(Base64.encode(emptyArr) == ""); | 
|  | assert(Base64.encode("f".representation) == "Zg=="); | 
|  | assert(Base64.encode("foo".representation) == "Zm9v"); | 
|  |  | 
|  | alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); | 
|  | assert(Base64Re.encode("f".representation) == "Zg"); | 
|  | assert(Base64Re.encode("foo".representation) == "Zm9v"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Exception thrown upon encountering Base64 encoding or decoding errors. | 
|  | */ | 
|  | class Base64Exception : Exception | 
|  | { | 
|  | @safe pure nothrow | 
|  | this(string s, string fn = __FILE__, size_t ln = __LINE__) | 
|  | { | 
|  | super(s, fn, ln); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// | 
|  | @system unittest | 
|  | { | 
|  | import std.exception : assertThrown; | 
|  | assertThrown!Base64Exception(Base64.decode("ab|c")); | 
|  | } | 
|  |  | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | import std.algorithm.comparison : equal; | 
|  | import std.algorithm.sorting : sort; | 
|  | import std.conv; | 
|  | import std.file; | 
|  | import std.stdio; | 
|  |  | 
|  | alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); | 
|  |  | 
|  | // Test vectors from RFC 4648 | 
|  | ubyte[][string] tv = [ | 
|  | ""      :cast(ubyte[])"", | 
|  | "f"     :cast(ubyte[])"f", | 
|  | "fo"    :cast(ubyte[])"fo", | 
|  | "foo"   :cast(ubyte[])"foo", | 
|  | "foob"  :cast(ubyte[])"foob", | 
|  | "fooba" :cast(ubyte[])"fooba", | 
|  | "foobar":cast(ubyte[])"foobar" | 
|  | ]; | 
|  |  | 
|  | { // Base64 | 
|  | // encode | 
|  | assert(Base64.encodeLength(tv[""].length)       == 0); | 
|  | assert(Base64.encodeLength(tv["f"].length)      == 4); | 
|  | assert(Base64.encodeLength(tv["fo"].length)     == 4); | 
|  | assert(Base64.encodeLength(tv["foo"].length)    == 4); | 
|  | assert(Base64.encodeLength(tv["foob"].length)   == 8); | 
|  | assert(Base64.encodeLength(tv["fooba"].length)  == 8); | 
|  | assert(Base64.encodeLength(tv["foobar"].length) == 8); | 
|  |  | 
|  | assert(Base64.encode(tv[""])       == ""); | 
|  | assert(Base64.encode(tv["f"])      == "Zg=="); | 
|  | assert(Base64.encode(tv["fo"])     == "Zm8="); | 
|  | assert(Base64.encode(tv["foo"])    == "Zm9v"); | 
|  | assert(Base64.encode(tv["foob"])   == "Zm9vYg=="); | 
|  | assert(Base64.encode(tv["fooba"])  == "Zm9vYmE="); | 
|  | assert(Base64.encode(tv["foobar"]) == "Zm9vYmFy"); | 
|  |  | 
|  | // decode | 
|  | assert(Base64.decodeLength(Base64.encode(tv[""]).length)       == 0); | 
|  | assert(Base64.decodeLength(Base64.encode(tv["f"]).length)      == 3); | 
|  | assert(Base64.decodeLength(Base64.encode(tv["fo"]).length)     == 3); | 
|  | assert(Base64.decodeLength(Base64.encode(tv["foo"]).length)    == 3); | 
|  | assert(Base64.decodeLength(Base64.encode(tv["foob"]).length)   == 6); | 
|  | assert(Base64.decodeLength(Base64.encode(tv["fooba"]).length)  == 6); | 
|  | assert(Base64.decodeLength(Base64.encode(tv["foobar"]).length) == 6); | 
|  |  | 
|  | assert(Base64.decode(Base64.encode(tv[""]))       == tv[""]); | 
|  | assert(Base64.decode(Base64.encode(tv["f"]))      == tv["f"]); | 
|  | assert(Base64.decode(Base64.encode(tv["fo"]))     == tv["fo"]); | 
|  | assert(Base64.decode(Base64.encode(tv["foo"]))    == tv["foo"]); | 
|  | assert(Base64.decode(Base64.encode(tv["foob"]))   == tv["foob"]); | 
|  | assert(Base64.decode(Base64.encode(tv["fooba"]))  == tv["fooba"]); | 
|  | assert(Base64.decode(Base64.encode(tv["foobar"])) == tv["foobar"]); | 
|  |  | 
|  | assertThrown!Base64Exception(Base64.decode("ab|c")); | 
|  |  | 
|  | // Test decoding incomplete strings. RFC does not specify the correct | 
|  | // behavior, but the code should never throw Errors on invalid input. | 
|  |  | 
|  | // decodeLength is nothrow | 
|  | assert(Base64.decodeLength(1) == 0); | 
|  | assert(Base64.decodeLength(2) <= 1); | 
|  | assert(Base64.decodeLength(3) <= 2); | 
|  |  | 
|  | // may throw Exceptions, may not throw Errors | 
|  | assertThrown!Base64Exception(Base64.decode("Zg")); | 
|  | assertThrown!Base64Exception(Base64.decode("Zg=")); | 
|  | assertThrown!Base64Exception(Base64.decode("Zm8")); | 
|  | assertThrown!Base64Exception(Base64.decode("Zg==;")); | 
|  | } | 
|  |  | 
|  | { // No padding | 
|  | // encode | 
|  | assert(Base64Re.encodeLength(tv[""].length)       == 0); | 
|  | assert(Base64Re.encodeLength(tv["f"].length)      == 2); | 
|  | assert(Base64Re.encodeLength(tv["fo"].length)     == 3); | 
|  | assert(Base64Re.encodeLength(tv["foo"].length)    == 4); | 
|  | assert(Base64Re.encodeLength(tv["foob"].length)   == 6); | 
|  | assert(Base64Re.encodeLength(tv["fooba"].length)  == 7); | 
|  | assert(Base64Re.encodeLength(tv["foobar"].length) == 8); | 
|  |  | 
|  | assert(Base64Re.encode(tv[""])       == ""); | 
|  | assert(Base64Re.encode(tv["f"])      == "Zg"); | 
|  | assert(Base64Re.encode(tv["fo"])     == "Zm8"); | 
|  | assert(Base64Re.encode(tv["foo"])    == "Zm9v"); | 
|  | assert(Base64Re.encode(tv["foob"])   == "Zm9vYg"); | 
|  | assert(Base64Re.encode(tv["fooba"])  == "Zm9vYmE"); | 
|  | assert(Base64Re.encode(tv["foobar"]) == "Zm9vYmFy"); | 
|  |  | 
|  | // decode | 
|  | assert(Base64Re.decodeLength(Base64Re.encode(tv[""]).length)       == 0); | 
|  | assert(Base64Re.decodeLength(Base64Re.encode(tv["f"]).length)      == 1); | 
|  | assert(Base64Re.decodeLength(Base64Re.encode(tv["fo"]).length)     == 2); | 
|  | assert(Base64Re.decodeLength(Base64Re.encode(tv["foo"]).length)    == 3); | 
|  | assert(Base64Re.decodeLength(Base64Re.encode(tv["foob"]).length)   == 4); | 
|  | assert(Base64Re.decodeLength(Base64Re.encode(tv["fooba"]).length)  == 5); | 
|  | assert(Base64Re.decodeLength(Base64Re.encode(tv["foobar"]).length) == 6); | 
|  |  | 
|  | assert(Base64Re.decode(Base64Re.encode(tv[""]))       == tv[""]); | 
|  | assert(Base64Re.decode(Base64Re.encode(tv["f"]))      == tv["f"]); | 
|  | assert(Base64Re.decode(Base64Re.encode(tv["fo"]))     == tv["fo"]); | 
|  | assert(Base64Re.decode(Base64Re.encode(tv["foo"]))    == tv["foo"]); | 
|  | assert(Base64Re.decode(Base64Re.encode(tv["foob"]))   == tv["foob"]); | 
|  | assert(Base64Re.decode(Base64Re.encode(tv["fooba"]))  == tv["fooba"]); | 
|  | assert(Base64Re.decode(Base64Re.encode(tv["foobar"])) == tv["foobar"]); | 
|  |  | 
|  | // decodeLength is nothrow | 
|  | assert(Base64.decodeLength(1) == 0); | 
|  | } | 
|  |  | 
|  | { // with OutputRange | 
|  | import std.array; | 
|  |  | 
|  | auto a = Appender!(char[])([]); | 
|  | auto b = Appender!(ubyte[])([]); | 
|  |  | 
|  | assert(Base64.encode(tv[""], a) == 0); | 
|  | assert(Base64.decode(a.data, b) == 0); | 
|  | assert(tv[""] == b.data); a.clear(); b.clear(); | 
|  |  | 
|  | assert(Base64.encode(tv["f"], a) == 4); | 
|  | assert(Base64.decode(a.data,  b) == 1); | 
|  | assert(tv["f"] == b.data); a.clear(); b.clear(); | 
|  |  | 
|  | assert(Base64.encode(tv["fo"], a) == 4); | 
|  | assert(Base64.decode(a.data,   b) == 2); | 
|  | assert(tv["fo"] == b.data); a.clear(); b.clear(); | 
|  |  | 
|  | assert(Base64.encode(tv["foo"], a) == 4); | 
|  | assert(Base64.decode(a.data,    b) == 3); | 
|  | assert(tv["foo"] == b.data); a.clear(); b.clear(); | 
|  |  | 
|  | assert(Base64.encode(tv["foob"], a) == 8); | 
|  | assert(Base64.decode(a.data,     b) == 4); | 
|  | assert(tv["foob"] == b.data); a.clear(); b.clear(); | 
|  |  | 
|  | assert(Base64.encode(tv["fooba"], a) == 8); | 
|  | assert(Base64.decode(a.data, b)      == 5); | 
|  | assert(tv["fooba"] == b.data); a.clear(); b.clear(); | 
|  |  | 
|  | assert(Base64.encode(tv["foobar"], a) == 8); | 
|  | assert(Base64.decode(a.data, b)       == 6); | 
|  | assert(tv["foobar"] == b.data); a.clear(); b.clear(); | 
|  | } | 
|  |  | 
|  | // @@@9543@@@ These tests were disabled because they actually relied on the input range having length. | 
|  | // The implementation (currently) doesn't support encoding/decoding from a length-less source. | 
|  | version (none) | 
|  | { // with InputRange | 
|  | // InputRange to ubyte[] or char[] | 
|  | auto encoded = Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"])); | 
|  | assert(encoded == "FPucA9l+"); | 
|  | assert(Base64.decode(map!q{a}(encoded)) == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); | 
|  |  | 
|  | // InputRange to OutputRange | 
|  | auto a = Appender!(char[])([]); | 
|  | auto b = Appender!(ubyte[])([]); | 
|  | assert(Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]), a) == 8); | 
|  | assert(a.data == "FPucA9l+"); | 
|  | assert(Base64.decode(map!q{a}(a.data), b) == 6); | 
|  | assert(b.data == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); | 
|  | } | 
|  |  | 
|  | { // Encoder and Decoder | 
|  | { | 
|  | string encode_file = std.file.deleteme ~ "-testingEncoder"; | 
|  | std.file.write(encode_file, "\nf\nfo\nfoo\nfoob\nfooba\nfoobar"); | 
|  |  | 
|  | auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]; | 
|  | auto f = File(encode_file); | 
|  | scope(exit) | 
|  | { | 
|  | f.close(); | 
|  | assert(!f.isOpen); | 
|  | std.file.remove(encode_file); | 
|  | } | 
|  |  | 
|  | size_t i; | 
|  | foreach (encoded; Base64.encoder(f.byLine())) | 
|  | assert(encoded == witness[i++]); | 
|  |  | 
|  | assert(i == witness.length); | 
|  | } | 
|  |  | 
|  | { | 
|  | string decode_file = std.file.deleteme ~ "-testingDecoder"; | 
|  | std.file.write(decode_file, "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy"); | 
|  |  | 
|  | auto witness = sort(tv.keys); | 
|  | auto f = File(decode_file); | 
|  | scope(exit) | 
|  | { | 
|  | f.close(); | 
|  | assert(!f.isOpen); | 
|  | std.file.remove(decode_file); | 
|  | } | 
|  |  | 
|  | size_t i; | 
|  | foreach (decoded; Base64.decoder(f.byLine())) | 
|  | assert(decoded == witness[i++]); | 
|  |  | 
|  | assert(i == witness.length); | 
|  | } | 
|  |  | 
|  | { // ForwardRange | 
|  | { | 
|  | auto encoder = Base64.encoder(sort(tv.values)); | 
|  | auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]; | 
|  | size_t i; | 
|  |  | 
|  | assert(encoder.front == witness[i++]); encoder.popFront(); | 
|  | assert(encoder.front == witness[i++]); encoder.popFront(); | 
|  | assert(encoder.front == witness[i++]); encoder.popFront(); | 
|  |  | 
|  | foreach (encoded; encoder.save) | 
|  | assert(encoded == witness[i++]); | 
|  | } | 
|  |  | 
|  | { | 
|  | auto decoder = Base64.decoder(["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]); | 
|  | auto witness = sort(tv.values); | 
|  | size_t i; | 
|  |  | 
|  | assert(decoder.front == witness[i++]); decoder.popFront(); | 
|  | assert(decoder.front == witness[i++]); decoder.popFront(); | 
|  | assert(decoder.front == witness[i++]); decoder.popFront(); | 
|  |  | 
|  | foreach (decoded; decoder.save) | 
|  | assert(decoded == witness[i++]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | { // Encoder and Decoder for single character encoding and decoding | 
|  | alias Base64NoPadding = Base64Impl!('+', '/', Base64.NoPadding); | 
|  |  | 
|  | auto tests = [ | 
|  | ""       : ["", "", "", ""], | 
|  | "f"      : ["Zg==", "Zg==", "Zg", "Zg"], | 
|  | "fo"     : ["Zm8=", "Zm8=", "Zm8", "Zm8"], | 
|  | "foo"    : ["Zm9v", "Zm9v", "Zm9v", "Zm9v"], | 
|  | "foob"   : ["Zm9vYg==", "Zm9vYg==", "Zm9vYg", "Zm9vYg"], | 
|  | "fooba"  : ["Zm9vYmE=", "Zm9vYmE=", "Zm9vYmE", "Zm9vYmE"], | 
|  | "foobar" : ["Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy"], | 
|  | ]; | 
|  |  | 
|  | foreach (u, e; tests) | 
|  | { | 
|  | assert(equal(Base64.encoder(cast(ubyte[]) u), e[0])); | 
|  | assert(equal(Base64.decoder(Base64.encoder(cast(ubyte[]) u)), u)); | 
|  |  | 
|  | assert(equal(Base64URL.encoder(cast(ubyte[]) u), e[1])); | 
|  | assert(equal(Base64URL.decoder(Base64URL.encoder(cast(ubyte[]) u)), u)); | 
|  |  | 
|  | assert(equal(Base64NoPadding.encoder(cast(ubyte[]) u), e[2])); | 
|  | assert(equal(Base64NoPadding.decoder(Base64NoPadding.encoder(cast(ubyte[]) u)), u)); | 
|  |  | 
|  | assert(equal(Base64Re.encoder(cast(ubyte[]) u), e[3])); | 
|  | assert(equal(Base64Re.decoder(Base64Re.encoder(cast(ubyte[]) u)), u)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Regression control for the output range ref bug in encode. | 
|  | @system unittest | 
|  | { | 
|  | struct InputRange | 
|  | { | 
|  | ubyte[] impl = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; | 
|  | @property bool empty() { return impl.length == 0; } | 
|  | @property ubyte front() { return impl[0]; } | 
|  | void popFront() { impl = impl[1 .. $]; } | 
|  | @property size_t length() { return impl.length; } | 
|  | } | 
|  |  | 
|  | struct OutputRange | 
|  | { | 
|  | char[] result; | 
|  | void put(char b) { result ~= b; } | 
|  | } | 
|  |  | 
|  | InputRange ir; | 
|  | OutputRange or; | 
|  | assert(Base64.encode(ir, or) == 8); | 
|  | assert(or.result == "Gis8TV1u"); | 
|  |  | 
|  | // Verify that any existing workaround that uses & still works. | 
|  | InputRange ir2; | 
|  | OutputRange or2; | 
|  | assert(Base64.encode(ir2, &or2) == 8); | 
|  | assert(or2.result == "Gis8TV1u"); | 
|  | } | 
|  |  | 
|  | // Regression control for the output range ref bug in decode. | 
|  | @system unittest | 
|  | { | 
|  | struct InputRange | 
|  | { | 
|  | const(char)[] impl = "Gis8TV1u"; | 
|  | @property bool empty() { return impl.length == 0; } | 
|  | @property dchar front() { return impl[0]; } | 
|  | void popFront() { impl = impl[1 .. $]; } | 
|  | @property size_t length() { return impl.length; } | 
|  | } | 
|  |  | 
|  | struct OutputRange | 
|  | { | 
|  | ubyte[] result; | 
|  | void put(ubyte b) { result ~= b; } | 
|  | } | 
|  |  | 
|  | InputRange ir; | 
|  | OutputRange or; | 
|  | assert(Base64.decode(ir, or) == 6); | 
|  | assert(or.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); | 
|  |  | 
|  | // Verify that any existing workaround that uses & still works. | 
|  | InputRange ir2; | 
|  | OutputRange or2; | 
|  | assert(Base64.decode(ir2, &or2) == 6); | 
|  | assert(or2.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); | 
|  | } |