aboutsummaryrefslogtreecommitdiff
path: root/libphobos/src/std/base64.d
diff options
context:
space:
mode:
authorIain Buclaw <ibuclaw@gcc.gnu.org>2018-10-28 19:51:47 +0000
committerIain Buclaw <ibuclaw@gcc.gnu.org>2018-10-28 19:51:47 +0000
commitb4c522fabd0df7be08882d2207df8b2765026110 (patch)
treeb5ffc312b0a441c1ba24323152aec463fdbe5e9f /libphobos/src/std/base64.d
parent01ce9e31a02c8039d88e90f983735104417bf034 (diff)
downloadgcc-b4c522fabd0df7be08882d2207df8b2765026110.zip
gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.gz
gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.bz2
Add D front-end, libphobos library, and D2 testsuite.
ChangeLog: * Makefile.def (target_modules): Add libphobos. (flags_to_pass): Add GDC, GDCFLAGS, GDC_FOR_TARGET and GDCFLAGS_FOR_TARGET. (dependencies): Make libphobos depend on libatomic, libbacktrace configure, and zlib configure. (language): Add language d. * Makefile.in: Rebuild. * Makefile.tpl (BUILD_EXPORTS): Add GDC and GDCFLAGS. (HOST_EXPORTS): Add GDC. (POSTSTAGE1_HOST_EXPORTS): Add GDC and GDC_FOR_BUILD. (BASE_TARGET_EXPORTS): Add GDC. (GDC_FOR_BUILD, GDC, GDCFLAGS): New variables. (GDC_FOR_TARGET, GDC_FLAGS_FOR_TARGET): New variables. (EXTRA_HOST_FLAGS): Add GDC. (STAGE1_FLAGS_TO_PASS): Add GDC. (EXTRA_TARGET_FLAGS): Add GDC and GDCFLAGS. * config-ml.in: Treat GDC and GDCFLAGS like other compiler/flag environment variables. * configure: Rebuild. * configure.ac: Add target-libphobos to target_libraries. Set and substitute GDC_FOR_BUILD and GDC_FOR_TARGET. config/ChangeLog: * multi.m4: Set GDC. gcc/ChangeLog: * Makefile.in (tm_d_file_list, tm_d_include_list): New variables. (TM_D_H, D_TARGET_DEF, D_TARGET_H, D_TARGET_OBJS): New variables. (tm_d.h, cs-tm_d.h, default-d.o): New rules. (d/d-target-hooks-def.h, s-d-target-hooks-def-h): New rules. (s-tm-texi): Also check timestamp on d-target.def. (generated_files): Add TM_D_H and d-target-hooks-def.h. (build/genhooks.o): Also depend on D_TARGET_DEF. * config.gcc (tm_d_file, d_target_objs, target_has_targetdm): New variables. * config/aarch64/aarch64-d.c: New file. * config/aarch64/aarch64-linux.h (GNU_USER_TARGET_D_CRITSEC_SIZE): Define. * config/aarch64/aarch64-protos.h (aarch64_d_target_versions): New prototype. * config/aarch64/aarch64.h (TARGET_D_CPU_VERSIONS): Define. * config/aarch64/t-aarch64 (aarch64-d.o): New rule. * config/arm/arm-d.c: New file. * config/arm/arm-protos.h (arm_d_target_versions): New prototype. * config/arm/arm.h (TARGET_D_CPU_VERSIONS): Define. * config/arm/linux-eabi.h (EXTRA_TARGET_D_OS_VERSIONS): Define. * config/arm/t-arm (arm-d.o): New rule. * config/default-d.c: New file. * config/glibc-d.c: New file. * config/gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/i386/i386-d.c: New file. * config/i386/i386-protos.h (ix86_d_target_versions): New prototype. * config/i386/i386.h (TARGET_D_CPU_VERSIONS): Define. * config/i386/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define. (GNU_USER_TARGET_D_CRITSEC_SIZE): Define. * config/i386/t-i386 (i386-d.o): New rule. * config/kfreebsd-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/kopensolaris-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/linux-android.h (ANDROID_TARGET_D_OS_VERSIONS): Define. * config/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/mips/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define. * config/mips/mips-d.c: New file. * config/mips/mips-protos.h (mips_d_target_versions): New prototype. * config/mips/mips.h (TARGET_D_CPU_VERSIONS): Define. * config/mips/t-mips (mips-d.o): New rule. * config/powerpcspe/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/powerpcspe/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/powerpcspe/powerpcspe-d.c: New file. * config/powerpcspe/powerpcspe-protos.h (rs6000_d_target_versions): New prototype. * config/powerpcspe/powerpcspe.c (rs6000_output_function_epilogue): Support GNU D by using 0 as the language type. * config/powerpcspe/powerpcspe.h (TARGET_D_CPU_VERSIONS): Define. * config/powerpcspe/t-powerpcspe (powerpcspe-d.o): New rule. * config/riscv/riscv-d.c: New file. * config/riscv/riscv-protos.h (riscv_d_target_versions): New prototype. * config/riscv/riscv.h (TARGET_D_CPU_VERSIONS): Define. * config/riscv/t-riscv (riscv-d.o): New rule. * config/rs6000/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/rs6000/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/rs6000/rs6000-d.c: New file. * config/rs6000/rs6000-protos.h (rs6000_d_target_versions): New prototype. * config/rs6000/rs6000.c (rs6000_output_function_epilogue): Support GNU D by using 0 as the language type. * config/rs6000/rs6000.h (TARGET_D_CPU_VERSIONS): Define. * config/rs6000/t-rs6000 (rs6000-d.o): New rule. * config/s390/s390-d.c: New file. * config/s390/s390-protos.h (s390_d_target_versions): New prototype. * config/s390/s390.h (TARGET_D_CPU_VERSIONS): Define. * config/s390/t-s390 (s390-d.o): New rule. * config/sparc/sparc-d.c: New file. * config/sparc/sparc-protos.h (sparc_d_target_versions): New prototype. * config/sparc/sparc.h (TARGET_D_CPU_VERSIONS): Define. * config/sparc/t-sparc (sparc-d.o): New rule. * config/t-glibc (glibc-d.o): New rule. * configure: Regenerated. * configure.ac (tm_d_file): New variable. (tm_d_file_list, tm_d_include_list, d_target_objs): Add substitutes. * doc/contrib.texi (Contributors): Add self for the D frontend. * doc/frontends.texi (G++ and GCC): Mention D as a supported language. * doc/install.texi (Configuration): Mention libphobos as an option for --enable-shared. Mention d as an option for --enable-languages. (Testing): Mention check-d as a target. * doc/invoke.texi (Overall Options): Mention .d, .dd, and .di as file name suffixes. Mention d as a -x option. * doc/sourcebuild.texi (Top Level): Mention libphobos. * doc/standards.texi (Standards): Add section on D language. * doc/tm.texi: Regenerated. * doc/tm.texi.in: Add @node for D language and ABI, and @hook for TARGET_CPU_VERSIONS, TARGET_D_OS_VERSIONS, and TARGET_D_CRITSEC_SIZE. * dwarf2out.c (is_dlang): New function. (gen_compile_unit_die): Use DW_LANG_D for D. (declare_in_namespace): Return module die for D, instead of adding extra declarations into the namespace. (gen_namespace_die): Generate DW_TAG_module for D. (gen_decl_die): Handle CONST_DECLSs for D. (dwarf2out_decl): Likewise. (prune_unused_types_walk_local_classes): Handle DW_tag_interface_type. (prune_unused_types_walk): Handle DW_tag_interface_type same as other kinds of aggregates. * gcc.c (default_compilers): Add entries for .d, .dd and .di. * genhooks.c: Include d/d-target.def. gcc/po/ChangeLog: * EXCLUDES: Add sources from d/dmd. gcc/testsuite/ChangeLog: * gcc.misc-tests/help.exp: Add D to option descriptions check. * gdc.dg/asan/asan.exp: New file. * gdc.dg/asan/gdc272.d: New test. * gdc.dg/compilable.d: New test. * gdc.dg/dg.exp: New file. * gdc.dg/gdc254.d: New test. * gdc.dg/gdc260.d: New test. * gdc.dg/gdc270a.d: New test. * gdc.dg/gdc270b.d: New test. * gdc.dg/gdc282.d: New test. * gdc.dg/gdc283.d: New test. * gdc.dg/imports/gdc170.d: New test. * gdc.dg/imports/gdc231.d: New test. * gdc.dg/imports/gdc239.d: New test. * gdc.dg/imports/gdc241a.d: New test. * gdc.dg/imports/gdc241b.d: New test. * gdc.dg/imports/gdc251a.d: New test. * gdc.dg/imports/gdc251b.d: New test. * gdc.dg/imports/gdc253.d: New test. * gdc.dg/imports/gdc254a.d: New test. * gdc.dg/imports/gdc256.d: New test. * gdc.dg/imports/gdc27.d: New test. * gdc.dg/imports/gdcpkg256/package.d: New test. * gdc.dg/imports/runnable.d: New test. * gdc.dg/link.d: New test. * gdc.dg/lto/lto.exp: New file. * gdc.dg/lto/ltotests_0.d: New test. * gdc.dg/lto/ltotests_1.d: New test. * gdc.dg/runnable.d: New test. * gdc.dg/simd.d: New test. * gdc.test/gdc-test.exp: New file. * lib/gdc-dg.exp: New file. * lib/gdc.exp: New file. libphobos/ChangeLog: * Makefile.am: New file. * Makefile.in: New file. * acinclude.m4: New file. * aclocal.m4: New file. * config.h.in: New file. * configure: New file. * configure.ac: New file. * d_rules.am: New file. * libdruntime/Makefile.am: New file. * libdruntime/Makefile.in: New file. * libdruntime/__entrypoint.di: New file. * libdruntime/__main.di: New file. * libdruntime/gcc/attribute.d: New file. * libdruntime/gcc/backtrace.d: New file. * libdruntime/gcc/builtins.d: New file. * libdruntime/gcc/config.d.in: New file. * libdruntime/gcc/deh.d: New file. * libdruntime/gcc/libbacktrace.d.in: New file. * libdruntime/gcc/unwind/arm.d: New file. * libdruntime/gcc/unwind/arm_common.d: New file. * libdruntime/gcc/unwind/c6x.d: New file. * libdruntime/gcc/unwind/generic.d: New file. * libdruntime/gcc/unwind/package.d: New file. * libdruntime/gcc/unwind/pe.d: New file. * m4/autoconf.m4: New file. * m4/druntime.m4: New file. * m4/druntime/cpu.m4: New file. * m4/druntime/libraries.m4: New file. * m4/druntime/os.m4: New file. * m4/gcc_support.m4: New file. * m4/gdc.m4: New file. * m4/libtool.m4: New file. * src/Makefile.am: New file. * src/Makefile.in: New file. * src/libgphobos.spec.in: New file. * testsuite/Makefile.am: New file. * testsuite/Makefile.in: New file. * testsuite/config/default.exp: New file. * testsuite/lib/libphobos-dg.exp: New file. * testsuite/lib/libphobos.exp: New file. * testsuite/testsuite_flags.in: New file. From-SVN: r265573
Diffstat (limited to 'libphobos/src/std/base64.d')
-rw-r--r--libphobos/src/std/base64.d2099
1 files changed, 2099 insertions, 0 deletions
diff --git a/libphobos/src/std/base64.d b/libphobos/src/std/base64.d
new file mode 100644
index 0000000..6211d10
--- /dev/null
+++ b/libphobos/src/std/base64.d
@@ -0,0 +1,2099 @@
+// 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]);
+}