diff options
author | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
commit | b4c522fabd0df7be08882d2207df8b2765026110 (patch) | |
tree | b5ffc312b0a441c1ba24323152aec463fdbe5e9f /libphobos/src/std/base64.d | |
parent | 01ce9e31a02c8039d88e90f983735104417bf034 (diff) | |
download | gcc-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.d | 2099 |
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]); +} |