// 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">`$2`</a> */module std.base64; import std.exception : enforce;import std.range.primitives : empty, front, isInputRange, isOutputRange, isForwardRange, ElementType, hasLength, popFront, put, save;import std.traits : isArray; // Make sure module header code examples work correctly.pure @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!('+', '/'); ///pure @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!('-', '_'); ///pure @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); ///pure @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 `Padding` parameter is * set to `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 @nogc 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 `char[]` buffer using Base64 * encoding. * * Params: * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) * to _encode. * buffer = The `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)(const scope R1 source, return scope 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"); } do { 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]; } /// @nogc nothrow @safe unittest { ubyte[6] 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"); } do { 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 (StdUnittest) 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 * $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) using * Base64 encoding. * * Params: * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) * to _encode. * range = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) * to store the encoded result. * * Returns: * The number of times the output range's `put` method was invoked. */ size_t encode(E, R)(scope const(E)[] source, auto ref R range) if (is(E : ubyte) && isOutputRange!(R, char) && !is(R == char[])) out(result) { assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); } do { immutable srcLen = source.length; if (srcLen == 0) return 0; immutable blocks = srcLen / 3; immutable remain = srcLen % 3; auto s = source; // copy for out contract length check size_t pcount; foreach (Unused; 0 .. blocks) { immutable val = s[0] << 16 | s[1] << 8 | s[2]; put(range, EncodeMap[val >> 18 ]); put(range, EncodeMap[val >> 12 & 0x3f]); put(range, EncodeMap[val >> 6 & 0x3f]); put(range, EncodeMap[val & 0x3f]); s = s[3 .. $]; pcount += 4; } if (remain) { immutable val = s[0] << 16 | (remain == 2 ? s[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; } /// @safe pure nothrow unittest { import std.array : appender; auto output = appender!string(); ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; // This overload of encode() returns the number of calls to the output // range's put method. assert(Base64.encode(data, output) == 8); assert(output.data == "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)) { 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; }Lines 1–500 of 2202. Load more
// 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">`$2`</a> */ module std.base64; import std.exception : enforce; import std.range.primitives : empty, front, isInputRange, isOutputRange, isForwardRange, ElementType, hasLength, popFront, put, save; import std.traits : isArray; // Make sure module header code examples work correctly. pure @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!('+', '/'); /// pure @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!('-', '_'); /// pure @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); /// pure @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 `Padding` parameter is * set to `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 @nogc 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 `char[]` buffer using Base64 * encoding. * * Params: * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) * to _encode. * buffer = The `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)(const scope R1 source, return scope 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"); } do { 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]; } /// @nogc nothrow @safe unittest { ubyte[6] 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"); } do { 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 (StdUnittest) 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 * $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) using * Base64 encoding. * * Params: * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) * to _encode. * range = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) * to store the encoded result. * * Returns: * The number of times the output range's `put` method was invoked. */ size_t encode(E, R)(scope const(E)[] source, auto ref R range) if (is(E : ubyte) && isOutputRange!(R, char) && !is(R == char[])) out(result) { assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); } do { immutable srcLen = source.length; if (srcLen == 0) return 0; immutable blocks = srcLen / 3; immutable remain = srcLen % 3; auto s = source; // copy for out contract length check size_t pcount; foreach (Unused; 0 .. blocks) { immutable val = s[0] << 16 | s[1] << 8 | s[2]; put(range, EncodeMap[val >> 18 ]); put(range, EncodeMap[val >> 12 & 0x3f]); put(range, EncodeMap[val >> 6 & 0x3f]); put(range, EncodeMap[val & 0x3f]); s = s[3 .. $]; pcount += 4; } if (remain) { immutable val = s[0] << 16 | (remain == 2 ? s[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; } /// @safe pure nothrow unittest { import std.array : appender; auto output = appender!string(); ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; // This overload of encode() returns the number of calls to the output // range's put method. assert(Base64.encode(data, output) == 8); assert(output.data == "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)) { 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; }