PageRenderTime 165ms CodeModel.GetById 14ms app.highlight 140ms RepoModel.GetById 1ms app.codeStats 1ms

/std/base64.d

http://github.com/jcd/phobos
D | 1675 lines | 1004 code | 282 blank | 389 comment | 330 complexity | 52732e39f98cc36bce30f32568b31963 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1// Written in the D programming language.
   2
   3/**
   4 * Encoding / Decoding Base64 format.
   5 *
   6 * Implemented according to $(WEB tools.ietf.org/html/rfc4648,
   7 * RFC 4648 - The Base16, Base32, and Base64 Data Encodings).
   8 *
   9 * Example:
  10 * -----
  11 * ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e];
  12 *
  13 * const(char)[] encoded = Base64.encode(data);
  14 * assert(encoded == "FPucA9l+");
  15 *
  16 * ubyte[] decoded = Base64.decode("FPucA9l+");
  17 * assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
  18 * -----
  19 *
  20 * Support Range interface using Encoder / Decoder.
  21 *
  22 * Example:
  23 * -----
  24 * // Create MIME Base64 with CRLF, per line 76.
  25 * File f = File("./text.txt", "r");
  26 * scope(exit) f.close();
  27 *
  28 * Appender!string mime64 = appender!string;
  29 *
  30 * foreach (encoded; Base64.encoder(f.byChunk(57)))
  31 * {
  32 *     mime64.put(encoded);
  33 *     mime64.put("\r\n");
  34 * }
  35 *
  36 * writeln(mime64.data);
  37 * -----
  38 *
  39 * Copyright: Masahiro Nakagawa 2010-.
  40 * License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
  41 * Authors:   Masahiro Nakagawa, Daniel Murphy (Single value Encoder and Decoder)
  42 * Source:    $(PHOBOSSRC std/_base64.d)
  43 */
  44module std.base64;
  45
  46import std.exception;  // enforce
  47import std.range;      // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength
  48import std.traits;     // isArray
  49
  50version(unittest) import std.algorithm, std.conv, std.file, std.stdio;
  51
  52
  53/**
  54 * The Base64
  55 */
  56alias Base64Impl!('+', '/') Base64;
  57
  58
  59/**
  60 * The "URL and Filename safe" Base64
  61 */
  62alias Base64Impl!('-', '_') Base64URL;
  63
  64
  65/**
  66 * Core implementation for Base64 format.
  67 *
  68 * Example:
  69 * -----
  70 * alias Base64Impl!('+', '/')                   Base64;    // The Base64 format(Already defined).
  71 * alias Base64Impl!('!', '=', Base64.NoPadding) Base64Re;  // non-standard Base64 format for Regular expression
  72 * -----
  73 *
  74 * NOTE:
  75 *  encoded-string doesn't have padding character if set Padding parameter to NoPadding.
  76 */
  77template Base64Impl(char Map62th, char Map63th, char Padding = '=')
  78{
  79    enum NoPadding = '\0';  /// represents no-padding encoding
  80
  81
  82    // Verify Base64 characters
  83    static assert(Map62th < 'A' || Map62th > 'Z', "Character '" ~ Map62th ~ "' cannot be used twice");
  84    static assert(Map63th < 'A' || Map63th > 'Z', "Character '" ~ Map63th ~ "' cannot be used twice");
  85    static assert(Padding < 'A' || Padding > 'Z', "Character '" ~ Padding ~ "' cannot be used twice");
  86    static assert(Map62th < 'a' || Map62th > 'z', "Character '" ~ Map62th ~ "' cannot be used twice");
  87    static assert(Map63th < 'a' || Map63th > 'z', "Character '" ~ Map63th ~ "' cannot be used twice");
  88    static assert(Padding < 'a' || Padding > 'z', "Character '" ~ Padding ~ "' cannot be used twice");
  89    static assert(Map62th < '0' || Map62th > '9', "Character '" ~ Map62th ~ "' cannot be used twice");
  90    static assert(Map63th < '0' || Map63th > '9', "Character '" ~ Map63th ~ "' cannot be used twice");
  91    static assert(Padding < '0' || Padding > '9', "Character '" ~ Padding ~ "' cannot be used twice");
  92    static assert(Map62th != Map63th, "Character '" ~ Map63th ~ "' cannot be used twice");
  93    static assert(Map62th != Padding, "Character '" ~ Padding ~ "' cannot be used twice");
  94    static assert(Map63th != Padding, "Character '" ~ Padding ~ "' cannot be used twice");
  95    static assert(Map62th != NoPadding, "'\\0' is not a valid Base64character");
  96    static assert(Map63th != NoPadding, "'\\0' is not a valid Base64character");
  97
  98
  99    /* Encode functions */
 100
 101
 102    private immutable EncodeMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ~ Map62th ~ Map63th;
 103
 104
 105    /**
 106     * Calculates the minimum length for encoding.
 107     *
 108     * Params:
 109     *  sourceLength = the length of source array.
 110     *
 111     * Returns:
 112     *  the calculated length using $(D_PARAM sourceLength).
 113     */
 114    @safe
 115    pure nothrow size_t encodeLength(in size_t sourceLength)
 116    {
 117        static if (Padding == NoPadding)
 118            return (sourceLength / 3) * 4 + (sourceLength % 3 == 0 ? 0 : sourceLength % 3 == 1 ? 2 : 3);
 119        else
 120            return (sourceLength / 3 + (sourceLength % 3 ? 1 : 0)) * 4;
 121    }
 122
 123
 124    // ubyte[] to char[]
 125
 126
 127    /**
 128     * Encodes $(D_PARAM source) into $(D_PARAM buffer).
 129     *
 130     * Params:
 131     *  source = an $(D InputRange) to encode.
 132     *  buffer = a buffer to store encoded result.
 133     *
 134     * Returns:
 135     *  the encoded string that slices buffer.
 136     */
 137    @trusted
 138    pure char[] encode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : ubyte) &&
 139                                                            is(R2 == char[]))
 140    in
 141    {
 142        assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding");
 143    }
 144    out(result)
 145    {
 146        assert(result.length == encodeLength(source.length), "The length of result is different from Base64");
 147    }
 148    body
 149    {
 150        immutable srcLen = source.length;
 151        if (srcLen == 0)
 152            return [];
 153
 154        immutable blocks = srcLen / 3;
 155        immutable remain = srcLen % 3;
 156        auto      bufptr = buffer.ptr;
 157        auto      srcptr = source.ptr;
 158
 159        foreach (Unused; 0..blocks) {
 160            immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2];
 161            *bufptr++ = EncodeMap[val >> 18       ];
 162            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
 163            *bufptr++ = EncodeMap[val >>  6 & 0x3f];
 164            *bufptr++ = EncodeMap[val       & 0x3f];
 165            srcptr += 3;
 166        }
 167
 168        if (remain) {
 169            immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0);
 170            *bufptr++ = EncodeMap[val >> 18       ];
 171            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
 172
 173            final switch (remain) {
 174            case 2:
 175                *bufptr++ = EncodeMap[val >> 6 & 0x3f];
 176                static if (Padding != NoPadding)
 177                    *bufptr++ = Padding;
 178                break;
 179            case 1:
 180                static if (Padding != NoPadding) {
 181                    *bufptr++ = Padding;
 182                    *bufptr++ = Padding;
 183                }
 184                break;
 185            }
 186        }
 187
 188        // encode method can't assume buffer length. So, slice needed.
 189        return buffer[0..bufptr - buffer.ptr];
 190    }
 191
 192
 193    // InputRange to char[]
 194
 195
 196    /**
 197     * ditto
 198     */
 199    char[] encode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 &&
 200                                                    is(ElementType!R1 : ubyte) && hasLength!R1 &&
 201                                                    is(R2 == char[]))
 202    in
 203    {
 204        assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding");
 205    }
 206    out(result)
 207    {
 208        // @@@BUG@@@ D's DbC can't caputre an argument of function and store the result of precondition.
 209        //assert(result.length == encodeLength(source.length), "The length of result is different from Base64");
 210    }
 211    body
 212    {
 213        immutable srcLen = source.length;
 214        if (srcLen == 0)
 215            return [];
 216
 217        immutable blocks = srcLen / 3;
 218        immutable remain = srcLen % 3;
 219        auto      bufptr = buffer.ptr;
 220
 221        foreach (Unused; 0..blocks) {
 222            immutable v1 = source.front; source.popFront();
 223            immutable v2 = source.front; source.popFront();
 224            immutable v3 = source.front; source.popFront();
 225            immutable val = v1 << 16 | v2 << 8 | v3;
 226            *bufptr++ = EncodeMap[val >> 18       ];
 227            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
 228            *bufptr++ = EncodeMap[val >>  6 & 0x3f];
 229            *bufptr++ = EncodeMap[val       & 0x3f];
 230        }
 231
 232        if (remain) {
 233            size_t val = source.front << 16;
 234            if (remain == 2) {
 235                source.popFront();
 236                val |= source.front << 8;
 237            }
 238
 239            *bufptr++ = EncodeMap[val >> 18       ];
 240            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
 241
 242            final switch (remain) {
 243            case 2:
 244                *bufptr++ = EncodeMap[val >> 6 & 0x3f];
 245                static if (Padding != NoPadding)
 246                    *bufptr++ = Padding;
 247                break;
 248            case 1:
 249                static if (Padding != NoPadding) {
 250                    *bufptr++ = Padding;
 251                    *bufptr++ = Padding;
 252                }
 253                break;
 254            }
 255        }
 256
 257        // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'.
 258        version (unittest) assert(bufptr - buffer.ptr == encodeLength(srcLen), "The length of result is different from Base64");
 259
 260        // encode method can't assume buffer length. So, slice needed.
 261        return buffer[0..bufptr - buffer.ptr];
 262    }
 263
 264
 265    // ubyte[] to OutputRange
 266
 267
 268    /**
 269     * Encodes $(D_PARAM source) into $(D_PARAM range).
 270     *
 271     * Params:
 272     *  source = an $(D InputRange) to encode.
 273     *  range  = an $(D OutputRange) to put encoded result.
 274     *
 275     * Returns:
 276     *  the number of calling put.
 277     */
 278    size_t encode(R1, R2)(in R1 source, R2 range) if (isArray!R1 && is(ElementType!R1 : ubyte) &&
 279                                                      !is(R2 == char[]))
 280    out(result)
 281    {
 282        assert(result == encodeLength(source.length), "The number of put is different from the length of Base64");
 283    }
 284    body
 285    {
 286        immutable srcLen = source.length;
 287        if (srcLen == 0)
 288            return 0;
 289
 290        immutable blocks = srcLen / 3;
 291        immutable remain = srcLen % 3;
 292        auto      srcptr = source.ptr;
 293        size_t    pcount;
 294
 295        foreach (Unused; 0..blocks) {
 296            immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2];
 297            put(range, EncodeMap[val >> 18       ]);
 298            put(range, EncodeMap[val >> 12 & 0x3f]);
 299            put(range, EncodeMap[val >>  6 & 0x3f]);
 300            put(range, EncodeMap[val       & 0x3f]);
 301            srcptr += 3;
 302            pcount += 4;
 303        }
 304
 305        if (remain) {
 306            immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0);
 307            put(range, EncodeMap[val >> 18       ]);
 308            put(range, EncodeMap[val >> 12 & 0x3f]);
 309            pcount += 2;
 310
 311            final switch (remain) {
 312            case 2:
 313                put(range, EncodeMap[val >> 6 & 0x3f]);
 314                pcount++;
 315
 316                static if (Padding != NoPadding) {
 317                    put(range, Padding);
 318                    pcount++;
 319                }
 320                break;
 321            case 1:
 322                static if (Padding != NoPadding) {
 323                    put(range, Padding);
 324                    put(range, Padding);
 325                    pcount += 2;
 326                }
 327                break;
 328            }
 329        }
 330
 331        return pcount;
 332    }
 333
 334
 335    // InputRange to OutputRange
 336
 337
 338    /**
 339     * ditto
 340     */
 341    size_t encode(R1, R2)(R1 source, R2 range) if (!isArray!R1 && isInputRange!R1 &&
 342                                                   is(ElementType!R1 : ubyte) && hasLength!R1 &&
 343                                                   !is(R2 == char[]) && isOutputRange!(R2, char))
 344    out(result)
 345    {
 346        // @@@BUG@@@ Workaround for DbC problem.
 347        //assert(result == encodeLength(source.length), "The number of put is different from the length of Base64");
 348    }
 349    body
 350    {
 351        immutable srcLen = source.length;
 352        if (srcLen == 0)
 353            return 0;
 354
 355        immutable blocks = srcLen / 3;
 356        immutable remain = srcLen % 3;
 357        size_t    pcount;
 358
 359        foreach (Unused; 0..blocks) {
 360            immutable v1 = source.front; source.popFront();
 361            immutable v2 = source.front; source.popFront();
 362            immutable v3 = source.front; source.popFront();
 363            immutable val = v1 << 16 | v2 << 8 | v3;
 364            put(range, EncodeMap[val >> 18       ]);
 365            put(range, EncodeMap[val >> 12 & 0x3f]);
 366            put(range, EncodeMap[val >>  6 & 0x3f]);
 367            put(range, EncodeMap[val       & 0x3f]);
 368            pcount += 4;
 369        }
 370
 371        if (remain) {
 372            size_t val = source.front << 16;
 373            if (remain == 2) {
 374                source.popFront();
 375                val |= source.front << 8;
 376            }
 377
 378            put(range, EncodeMap[val >> 18       ]);
 379            put(range, EncodeMap[val >> 12 & 0x3f]);
 380            pcount += 2;
 381
 382            final switch (remain) {
 383            case 2:
 384                put(range, EncodeMap[val >> 6 & 0x3f]);
 385                pcount++;
 386
 387                static if (Padding != NoPadding) {
 388                    put(range, Padding);
 389                    pcount++;
 390                }
 391                break;
 392            case 1:
 393                static if (Padding != NoPadding) {
 394                    put(range, Padding);
 395                    put(range, Padding);
 396                    pcount += 2;
 397                }
 398                break;
 399            }
 400        }
 401
 402        // @@@BUG@@@ Workaround for DbC problem.
 403        version (unittest) assert(pcount == encodeLength(srcLen), "The number of put is different from the length of Base64");
 404
 405        return pcount;
 406    }
 407
 408
 409    /**
 410     * Encodes $(D_PARAM source) to new buffer.
 411     *
 412     * Shortcut to encode(source, buffer) function.
 413     */
 414    @safe
 415    pure char[] encode(Range)(Range source) if (isArray!Range && is(ElementType!Range : ubyte))
 416    {
 417        return encode(source, new char[encodeLength(source.length)]);
 418    }
 419
 420
 421    /**
 422     * ditto
 423     */
 424    char[] encode(Range)(Range source) if (!isArray!Range && isInputRange!Range &&
 425                                           is(ElementType!Range : ubyte) && hasLength!Range)
 426    {
 427        return encode(source, new char[encodeLength(source.length)]);
 428    }
 429
 430
 431    /**
 432     * Range that encodes chunk data at a time.
 433     */
 434    struct Encoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(ubyte)[]) ||
 435                                                     is(ElementType!Range : const(char)[])))
 436    {
 437      private:
 438        Range  range_;
 439        char[] buffer_, encoded_;
 440
 441
 442      public:
 443        this(Range range)
 444        {
 445            range_ = range;
 446            doEncoding();
 447        }
 448
 449
 450        /**
 451         * Range primitive operation that checks iteration state.
 452         *
 453         * Returns:
 454         *  true if there are no more elements to be iterated.
 455         */
 456        @property @trusted
 457        bool empty()
 458        {
 459            return range_.empty;
 460        }
 461
 462
 463        /**
 464         * Range primitive operation that returns the currently iterated element.
 465         *
 466         * Returns:
 467         *  the encoded string.
 468         */
 469        @property @safe
 470        nothrow char[] front()
 471        {
 472            return encoded_;
 473        }
 474
 475
 476        /**
 477         * Range primitive operation that advances the range to its next element.
 478         *
 479         * Throws:
 480         *  an Exception when you try to call popFront on empty range.
 481         */
 482        void popFront()
 483        {
 484            enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
 485
 486            range_.popFront();
 487
 488            /*
 489             * This check is very ugly. I think this is a Range's flaw.
 490             * I very strongly want the Range guideline for unified implementation.
 491             *
 492             * In this case, Encoder becomes a beautiful implementation if 'front' performs Base64 encoding.
 493             */
 494            if (!empty)
 495                doEncoding();
 496        }
 497
 498
 499        static if (isForwardRange!Range) {
 500            /**
 501             * Captures a Range state.
 502             *
 503             * Returns:
 504             *  a copy of $(D this).
 505             */
 506            @property
 507            typeof(this) save()
 508            {
 509                typeof(return) encoder;
 510
 511                encoder.range_   = range_.save;
 512                encoder.buffer_  = buffer_.dup;
 513                encoder.encoded_ = encoder.buffer_[0..encoded_.length];
 514
 515                return encoder;
 516            }
 517        }
 518
 519
 520      private:
 521        void doEncoding()
 522        {
 523            auto data = cast(const(ubyte)[])range_.front;
 524            auto size = encodeLength(data.length);
 525            if (size > buffer_.length)
 526                buffer_.length = size;
 527
 528            encoded_ = encode(data, buffer_);
 529        }
 530    }
 531
 532
 533    /**
 534     * Range that encodes single character at a time.
 535     */
 536    struct Encoder(Range) if (isInputRange!Range && is(ElementType!Range : ubyte))
 537    {
 538      private:
 539        Range range_;
 540        ubyte first;
 541        int   pos, padding;
 542
 543
 544      public:
 545        this(Range range)
 546        {
 547            range_ = range;
 548            static if (isForwardRange!Range)
 549                range_ = range_.save;
 550
 551            if (range_.empty)
 552                pos = -1;
 553            else
 554                popFront();
 555        }
 556
 557
 558        /**
 559         * Range primitive operation that checks iteration state.
 560         *
 561         * Returns:
 562         *  true if there are no more elements to be iterated.
 563         */
 564        @property @safe
 565        nothrow bool empty() const
 566        {
 567            static if (Padding == NoPadding)
 568                return pos < 0;
 569            else
 570                return pos < 0 && !padding;
 571        }
 572
 573
 574        /**
 575         * Range primitive operation that returns the currently iterated element.
 576         *
 577         * Returns:
 578         *  the encoded character.
 579         */
 580        @property @safe
 581        nothrow ubyte front()
 582        {
 583            return first;
 584        }
 585
 586
 587        /**
 588         * Range primitive operation that advances the range to its next element.
 589         *
 590         * Throws:
 591         *  an Exception when you try to call popFront on empty range.
 592         */
 593        void popFront()
 594        {
 595            enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
 596
 597            static if (Padding != NoPadding)
 598                if (padding) {
 599                    first = Padding;
 600                    pos   = -1;
 601                    padding--;
 602                    return;
 603                }
 604
 605            if (range_.empty) {
 606                pos = -1;
 607                return;
 608            }
 609
 610            final switch (pos) {
 611            case 0:
 612                first = EncodeMap[range_.front >> 2];
 613                break;
 614            case 1:
 615                immutable t = (range_.front & 0b11) << 4;
 616                range_.popFront();
 617
 618                if (range_.empty) {
 619                    first   = EncodeMap[t];
 620                    padding = 3;
 621                } else {
 622                    first = EncodeMap[t | (range_.front >> 4)];
 623                }
 624                break;
 625            case 2:
 626                immutable t = (range_.front & 0b1111) << 2;
 627                range_.popFront();
 628
 629                if (range_.empty) {
 630                    first   = EncodeMap[t];
 631                    padding = 2;
 632                } else {
 633                    first = EncodeMap[t | (range_.front >> 6)];
 634                }
 635                break;
 636            case 3:
 637                first = EncodeMap[range_.front & 0b111111];
 638                range_.popFront();
 639                break;
 640            }
 641
 642            ++pos %= 4;
 643        }
 644
 645
 646        static if (isForwardRange!Range) {
 647            /**
 648             * Captures a Range state.
 649             *
 650             * Returns:
 651             *  a copy of $(D this).
 652             */
 653            @property
 654            typeof(this) save()
 655            {
 656                auto encoder = this;
 657                encoder.range_ = encoder.range_.save;
 658                return encoder;
 659            }
 660        }
 661    }
 662
 663
 664    /**
 665     * Iterates through an $(D InputRange) at a time by using $(D Encoder).
 666     *
 667     * Default $(D Encoder) encodes chunk data.
 668     *
 669     * Example:
 670     * -----
 671     * File f = File("text.txt", "r");
 672     * scope(exit) f.close();
 673     *
 674     * uint line = 0;
 675     * foreach (encoded; Base64.encoder(f.byLine()))
 676     * {
 677     *     writeln(++line, ". ", encoded);
 678     * }
 679     * -----
 680     *
 681     * In addition, You can use $(D Encoder) that returns encoded single character.
 682     * This $(D Encoder) performs Range-based and lazy encoding.
 683     *
 684     * Example:
 685     * -----
 686     * ubyte[] data = cast(ubyte[]) "0123456789";
 687     *
 688     * // The ElementType of data is not aggregation type
 689     * foreach (encoded; Base64.encoder(data))
 690     * {
 691     *     writeln(encoded);
 692     * }
 693     * -----
 694     *
 695     * Params:
 696     *  range = an $(D InputRange) to iterate.
 697     *
 698     * Returns:
 699     *  a $(D Encoder) object instantiated and initialized according to the arguments.
 700     */
 701    Encoder!(Range) encoder(Range)(Range range) if (isInputRange!Range)
 702    {
 703        return typeof(return)(range);
 704    }
 705
 706
 707    /* Decode functions */
 708
 709
 710    private immutable int[char.max + 1] DecodeMap = [
 711        'A':0b000000, 'B':0b000001, 'C':0b000010, 'D':0b000011, 'E':0b000100,
 712        'F':0b000101, 'G':0b000110, 'H':0b000111, 'I':0b001000, 'J':0b001001,
 713        'K':0b001010, 'L':0b001011, 'M':0b001100, 'N':0b001101, 'O':0b001110,
 714        'P':0b001111, 'Q':0b010000, 'R':0b010001, 'S':0b010010, 'T':0b010011,
 715        'U':0b010100, 'V':0b010101, 'W':0b010110, 'X':0b010111, 'Y':0b011000,
 716        'Z':0b011001, 'a':0b011010, 'b':0b011011, 'c':0b011100, 'd':0b011101,
 717        'e':0b011110, 'f':0b011111, 'g':0b100000, 'h':0b100001, 'i':0b100010,
 718        'j':0b100011, 'k':0b100100, 'l':0b100101, 'm':0b100110, 'n':0b100111,
 719        'o':0b101000, 'p':0b101001, 'q':0b101010, 'r':0b101011, 's':0b101100,
 720        't':0b101101, 'u':0b101110, 'v':0b101111, 'w':0b110000, 'x':0b110001,
 721        'y':0b110010, 'z':0b110011, '0':0b110100, '1':0b110101, '2':0b110110,
 722        '3':0b110111, '4':0b111000, '5':0b111001, '6':0b111010, '7':0b111011,
 723        '8':0b111100, '9':0b111101, Map62th:0b111110, Map63th:0b111111, Padding:-1
 724    ];
 725
 726
 727    /**
 728     * Calculates the minimum length for decoding.
 729     *
 730     * Params:
 731     *  sourceLength = the length of source array.
 732     *
 733     * Returns:
 734     *  calculated length using $(D_PARAM sourceLength).
 735     */
 736    @safe
 737    pure nothrow size_t decodeLength(in size_t sourceLength)
 738    {
 739        static if (Padding == NoPadding)
 740            return (sourceLength / 4) * 3 + (sourceLength % 4 < 2 ? 0 : sourceLength % 4 == 2 ? 1 : 2);
 741        else
 742            return (sourceLength / 4) * 3;
 743    }
 744
 745
 746    // Used in decode contracts. Calculates the actual size the decoded
 747    // result should have, taking into account trailing padding.
 748    @safe
 749    pure nothrow private size_t realDecodeLength(R)(R source)
 750    {
 751        auto expect = decodeLength(source.length);
 752        static if (Padding != NoPadding)
 753        {
 754            if (source.length % 4 == 0)
 755            {
 756                expect -= source.length == 0       ? 0 :
 757                          source[$ - 2] == Padding ? 2 :
 758                          source[$ - 1] == Padding ? 1 : 0;
 759            }
 760        }
 761        return expect;
 762    }
 763
 764
 765    // char[] to ubyte[]
 766
 767
 768    /**
 769     * Decodes $(D_PARAM source) into $(D_PARAM buffer).
 770     *
 771     * Params:
 772     *  source = an $(D InputRange) to decode.
 773     *  buffer = a buffer to store decoded result.
 774     *
 775     * Returns:
 776     *  the decoded string that slices buffer.
 777     *
 778     * Throws:
 779     *  an Exception if $(D_PARAM source) has character outside base-alphabet.
 780     */
 781    @trusted
 782    pure ubyte[] decode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : dchar) &&
 783                                                             is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
 784    in
 785    {
 786        assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding");
 787    }
 788    out(result)
 789    {
 790        immutable expect = realDecodeLength(source);
 791        assert(result.length == expect, "The length of result is different from the expected length");
 792    }
 793    body
 794    {
 795        immutable srcLen = source.length;
 796        if (srcLen == 0)
 797            return [];
 798        static if (Padding != NoPadding)
 799            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
 800
 801        immutable blocks = srcLen / 4;
 802        auto      srcptr = source.ptr;
 803        auto      bufptr = buffer.ptr;
 804
 805        foreach (Unused; 0..blocks) {
 806            immutable v1 = decodeChar(*srcptr++);
 807            immutable v2 = decodeChar(*srcptr++);
 808
 809            *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
 810
 811            immutable v3 = decodeChar(*srcptr++);
 812            if (v3 == -1)
 813                break;
 814
 815            *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff);
 816
 817            immutable v4 = decodeChar(*srcptr++);
 818            if (v4 == -1)
 819                break;
 820
 821            *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff);
 822        }
 823
 824        static if (Padding == NoPadding) {
 825            immutable remain = srcLen % 4;
 826
 827            if (remain) {
 828                immutable v1 = decodeChar(*srcptr++);
 829                immutable v2 = decodeChar(*srcptr++);
 830
 831                *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
 832
 833                if (remain == 3)
 834                    *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff);
 835            }
 836        }
 837
 838        return buffer[0..bufptr - buffer.ptr];
 839    }
 840
 841
 842    // InputRange to ubyte[]
 843
 844
 845    /**
 846     * ditto
 847     */
 848    ubyte[] decode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 &&
 849                                                     is(ElementType!R1 : dchar) && hasLength!R1 &&
 850                                                     is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
 851    in
 852    {
 853        assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding");
 854    }
 855    out(result)
 856    {
 857        // @@@BUG@@@ Workaround for DbC problem.
 858        //immutable expect = decodeLength(source.length) - 2;
 859        //assert(result.length >= expect, "The length of result is smaller than expected length");
 860    }
 861    body
 862    {
 863        immutable srcLen = source.length;
 864        if (srcLen == 0)
 865            return [];
 866        static if (Padding != NoPadding)
 867            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
 868
 869        immutable blocks = srcLen / 4;
 870        auto      bufptr = buffer.ptr;
 871
 872        foreach (Unused; 0..blocks) {
 873            immutable v1 = decodeChar(source.front); source.popFront();
 874            immutable v2 = decodeChar(source.front); source.popFront();
 875
 876            *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
 877
 878            immutable v3 = decodeChar(source.front);
 879            if (v3 == -1)
 880                break;
 881
 882            *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff);
 883            source.popFront();
 884
 885            immutable v4 = decodeChar(source.front);
 886            if (v4 == -1)
 887                break;
 888
 889            *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff);
 890            source.popFront();
 891        }
 892
 893        static if (Padding == NoPadding) {
 894            immutable remain = srcLen % 4;
 895
 896            if (remain) {
 897                immutable v1 = decodeChar(source.front); source.popFront();
 898                immutable v2 = decodeChar(source.front);
 899
 900                *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
 901
 902                if (remain == 3) {
 903                    source.popFront();
 904                    *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff);
 905                }
 906            }
 907        }
 908
 909        // @@@BUG@@@ Workaround for DbC problem.
 910        version (unittest) assert((bufptr - buffer.ptr) >= (decodeLength(srcLen) - 2), "The length of result is smaller than expected length");
 911
 912        return buffer[0..bufptr - buffer.ptr];
 913    }
 914
 915
 916    // char[] to OutputRange
 917
 918
 919    /**
 920     * Decodes $(D_PARAM source) into $(D_PARAM range).
 921     *
 922     * Params:
 923     *  source = an $(D InputRange) to decode.
 924     *  range  = an $(D OutputRange) to put decoded result
 925     *
 926     * Returns:
 927     *  the number of calling put.
 928     *
 929     * Throws:
 930     *  an Exception if $(D_PARAM source) has character outside base-alphabet.
 931     */
 932    size_t decode(R1, R2)(in R1 source, R2 range) if (isArray!R1 && is(ElementType!R1 : dchar) &&
 933                                                      !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
 934    out(result)
 935    {
 936        immutable expect = realDecodeLength(source);
 937        assert(result == expect, "The result of decode is different from the expected");
 938    }
 939    body
 940    {
 941        immutable srcLen = source.length;
 942        if (srcLen == 0)
 943            return 0;
 944        static if (Padding != NoPadding)
 945            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
 946
 947        immutable blocks = srcLen / 4;
 948        auto      srcptr = source.ptr;
 949        size_t    pcount;
 950
 951        foreach (Unused; 0..blocks) {
 952            immutable v1 = decodeChar(*srcptr++);
 953            immutable v2 = decodeChar(*srcptr++);
 954
 955            put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
 956            pcount++;
 957
 958            immutable v3 = decodeChar(*srcptr++);
 959            if (v3 == -1)
 960                break;
 961
 962            put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff));
 963            pcount++;
 964
 965            immutable v4 = decodeChar(*srcptr++);
 966            if (v4 == -1)
 967                break;
 968
 969            put(range, cast(ubyte)((v3 << 6 | v4) & 0xff));
 970            pcount++;
 971        }
 972
 973        static if (Padding == NoPadding) {
 974            immutable remain = srcLen % 4;
 975
 976            if (remain) {
 977                immutable v1 = decodeChar(*srcptr++);
 978                immutable v2 = decodeChar(*srcptr++);
 979
 980                put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
 981                pcount++;
 982
 983                if (remain == 3) {
 984                    put(range, cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff));
 985                    pcount++;
 986                }
 987            }
 988        }
 989
 990        return pcount;
 991    }
 992
 993
 994    // InputRange to OutputRange
 995
 996
 997    /**
 998     * ditto
 999     */
1000    size_t decode(R1, R2)(R1 source, R2 range) if (!isArray!R1 && isInputRange!R1 &&
1001                                                   is(ElementType!R1 : dchar) && hasLength!R1 &&
1002                                                   !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
1003    out(result)
1004    {
1005        // @@@BUG@@@ Workaround for DbC problem.
1006        //immutable expect = decodeLength(source.length) - 2;
1007        //assert(result >= expect, "The length of result is smaller than expected length");
1008    }
1009    body
1010    {
1011        immutable srcLen = source.length;
1012        if (srcLen == 0)
1013            return 0;
1014        static if (Padding != NoPadding)
1015            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
1016
1017        immutable blocks = srcLen / 4;
1018        size_t    pcount;
1019
1020        foreach (Unused; 0..blocks) {
1021            immutable v1 = decodeChar(source.front); source.popFront();
1022            immutable v2 = decodeChar(source.front); source.popFront();
1023
1024            put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
1025            pcount++;
1026
1027            immutable v3 = decodeChar(source.front);
1028            if (v3 == -1)
1029                break;
1030
1031            put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff));
1032            source.popFront();
1033            pcount++;
1034
1035            immutable v4 = decodeChar(source.front);
1036            if (v4 == -1)
1037                break;
1038
1039            put(range, cast(ubyte)((v3 << 6 | v4) & 0xff));
1040            source.popFront();
1041            pcount++;
1042        }
1043
1044        static if (Padding == NoPadding) {
1045            immutable remain = srcLen % 4;
1046
1047            if (remain) {
1048                immutable v1 = decodeChar(source.front); source.popFront();
1049                immutable v2 = decodeChar(source.front);
1050
1051                put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
1052                pcount++;
1053
1054                if (remain == 3) {
1055                    source.popFront();
1056                    put(range, cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff));
1057                    pcount++;
1058                }
1059            }
1060        }
1061
1062        // @@@BUG@@@ Workaround for DbC problem.
1063        version (unittest) assert(pcount >= (decodeLength(srcLen) - 2), "The length of result is smaller than expected length");
1064
1065        return pcount;
1066    }
1067
1068
1069    /**
1070     * Decodes $(D_PARAM source) into new buffer.
1071     *
1072     * Shortcut to decode(source, buffer) function.
1073     */
1074    @safe
1075    pure ubyte[] decode(Range)(Range source) if (isArray!Range && is(ElementType!Range : dchar))
1076    {
1077        return decode(source, new ubyte[decodeLength(source.length)]);
1078    }
1079
1080
1081    /**
1082     * ditto
1083     */
1084    ubyte[] decode(Range)(Range source) if (!isArray!Range && isInputRange!Range &&
1085                                            is(ElementType!Range : dchar) && hasLength!Range)
1086    {
1087        return decode(source, new ubyte[decodeLength(source.length)]);
1088    }
1089
1090
1091    /**
1092     * Range that decodes chunk data at a time.
1093     */
1094    struct Decoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(char)[]) ||
1095                                                     is(ElementType!Range : const(ubyte)[])))
1096    {
1097      private:
1098        Range   range_;
1099        ubyte[] buffer_, decoded_;
1100
1101
1102      public:
1103        this(Range range)
1104        {
1105            range_ = range;
1106            doDecoding();
1107        }
1108
1109
1110        /**
1111         * Range primitive operation that checks iteration state.
1112         *
1113         * Returns:
1114         *  true if there are no more elements to be iterated.
1115         */
1116        @property @trusted
1117        bool empty()
1118        {
1119            return range_.empty;
1120        }
1121
1122
1123        /**
1124         * Range primitive operation that returns the currently iterated element.
1125         *
1126         * Returns:
1127         *  the decoded result.
1128         */
1129        @property @safe
1130        nothrow ubyte[] front()
1131        {
1132            return decoded_;
1133        }
1134
1135
1136        /**
1137         * Range primitive operation that advances the range to its next element.
1138         *
1139         * Throws:
1140         *  an Exception when you try to call popFront on empty range.
1141         */
1142        void popFront()
1143        {
1144            enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining."));
1145
1146            range_.popFront();
1147
1148            /*
1149             * I mentioned Encoder's popFront.
1150             */
1151            if (!empty)
1152                doDecoding();
1153        }
1154
1155
1156        static if (isForwardRange!Range) {
1157            /**
1158             * Captures a Range state.
1159             *
1160             * Returns:
1161             *  a copy of $(D this).
1162             */
1163            @property
1164            typeof(this) save()
1165            {
1166                typeof(return) decoder;
1167
1168                decoder.range_   = range_.save;
1169                decoder.buffer_  = buffer_.dup;
1170                decoder.decoded_ = decoder.buffer_[0..decoded_.length];
1171
1172                return decoder;
1173            }
1174        }
1175
1176
1177      private:
1178        void doDecoding()
1179        {
1180            auto data = cast(const(char)[])range_.front;
1181
1182            static if (Padding == NoPadding) {
1183                while (data.length % 4 == 1) {
1184                    range_.popFront();
1185                    data ~= cast(const(char)[])range_.front;
1186                }
1187            } else {
1188                while (data.length % 4 != 0) {
1189                    range_.popFront();
1190                    data ~= cast(const(char)[])range_.front;
1191                }
1192            }
1193
1194            auto size = decodeLength(data.length);
1195            if (size > buffer_.length)
1196                buffer_.length = size;
1197
1198            decoded_ = decode(data, buffer_);
1199        }
1200    }
1201
1202
1203    /**
1204     * Range that decodes single character at a time.
1205     */
1206    struct Decoder(Range) if (isInputRange!Range && is(ElementType!Range : char))
1207    {
1208      private:
1209        Range range_;
1210        ubyte first;
1211        int   pos;
1212
1213
1214      public:
1215        this(Range range)
1216        {
1217            range_ = range;
1218            static if (isForwardRange!Range)
1219                range_ = range_.save;
1220
1221            static if (Padding != NoPadding && hasLength!Range)
1222                enforce(range_.length % 4 == 0, new Base64Exception("Invalid length of encoded data"));
1223
1224            if (range_.empty)
1225                pos = -1;
1226            else
1227                popFront();
1228        }
1229
1230
1231        /**
1232         * Range primitive operation that checks iteration state.
1233         *
1234         * Returns:
1235         *  true if there are no more elements to be iterated.
1236         */
1237        @property @safe
1238        nothrow bool empty() const
1239        {
1240            return pos < 0;
1241        }
1242
1243
1244        /**
1245         * Range primitive operation that returns the currently iterated element.
1246         *
1247         * Returns:
1248         *  the decoded result.
1249         */
1250        @property @safe
1251        nothrow ubyte front()
1252        {
1253            return first;
1254        }
1255
1256
1257        /**
1258         * Range primitive operation that advances the range to its next element.
1259         *
1260         * Throws:
1261         *  an Exception when you try to call popFront on empty range.
1262         */
1263        void popFront()
1264        {
1265            enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining"));
1266
1267            static if (Padding == NoPadding) {
1268                bool endCondition()
1269                {
1270                    return range_.empty;
1271                }
1272            } else {
1273                bool endCondition()
1274                {
1275                    enforce(!range_.empty, new Base64Exception("Missing padding"));
1276                    return range_.front == Padding;
1277                }
1278            }
1279
1280            if (range_.empty || range_.front == Padding) {
1281                pos = -1;
1282                return;
1283            }
1284
1285            final switch (pos) {
1286            case 0:
1287                enforce(!endCondition(), new Base64Exception("Premature end of data found"));
1288
1289                immutable t = DecodeMap[range_.front] << 2;
1290                range_.popFront();
1291
1292                enforce(!endCondition(), new Base64Exception("Premature end of data found"));
1293                first = cast(ubyte)(t | (DecodeMap[range_.front] >> 4));
1294                break;
1295            case 1:
1296                immutable t = (DecodeMap[range_.front] & 0b1111) << 4;
1297                range_.popFront();
1298
1299                if (endCondition()) {
1300                    pos = -1;
1301                    return;
1302                } else {
1303                    first = cast(ubyte)(t | (DecodeMap[range_.front] >> 2));
1304                }
1305                break;
1306            case 2:
1307                immutable t = (DecodeMap[range_.front] & 0b11) << 6;
1308                range_.popFront();
1309
1310                if (endCondition()) {
1311                    pos = -1;
1312                    return;
1313                } else {
1314                    first = cast(ubyte)(t | DecodeMap[range_.front]);
1315                }
1316
1317                range_.popFront();
1318                break;
1319            }
1320
1321            ++pos %= 3;
1322        }
1323
1324
1325        static if (isForwardRange!Range) {
1326            /**
1327             * Captures a Range state.
1328             *
1329             * Returns:
1330             *  a copy of $(D this).
1331             */
1332            @property
1333            typeof(this) save()
1334            {
1335                auto decoder = this;
1336                decoder.range_ = decoder.range_.save;
1337                return decoder;
1338            }
1339        }
1340    }
1341
1342
1343    /**
1344     * Iterates through an $(D InputRange) at a time by using $(D Decoder).
1345     *
1346     * Default $(D Decoder) decodes chunk data.
1347     *
1348     * Example:
1349     * -----
1350     * foreach (decoded; Base64.decoder(stdin.byLine()))
1351     * {
1352     *     writeln(decoded);
1353     * }
1354     * -----
1355     *
1356     * In addition, You can use $(D Decoder) that returns decoded single character.
1357     * This $(D Decoder) performs Range-based and lazy decoding.
1358     *
1359     * Example:
1360     * -----
1361     * auto encoded = Base64.encoder(cast(ubyte[])"0123456789");
1362     * foreach (n; map!q{a - '0'}(Base64.decoder(encoded)))
1363     * {
1364     *     writeln(n);
1365     * }
1366     * -----
1367     *
1368     * NOTE:
1369     *  If you use $(D ByChunk), chunk-size should be the multiple of 4.
1370     *  $(D Decoder) can't judge a encode-boundary.
1371     *
1372     * Params:
1373     *  range = an $(D InputRange) to iterate.
1374     *
1375     * Returns:
1376     *  a $(D Decoder) object instantiated and initialized according to the arguments.
1377     */
1378    Decoder!(Range) decoder(Range)(Range range) if (isInputRange!Range)
1379    {
1380        return typeof(return)(range);
1381    }
1382
1383
1384  private:
1385    @safe
1386    pure int decodeChar()(char chr)
1387    {
1388        immutable val = DecodeMap[chr];
1389
1390        // enforce can't be a pure function, so I use trivial check.
1391        if (val == 0 && chr != 'A')
1392            throw new Base64Exception("Invalid character: " ~ chr);
1393
1394        return val;
1395    }
1396
1397
1398    @safe
1399    pure int decodeChar()(dchar chr)
1400    {
1401        // See above comment.
1402        if (chr > 0x7f)
1403            throw new Base64Exception("Base64-encoded character must be a single byte");
1404
1405        return decodeChar(cast(char)chr);
1406    }
1407}
1408
1409
1410/**
1411 * Exception thrown on Base64 errors.
1412 */
1413class Base64Exception : Exception
1414{
1415    @safe pure nothrow
1416    this(string s, string fn = __FILE__, size_t ln = __LINE__)
1417    {
1418        super(s, fn, ln);
1419    }
1420}
1421
1422
1423unittest
1424{
1425    alias Base64Impl!('!', '=', Base64.NoPadding) Base64Re;
1426
1427    // Test vectors from RFC 4648
1428    ubyte[][string] tv = [
1429         ""      :cast(ubyte[])"",
1430         "f"     :cast(ubyte[])"f",
1431         "fo"    :cast(ubyte[])"fo",
1432         "foo"   :cast(ubyte[])"foo",
1433         "foob"  :cast(ubyte[])"foob",
1434         "fooba" :cast(ubyte[])"fooba",
1435         "foobar":cast(ubyte[])"foobar"
1436    ];
1437
1438    { // Base64
1439        // encode
1440        assert(Base64.encodeLength(tv[""].length)       == 0);
1441        assert(Base64.encodeLength(tv["f"].length)      == 4);
1442        assert(Base64.encodeLength(tv["fo"].length)     == 4);
1443        assert(Base64.encodeLength(tv["foo"].length)    == 4);
1444        assert(Base64.encodeLength(tv["foob"].length)   == 8);
1445        assert(Base64.encodeLength(tv["fooba"].length)  == 8);
1446        assert(Base64.encodeLength(tv["foobar"].length) == 8);
1447
1448        assert(Base64.encode(tv[""])       == "");
1449        assert(Base64.encode(tv["f"])      == "Zg==");
1450        assert(Base64.encode(tv["fo"])     == "Zm8=");
1451        assert(Base64.encode(tv["foo"])    == "Zm9v");
1452        assert(Base64.encode(tv["foob"])   == "Zm9vYg==");
1453        assert(Base64.encode(tv["fooba"])  == "Zm9vYmE=");
1454        assert(Base64.encode(tv["foobar"]) == "Zm9vYmFy");
1455
1456        // decode
1457        assert(Base64.decodeLength(Base64.encode(tv[""]).length)       == 0);
1458        assert(Base64.decodeLength(Base64.encode(tv["f"]).length)      == 3);
1459        assert(Base64.decodeLength(Base64.encode(tv["fo"]).length)     == 3);
1460        assert(Base64.decodeLength(Base64.encode(tv["foo"]).length)    == 3);
1461        assert(Base64.decodeLength(Base64.encode(tv["foob"]).length)   == 6);
1462        assert(Base64.decodeLength(Base64.encode(tv["fooba"]).length)  == 6);
1463        assert(Base64.decodeLength(Base64.encode(tv["foobar"]).length) == 6);
1464
1465        assert(Base64.decode(Base64.encode(tv[""]))       == tv[""]);
1466        assert(Base64.decode(Base64.encode(tv["f"]))      == tv["f"]);
1467        assert(Base64.decode(Base64.encode(tv["fo"]))     == tv["fo"]);
1468        assert(Base64.decode(Base64.encode(tv["foo"]))    == tv["foo"]);
1469        assert(Base64.decode(Base64.encode(tv["foob"]))   == tv["foob"]);
1470        assert(Base64.decode(Base64.encode(tv["fooba"]))  == tv["fooba"]);
1471        assert(Base64.decode(Base64.encode(tv["foobar"])) == tv["foobar"]);
1472
1473        assertThrown!Base64Exception(Base64.decode("ab|c"));
1474
1475        // Test decoding incomplete strings. RFC does not specify the correct
1476        // behavior, but the code should never throw Errors on invalid input.
1477
1478        // decodeLength is nothrow
1479        assert(Base64.decodeLength(1) == 0);
1480        assert(Base64.decodeLength(2) <= 1);
1481        assert(Base64.decodeLength(3) <= 2);
1482
1483        // may throw Exceptions, may not throw Errors
1484        assertThrown!Base64Exception(Base64.decode("Zg"));
1485        assertThrown!Base64Exception(Base64.decode("Zg="));
1486        assertThrown!Base64Exception(Base64.decode("Zm8"));
1487        assertThrown!Base64Exception(Base64.decode("Zg==;"));
1488    }
1489
1490    { // No padding
1491        // encode
1492        assert(Base64Re.encodeLength(tv[""].length)       == 0);
1493        assert(Base64Re.encodeLength(tv["f"].length)      == 2);
1494        assert(Base64Re.encodeLength(tv["fo"].length)     == 3);
1495        assert(Base64Re.encodeLength(tv["foo"].length)    == 4);
1496        assert(Base64Re.encodeLength(tv["foob"].length)   == 6);
1497        assert(Base64Re.encodeLength(tv["fooba"].length)  == 7);
1498        assert(Base64Re.encodeLength(tv["foobar"].length) == 8);
1499
1500        assert(Base64Re.encode(tv[""])       == "");
1501        assert(Base64Re.encode(tv["f"])      == "Zg");
1502        assert(Base64Re.encode(tv["fo"])     == "Zm8");
1503        assert(Base64Re.encode(tv["foo"])    == "Zm9v");
1504        assert(Base64Re.encode(tv["foob"])   == "Zm9vYg");
1505        assert(Base64Re.encode(tv["fooba"])  == "Zm9vYmE");
1506        assert(Base64Re.encode(tv["foobar"]) == "Zm9vYmFy");
1507
1508        // decode
1509        assert(Base64Re.decodeLength(Base64Re.encode(tv[""]).length)       == 0);
1510        assert(Base64Re.decodeLength(Base64Re.encode(tv["f"]).length)      == 1);
1511        assert(Base64Re.decodeLength(Base64Re.encode(tv["fo"]).length)     == 2);
1512        assert(Base64Re.decodeLength(Base64Re.encode(tv["foo"]).length)    == 3);
1513        assert(Base64Re.decodeLength(Base64Re.encode(tv["foob"]).length)   == 4);
1514        assert(Base64Re.decodeLength(Base64Re.encode(tv["fooba"]).length)  == 5);
1515        assert(Base64Re.decodeLength(Base64Re.encode(tv["foobar"]).length) == 6);
1516
1517        assert(Base64Re.decode(Base64Re.encode(tv[""]))       == tv[""]);
1518        assert(Base64Re.decode(Base64Re.encode(tv["f"]))      == tv["f"]);
1519        assert(Base64Re.decode(Base64Re.encode(tv["fo"]))     == tv["fo"]);
1520        assert(Base64Re.decode(Base64Re.encode(tv["foo"]))    == tv["foo"]);
1521        assert(Base64Re.decode(Base64Re.encode(tv["foob"]))   == tv["foob"]);
1522        assert(Base64Re.decode(Base64Re.encode(tv["fooba"]))  == tv["fooba"]);
1523        assert(Base64Re.decode(Base64Re.encode(tv["foobar"])) == tv["foobar"]);
1524
1525        // decodeLength is nothrow
1526        assert(Base64.decodeLength(1) == 0);
1527    }
1528
1529    { // with OutputRange
1530        auto a = Appender!(char[])([]);
1531        auto b = Appender!(ubyte[])([]);
1532
1533        assert(Base64.encode(tv[""], a) == 0);
1534        assert(Base64.decode(a.data, b) == 0);
1535        assert(tv[""] == b.data); a.clear(); b.clear();
1536
1537        assert(Base64.encode(tv["f"], a) == 4);
1538        assert(Base64.decode(a.data,  b) == 1);
1539        assert(tv["f"] == b.data); a.clear(); b.clear();
1540
1541        assert(Base64.encode(tv["fo"], a) == 4);
1542        assert(Base64.decode(a.data,   b) == 2);
1543        assert(tv["fo"] == b.data); a.clear(); b.clear();
1544
1545        assert(Base64.encode(tv["foo"], a) == 4);
1546        assert(Base64.decode(a.data,    b) == 3);
1547        assert(tv["foo"] == b.data); a.clear(); b.clear();
1548
1549        assert(Base64.encode(tv["foob"], a) == 8);
1550        assert(Base64.decode(a.data,     b) == 4);
1551        assert(tv["foob"] == b.data); a.clear(); b.clear();
1552
1553        assert(Base64.encode(tv["fooba"], a) == 8);
1554        assert(Base64.decode(a.data, b)      == 5);
1555        assert(tv["fooba"] == b.data); a.clear(); b.clear();
1556
1557        assert(Base64.encode(tv["foobar"], a) == 8);
1558        assert(Base64.decode(a.data, b)       == 6);
1559        assert(tv["foobar"] == b.data); a.clear(); b.clear();
1560    }
1561
1562    // @@@9543@@@ These tests were disabled because they actually relied on the input range having length.
1563    // The implementation (currently) doesn't support encoding/decoding from a length-less source.
1564    version(none)
1565    { // with InputRange
1566        // InputRange to ubyte[] or char[]
1567        auto encoded = Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]));
1568        assert(encoded == "FPucA9l+");
1569        assert(Base64.decode(map!q{a}(encoded)) == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
1570
1571        // InputRange to OutputRange
1572        auto a = Appender!(char[])([]);
1573        auto b = Appender!(ubyte[])([]);
1574        assert(Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]), a) == 8);
1575        assert(a.data == "FPucA9l+");
1576        assert(Base64.decode(map!q{a}(a.data), b) == 6);
1577        assert(b.data == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
1578    }
1579
1580    { // Encoder and Decoder
1581        {
1582            std.file.write("testingEncoder", "\nf\nfo\nfoo\nfoob\nfooba\nfoobar");
1583
1584            auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"];
1585            auto f = File("testingEncoder");
1586            scope(exit)
1587            {
1588                f.close();
1589                assert(!f.isOpen);
1590                std.file.remove("testingEncoder");
1591            }
1592
1593            size_t i;
1594            foreach (encoded; Base64.encoder(f.byLine()))
1595                assert(encoded == witness[i++]);
1596
1597            assert(i == witness.length);
1598        }
1599
1600        {
1601            std.file.write("testingDecoder", "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy");
1602
1603            auto witness = tv.keys.sort;
1604            auto f = File("testingDecoder");
1605            scope(exit)
1606            {
1607                f.close();
1608                assert(!f.isOpen);
1609                std.file.remove("testingDecoder");
1610            }
1611
1612            size_t i;
1613            foreach (decoded; Base64.decoder(f.byLine()))
1614                assert(decoded == witness[i++]);
1615
1616            assert(i == witness.length);
1617        }
1618
1619        { // ForwardRange
1620            {
1621                auto encoder = Base64.encoder(tv.values.sort);
1622          

Large files files are truncated, but you can click here to view the full file