1 // Written in the D programming language. 2 3 /** 4 * Support for Base64 encoding and decoding. 5 * 6 * This module provides two default implementations of Base64 encoding, 7 * $(LREF Base64) with a standard encoding alphabet, and a variant 8 * $(LREF Base64URL) that has a modified encoding alphabet designed to be 9 * safe for embedding in URLs and filenames. 10 * 11 * Both variants are implemented as instantiations of the template 12 * $(LREF Base64Impl). Most users will not need to use this template 13 * directly; however, it can be used to create customized Base64 encodings, 14 * such as one that omits padding characters, or one that is safe to embed 15 * inside a regular expression. 16 * 17 * Example: 18 * ----- 19 * ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]; 20 * 21 * const(char)[] encoded = Base64.encode(data); 22 * assert(encoded == "FPucA9l+"); 23 * 24 * ubyte[] decoded = Base64.decode("FPucA9l+"); 25 * assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); 26 * ----- 27 * 28 * The range API is supported for both encoding and decoding: 29 * 30 * Example: 31 * ----- 32 * // Create MIME Base64 with CRLF, per line 76. 33 * File f = File("./text.txt", "r"); 34 * scope(exit) f.close(); 35 * 36 * Appender!string mime64 = appender!string; 37 * 38 * foreach (encoded; Base64.encoder(f.byChunk(57))) 39 * { 40 * mime64.put(encoded); 41 * mime64.put("\r\n"); 42 * } 43 * 44 * writeln(mime64.data); 45 * ----- 46 * 47 * References: 48 * $(LINK2 https://tools.ietf.org/html/rfc4648, RFC 4648 - The Base16, Base32, and Base64 49 * Data Encodings) 50 * 51 * Copyright: Masahiro Nakagawa 2010-. 52 * License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 53 * Authors: Masahiro Nakagawa, Daniel Murphy (Single value Encoder and Decoder) 54 * Source: $(PHOBOSSRC std/base64.d) 55 * Macros: 56 * LREF2=<a href="#$1">`$2`</a> 57 */ 58 module std.base64; 59 60 import std.exception : enforce; 61 import std.range.primitives : empty, front, isInputRange, isOutputRange, 62 isForwardRange, ElementType, hasLength, popFront, put, save; 63 import std.traits : isArray; 64 65 // Make sure module header code examples work correctly. 66 pure @safe unittest 67 { 68 ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]; 69 70 const(char)[] encoded = Base64.encode(data); 71 assert(encoded == "FPucA9l+"); 72 73 ubyte[] decoded = Base64.decode("FPucA9l+"); 74 assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); 75 } 76 77 /** 78 * Implementation of standard _Base64 encoding. 79 * 80 * See $(LREF Base64Impl) for a description of available methods. 81 */ 82 alias Base64 = Base64Impl!('+', '/'); 83 84 /// 85 pure @safe unittest 86 { 87 ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; 88 assert(Base64.encode(data) == "g9cwegE/"); 89 assert(Base64.decode("g9cwegE/") == data); 90 } 91 92 93 /** 94 * Variation of Base64 encoding that is safe for use in URLs and filenames. 95 * 96 * See $(LREF Base64Impl) for a description of available methods. 97 */ 98 alias Base64URL = Base64Impl!('-', '_'); 99 100 /// 101 pure @safe unittest 102 { 103 ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; 104 assert(Base64URL.encode(data) == "g9cwegE_"); 105 assert(Base64URL.decode("g9cwegE_") == data); 106 } 107 108 /** 109 * Unpadded variation of Base64 encoding that is safe for use in URLs and 110 * filenames, as used in RFCs 4648 and 7515 (JWS/JWT/JWE). 111 * 112 * See $(LREF Base64Impl) for a description of available methods. 113 */ 114 alias Base64URLNoPadding = Base64Impl!('-', '_', Base64.NoPadding); 115 116 /// 117 pure @safe unittest 118 { 119 ubyte[] data = [0x83, 0xd7, 0x30, 0x7b, 0xef]; 120 assert(Base64URLNoPadding.encode(data) == "g9cwe-8"); 121 assert(Base64URLNoPadding.decode("g9cwe-8") == data); 122 } 123 124 /** 125 * Template for implementing Base64 encoding and decoding. 126 * 127 * For most purposes, direct usage of this template is not necessary; instead, 128 * this module provides default implementations: $(LREF Base64), implementing 129 * basic Base64 encoding, and $(LREF Base64URL) and $(LREF Base64URLNoPadding), 130 * that implement the Base64 variant for use in URLs and filenames, with 131 * and without padding, respectively. 132 * 133 * Customized Base64 encoding schemes can be implemented by instantiating this 134 * template with the appropriate arguments. For example: 135 * 136 * ----- 137 * // Non-standard Base64 format for embedding in regular expressions. 138 * alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); 139 * ----- 140 * 141 * NOTE: 142 * Encoded strings will not have any padding if the `Padding` parameter is 143 * set to `NoPadding`. 144 */ 145 template Base64Impl(char Map62th, char Map63th, char Padding = '=') 146 { 147 enum NoPadding = '\0'; /// represents no-padding encoding 148 149 150 // Verify Base64 characters 151 static assert(Map62th < 'A' || Map62th > 'Z', "Character '" ~ Map62th ~ "' cannot be used twice"); 152 static assert(Map63th < 'A' || Map63th > 'Z', "Character '" ~ Map63th ~ "' cannot be used twice"); 153 static assert(Padding < 'A' || Padding > 'Z', "Character '" ~ Padding ~ "' cannot be used twice"); 154 static assert(Map62th < 'a' || Map62th > 'z', "Character '" ~ Map62th ~ "' cannot be used twice"); 155 static assert(Map63th < 'a' || Map63th > 'z', "Character '" ~ Map63th ~ "' cannot be used twice"); 156 static assert(Padding < 'a' || Padding > 'z', "Character '" ~ Padding ~ "' cannot be used twice"); 157 static assert(Map62th < '0' || Map62th > '9', "Character '" ~ Map62th ~ "' cannot be used twice"); 158 static assert(Map63th < '0' || Map63th > '9', "Character '" ~ Map63th ~ "' cannot be used twice"); 159 static assert(Padding < '0' || Padding > '9', "Character '" ~ Padding ~ "' cannot be used twice"); 160 static assert(Map62th != Map63th, "Character '" ~ Map63th ~ "' cannot be used twice"); 161 static assert(Map62th != Padding, "Character '" ~ Padding ~ "' cannot be used twice"); 162 static assert(Map63th != Padding, "Character '" ~ Padding ~ "' cannot be used twice"); 163 static assert(Map62th != NoPadding, "'\\0' is not a valid Base64character"); 164 static assert(Map63th != NoPadding, "'\\0' is not a valid Base64character"); 165 166 167 /* Encode functions */ 168 169 170 private immutable EncodeMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ~ Map62th ~ Map63th; 171 172 173 /** 174 * Calculates the length needed to store the encoded string corresponding 175 * to an input of the given length. 176 * 177 * Params: 178 * sourceLength = Length of the source array. 179 * 180 * Returns: 181 * The length of a Base64 encoding of an array of the given length. 182 */ 183 @safe @nogc 184 pure nothrow size_t encodeLength(in size_t sourceLength) 185 { 186 static if (Padding == NoPadding) 187 return (sourceLength / 3) * 4 + (sourceLength % 3 == 0 ? 0 : sourceLength % 3 == 1 ? 2 : 3); 188 else 189 return (sourceLength / 3 + (sourceLength % 3 ? 1 : 0)) * 4; 190 } 191 192 /// 193 @safe unittest 194 { 195 ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; 196 197 // Allocate a buffer large enough to hold the encoded string. 198 auto buf = new char[Base64.encodeLength(data.length)]; 199 200 Base64.encode(data, buf); 201 assert(buf == "Gis8TV1u"); 202 } 203 204 205 // ubyte[] to char[] 206 207 208 /** 209 * Encode $(D_PARAM source) into a `char[]` buffer using Base64 210 * encoding. 211 * 212 * Params: 213 * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 214 * to _encode. 215 * buffer = The `char[]` buffer to store the encoded result. 216 * 217 * Returns: 218 * The slice of $(D_PARAM buffer) that contains the encoded string. 219 */ 220 @trusted 221 pure char[] encode(R1, R2)(const scope R1 source, return scope R2 buffer) 222 if (isArray!R1 && is(ElementType!R1 : ubyte) && is(R2 == char[])) 223 in 224 { 225 assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding"); 226 } 227 out(result) 228 { 229 assert(result.length == encodeLength(source.length), "The length of result is different from Base64"); 230 } 231 do 232 { 233 immutable srcLen = source.length; 234 if (srcLen == 0) 235 return []; 236 237 immutable blocks = srcLen / 3; 238 immutable remain = srcLen % 3; 239 auto bufptr = buffer.ptr; 240 auto srcptr = source.ptr; 241 242 foreach (Unused; 0 .. blocks) 243 { 244 immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2]; 245 *bufptr++ = EncodeMap[val >> 18 ]; 246 *bufptr++ = EncodeMap[val >> 12 & 0x3f]; 247 *bufptr++ = EncodeMap[val >> 6 & 0x3f]; 248 *bufptr++ = EncodeMap[val & 0x3f]; 249 srcptr += 3; 250 } 251 252 if (remain) 253 { 254 immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0); 255 *bufptr++ = EncodeMap[val >> 18 ]; 256 *bufptr++ = EncodeMap[val >> 12 & 0x3f]; 257 258 final switch (remain) 259 { 260 case 2: 261 *bufptr++ = EncodeMap[val >> 6 & 0x3f]; 262 static if (Padding != NoPadding) 263 *bufptr++ = Padding; 264 break; 265 case 1: 266 static if (Padding != NoPadding) 267 { 268 *bufptr++ = Padding; 269 *bufptr++ = Padding; 270 } 271 break; 272 } 273 } 274 275 // encode method can't assume buffer length. So, slice needed. 276 return buffer[0 .. bufptr - buffer.ptr]; 277 } 278 279 /// 280 @nogc nothrow @safe unittest 281 { 282 ubyte[6] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; 283 char[32] buffer; // much bigger than necessary 284 285 // Just to be sure... 286 auto encodedLength = Base64.encodeLength(data.length); 287 assert(buffer.length >= encodedLength); 288 289 // encode() returns a slice to the provided buffer. 290 auto encoded = Base64.encode(data[], buffer[]); 291 assert(encoded is buffer[0 .. encodedLength]); 292 assert(encoded == "g9cwegE/"); 293 } 294 295 296 // InputRange to char[] 297 298 299 /** 300 * ditto 301 */ 302 char[] encode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 && 303 is(ElementType!R1 : ubyte) && hasLength!R1 && 304 is(R2 == char[])) 305 in 306 { 307 assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding"); 308 } 309 out(result) 310 { 311 // @@@BUG@@@ D's DbC can't caputre an argument of function and store the result of precondition. 312 //assert(result.length == encodeLength(source.length), "The length of result is different from Base64"); 313 } 314 do 315 { 316 immutable srcLen = source.length; 317 if (srcLen == 0) 318 return []; 319 320 immutable blocks = srcLen / 3; 321 immutable remain = srcLen % 3; 322 auto bufptr = buffer.ptr; 323 324 foreach (Unused; 0 .. blocks) 325 { 326 immutable v1 = source.front; source.popFront(); 327 immutable v2 = source.front; source.popFront(); 328 immutable v3 = source.front; source.popFront(); 329 immutable val = v1 << 16 | v2 << 8 | v3; 330 *bufptr++ = EncodeMap[val >> 18 ]; 331 *bufptr++ = EncodeMap[val >> 12 & 0x3f]; 332 *bufptr++ = EncodeMap[val >> 6 & 0x3f]; 333 *bufptr++ = EncodeMap[val & 0x3f]; 334 } 335 336 if (remain) 337 { 338 size_t val = source.front << 16; 339 if (remain == 2) 340 { 341 source.popFront(); 342 val |= source.front << 8; 343 } 344 345 *bufptr++ = EncodeMap[val >> 18 ]; 346 *bufptr++ = EncodeMap[val >> 12 & 0x3f]; 347 348 final switch (remain) 349 { 350 case 2: 351 *bufptr++ = EncodeMap[val >> 6 & 0x3f]; 352 static if (Padding != NoPadding) 353 *bufptr++ = Padding; 354 break; 355 case 1: 356 static if (Padding != NoPadding) 357 { 358 *bufptr++ = Padding; 359 *bufptr++ = Padding; 360 } 361 break; 362 } 363 } 364 365 // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'. 366 version (StdUnittest) 367 assert( 368 bufptr - buffer.ptr == encodeLength(srcLen), 369 "The length of result is different from Base64" 370 ); 371 372 // encode method can't assume buffer length. So, slice needed. 373 return buffer[0 .. bufptr - buffer.ptr]; 374 } 375 376 377 // ubyte[] to OutputRange 378 379 380 /** 381 * Encodes $(D_PARAM source) into an 382 * $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) using 383 * Base64 encoding. 384 * 385 * Params: 386 * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 387 * to _encode. 388 * range = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) 389 * to store the encoded result. 390 * 391 * Returns: 392 * The number of times the output range's `put` method was invoked. 393 */ 394 size_t encode(E, R)(scope const(E)[] source, auto ref R range) 395 if (is(E : ubyte) && isOutputRange!(R, char) && !is(R == char[])) 396 out(result) 397 { 398 assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); 399 } 400 do 401 { 402 immutable srcLen = source.length; 403 if (srcLen == 0) 404 return 0; 405 406 immutable blocks = srcLen / 3; 407 immutable remain = srcLen % 3; 408 auto s = source; // copy for out contract length check 409 size_t pcount; 410 411 foreach (Unused; 0 .. blocks) 412 { 413 immutable val = s[0] << 16 | s[1] << 8 | s[2]; 414 put(range, EncodeMap[val >> 18 ]); 415 put(range, EncodeMap[val >> 12 & 0x3f]); 416 put(range, EncodeMap[val >> 6 & 0x3f]); 417 put(range, EncodeMap[val & 0x3f]); 418 s = s[3 .. $]; 419 pcount += 4; 420 } 421 422 if (remain) 423 { 424 immutable val = s[0] << 16 | (remain == 2 ? s[1] << 8 : 0); 425 put(range, EncodeMap[val >> 18 ]); 426 put(range, EncodeMap[val >> 12 & 0x3f]); 427 pcount += 2; 428 429 final switch (remain) 430 { 431 case 2: 432 put(range, EncodeMap[val >> 6 & 0x3f]); 433 pcount++; 434 435 static if (Padding != NoPadding) 436 { 437 put(range, Padding); 438 pcount++; 439 } 440 break; 441 case 1: 442 static if (Padding != NoPadding) 443 { 444 put(range, Padding); 445 put(range, Padding); 446 pcount += 2; 447 } 448 break; 449 } 450 } 451 452 return pcount; 453 } 454 455 /// 456 @safe pure nothrow unittest 457 { 458 import std.array : appender; 459 460 auto output = appender!string(); 461 ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; 462 463 // This overload of encode() returns the number of calls to the output 464 // range's put method. 465 assert(Base64.encode(data, output) == 8); 466 assert(output.data == "Gis8TV1u"); 467 } 468 469 470 // InputRange to OutputRange 471 472 473 /** 474 * ditto 475 */ 476 size_t encode(R1, R2)(R1 source, auto ref R2 range) 477 if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : ubyte) && 478 hasLength!R1 && !is(R2 == char[]) && isOutputRange!(R2, char)) 479 { 480 immutable srcLen = source.length; 481 if (srcLen == 0) 482 return 0; 483 484 immutable blocks = srcLen / 3; 485 immutable remain = srcLen % 3; 486 size_t pcount; 487 488 foreach (Unused; 0 .. blocks) 489 { 490 immutable v1 = source.front; source.popFront(); 491 immutable v2 = source.front; source.popFront(); 492 immutable v3 = source.front; source.popFront(); 493 immutable val = v1 << 16 | v2 << 8 | v3; 494 put(range, EncodeMap[val >> 18 ]); 495 put(range, EncodeMap[val >> 12 & 0x3f]); 496 put(range, EncodeMap[val >> 6 & 0x3f]); 497 put(range, EncodeMap[val & 0x3f]); 498 pcount += 4; 499 } 500 501 if (remain) 502 { 503 size_t val = source.front << 16; 504 if (remain == 2) 505 { 506 source.popFront(); 507 val |= source.front << 8; 508 } 509 510 put(range, EncodeMap[val >> 18 ]); 511 put(range, EncodeMap[val >> 12 & 0x3f]); 512 pcount += 2; 513 514 final switch (remain) 515 { 516 case 2: 517 put(range, EncodeMap[val >> 6 & 0x3f]); 518 pcount++; 519 520 static if (Padding != NoPadding) 521 { 522 put(range, Padding); 523 pcount++; 524 } 525 break; 526 case 1: 527 static if (Padding != NoPadding) 528 { 529 put(range, Padding); 530 put(range, Padding); 531 pcount += 2; 532 } 533 break; 534 } 535 } 536 537 // @@@BUG@@@ Workaround for DbC problem. 538 version (StdUnittest) 539 assert( 540 pcount == encodeLength(srcLen), 541 "The number of put is different from the length of Base64" 542 ); 543 544 return pcount; 545 } 546 547 548 /** 549 * Encodes $(D_PARAM source) to newly-allocated buffer. 550 * 551 * This convenience method alleviates the need to manually manage output 552 * buffers. 553 * 554 * Params: 555 * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 556 * to _encode. 557 * 558 * Returns: 559 * A newly-allocated `char[]` buffer containing the encoded string. 560 */ 561 @safe 562 pure char[] encode(Range)(Range source) if (isArray!Range && is(ElementType!Range : ubyte)) 563 { 564 return encode(source, new char[encodeLength(source.length)]); 565 } 566 567 /// 568 @safe unittest 569 { 570 ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; 571 assert(Base64.encode(data) == "Gis8TV1u"); 572 } 573 574 575 /** 576 * ditto 577 */ 578 char[] encode(Range)(Range source) if (!isArray!Range && isInputRange!Range && 579 is(ElementType!Range : ubyte) && hasLength!Range) 580 { 581 return encode(source, new char[encodeLength(source.length)]); 582 } 583 584 585 /** 586 * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that 587 * iterates over the respective Base64 encodings of a range of data items. 588 * 589 * This range will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 590 * if the underlying data source is at least a forward range. 591 * 592 * Note: This struct is not intended to be created in user code directly; 593 * use the $(LREF encoder) function instead. 594 */ 595 struct Encoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(ubyte)[]) || 596 is(ElementType!Range : const(char)[]))) 597 { 598 private: 599 Range range_; 600 char[] buffer_, encoded_; 601 602 603 public: 604 this(Range range) 605 { 606 range_ = range; 607 if (!empty) 608 doEncoding(); 609 } 610 611 612 /** 613 * Returns: 614 * true if there is no more encoded data left. 615 */ 616 @property @trusted 617 bool empty() 618 { 619 return range_.empty; 620 } 621 622 623 /** 624 * Returns: The current chunk of encoded data. 625 */ 626 @property @safe 627 nothrow char[] front() 628 { 629 return encoded_; 630 } 631 632 633 /** 634 * Advance the range to the next chunk of encoded data. 635 * 636 * Throws: 637 * `Base64Exception` If invoked when 638 * $(LREF2 .Base64Impl.Encoder.empty, empty) returns `true`. 639 */ 640 void popFront() 641 { 642 assert(!empty, "Cannot call popFront on Encoder with no data remaining"); 643 644 range_.popFront(); 645 646 /* 647 * This check is very ugly. I think this is a Range's flaw. 648 * I very strongly want the Range guideline for unified implementation. 649 * 650 * In this case, Encoder becomes a beautiful implementation if 'front' performs Base64 encoding. 651 */ 652 if (!empty) 653 doEncoding(); 654 } 655 656 657 static if (isForwardRange!Range) 658 { 659 /** 660 * Save the current iteration state of the range. 661 * 662 * This method is only available if the underlying range is a 663 * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives). 664 * 665 * Returns: 666 * A copy of `this`. 667 */ 668 @property 669 typeof(this) save() 670 { 671 typeof(return) encoder; 672 673 encoder.range_ = range_.save; 674 encoder.buffer_ = buffer_.dup; 675 encoder.encoded_ = encoder.buffer_[0 .. encoded_.length]; 676 677 return encoder; 678 } 679 } 680 681 682 private: 683 void doEncoding() 684 { 685 auto data = cast(const(ubyte)[])range_.front; 686 auto size = encodeLength(data.length); 687 if (size > buffer_.length) 688 buffer_.length = size; 689 690 encoded_ = encode(data, buffer_); 691 } 692 } 693 694 695 /** 696 * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that 697 * iterates over the encoded bytes of the given source data. 698 * 699 * It will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 700 * if the underlying data source is at least a forward range. 701 * 702 * Note: This struct is not intended to be created in user code directly; 703 * use the $(LREF encoder) function instead. 704 */ 705 struct Encoder(Range) if (isInputRange!Range && is(ElementType!Range : ubyte)) 706 { 707 private: 708 Range range_; 709 ubyte first; 710 int pos, padding; 711 712 713 public: 714 this(Range range) 715 { 716 range_ = range; 717 static if (isForwardRange!Range) 718 range_ = range_.save; 719 720 if (range_.empty) 721 pos = -1; 722 else 723 popFront(); 724 } 725 726 727 /** 728 * Returns: 729 * true if there are no more encoded characters to be iterated. 730 */ 731 @property @safe 732 nothrow bool empty() const 733 { 734 static if (Padding == NoPadding) 735 return pos < 0; 736 else 737 return pos < 0 && !padding; 738 } 739 740 741 /** 742 * Returns: The current encoded character. 743 */ 744 @property @safe 745 nothrow ubyte front() 746 { 747 return first; 748 } 749 750 751 /** 752 * Advance to the next encoded character. 753 * 754 * Throws: 755 * `Base64Exception` If invoked when $(LREF2 .Base64Impl.Encoder.empty.2, 756 * empty) returns `true`. 757 */ 758 void popFront() 759 { 760 assert(!empty, "Cannot call popFront on Encoder with no data remaining"); 761 762 static if (Padding != NoPadding) 763 if (padding) 764 { 765 first = Padding; 766 pos = -1; 767 padding--; 768 return; 769 } 770 771 if (range_.empty) 772 { 773 pos = -1; 774 return; 775 } 776 777 final switch (pos) 778 { 779 case 0: 780 first = EncodeMap[range_.front >> 2]; 781 break; 782 case 1: 783 immutable t = (range_.front & 0b11) << 4; 784 range_.popFront(); 785 786 if (range_.empty) 787 { 788 first = EncodeMap[t]; 789 padding = 3; 790 } 791 else 792 { 793 first = EncodeMap[t | (range_.front >> 4)]; 794 } 795 break; 796 case 2: 797 immutable t = (range_.front & 0b1111) << 2; 798 range_.popFront(); 799 800 if (range_.empty) 801 { 802 first = EncodeMap[t]; 803 padding = 2; 804 } 805 else 806 { 807 first = EncodeMap[t | (range_.front >> 6)]; 808 } 809 break; 810 case 3: 811 first = EncodeMap[range_.front & 0b111111]; 812 range_.popFront(); 813 break; 814 } 815 816 ++pos %= 4; 817 } 818 819 820 static if (isForwardRange!Range) 821 { 822 /** 823 * Save the current iteration state of the range. 824 * 825 * This method is only available if the underlying range is a 826 * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives). 827 * 828 * Returns: 829 * A copy of `this`. 830 */ 831 @property 832 typeof(this) save() 833 { 834 auto encoder = this; 835 encoder.range_ = encoder.range_.save; 836 return encoder; 837 } 838 } 839 } 840 841 842 /** 843 * Construct an `Encoder` that iterates over the Base64 encoding of the 844 * given $(REF_ALTTEXT input range, isInputRange, std,range,primitives). 845 * 846 * Params: 847 * range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 848 * over the data to be encoded. 849 * 850 * Returns: 851 * If $(D_PARAM range) is a range of bytes, an `Encoder` that iterates 852 * over the bytes of the corresponding Base64 encoding. 853 * 854 * If $(D_PARAM range) is a range of ranges of bytes, an `Encoder` that 855 * iterates over the Base64 encoded strings of each element of the range. 856 * 857 * In both cases, the returned `Encoder` will be a 858 * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) if the 859 * given `range` is at least a forward range, otherwise it will be only 860 * an input range. 861 * 862 * Example: 863 * This example encodes the input one line at a time. 864 * ----- 865 * File f = File("text.txt", "r"); 866 * scope(exit) f.close(); 867 * 868 * uint line = 0; 869 * foreach (encoded; Base64.encoder(f.byLine())) 870 * { 871 * writeln(++line, ". ", encoded); 872 * } 873 * ----- 874 * 875 * Example: 876 * This example encodes the input data one byte at a time. 877 * ----- 878 * ubyte[] data = cast(ubyte[]) "0123456789"; 879 * 880 * // The ElementType of data is not aggregation type 881 * foreach (encoded; Base64.encoder(data)) 882 * { 883 * writeln(encoded); 884 * } 885 * ----- 886 */ 887 Encoder!(Range) encoder(Range)(Range range) if (isInputRange!Range) 888 { 889 return typeof(return)(range); 890 } 891 892 893 /* Decode functions */ 894 895 896 private immutable int[char.max + 1] DecodeMap = [ 897 'A':0b000000, 'B':0b000001, 'C':0b000010, 'D':0b000011, 'E':0b000100, 898 'F':0b000101, 'G':0b000110, 'H':0b000111, 'I':0b001000, 'J':0b001001, 899 'K':0b001010, 'L':0b001011, 'M':0b001100, 'N':0b001101, 'O':0b001110, 900 'P':0b001111, 'Q':0b010000, 'R':0b010001, 'S':0b010010, 'T':0b010011, 901 'U':0b010100, 'V':0b010101, 'W':0b010110, 'X':0b010111, 'Y':0b011000, 902 'Z':0b011001, 'a':0b011010, 'b':0b011011, 'c':0b011100, 'd':0b011101, 903 'e':0b011110, 'f':0b011111, 'g':0b100000, 'h':0b100001, 'i':0b100010, 904 'j':0b100011, 'k':0b100100, 'l':0b100101, 'm':0b100110, 'n':0b100111, 905 'o':0b101000, 'p':0b101001, 'q':0b101010, 'r':0b101011, 's':0b101100, 906 't':0b101101, 'u':0b101110, 'v':0b101111, 'w':0b110000, 'x':0b110001, 907 'y':0b110010, 'z':0b110011, '0':0b110100, '1':0b110101, '2':0b110110, 908 '3':0b110111, '4':0b111000, '5':0b111001, '6':0b111010, '7':0b111011, 909 '8':0b111100, '9':0b111101, Map62th:0b111110, Map63th:0b111111, Padding:-1 910 ]; 911 912 913 /** 914 * Given a Base64 encoded string, calculates the length of the decoded 915 * string. 916 * 917 * Params: 918 * sourceLength = The length of the Base64 encoding. 919 * 920 * Returns: 921 * The length of the decoded string corresponding to a Base64 encoding of 922 * length $(D_PARAM sourceLength). 923 */ 924 @safe 925 pure @nogc nothrow size_t decodeLength(in size_t sourceLength) 926 { 927 static if (Padding == NoPadding) 928 return (sourceLength / 4) * 3 + (sourceLength % 4 < 2 ? 0 : sourceLength % 4 == 2 ? 1 : 2); 929 else 930 return (sourceLength / 4) * 3; 931 } 932 933 /// 934 @safe unittest 935 { 936 auto encoded = "Gis8TV1u"; 937 938 // Allocate a sufficiently large buffer to hold to decoded result. 939 auto buffer = new ubyte[Base64.decodeLength(encoded.length)]; 940 941 Base64.decode(encoded, buffer); 942 assert(buffer == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); 943 } 944 945 946 // Used in decode contracts. Calculates the actual size the decoded 947 // result should have, taking into account trailing padding. 948 @safe 949 pure @nogc nothrow private size_t realDecodeLength(R)(R source) 950 { 951 auto expect = decodeLength(source.length); 952 static if (Padding != NoPadding) 953 { 954 if (source.length % 4 == 0) 955 { 956 expect -= source.length == 0 ? 0 : 957 source[$ - 2] == Padding ? 2 : 958 source[$ - 1] == Padding ? 1 : 0; 959 } 960 } 961 return expect; 962 } 963 964 965 // char[] to ubyte[] 966 967 968 /** 969 * Decodes $(D_PARAM source) into the given buffer. 970 * 971 * Params: 972 * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 973 * to _decode. 974 * buffer = The buffer to store decoded result. 975 * 976 * Returns: 977 * The slice of $(D_PARAM buffer) containing the decoded result. 978 * 979 * Throws: 980 * `Base64Exception` if $(D_PARAM source) contains characters outside the 981 * base alphabet of the current Base64 encoding scheme. 982 */ 983 @trusted 984 pure ubyte[] decode(R1, R2)(in R1 source, return scope R2 buffer) if (isArray!R1 && is(ElementType!R1 : dchar) && 985 is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) 986 in 987 { 988 assert(buffer.length >= realDecodeLength(source), "Insufficient buffer for decoding"); 989 } 990 out(result) 991 { 992 immutable expect = realDecodeLength(source); 993 assert(result.length == expect, "The length of result is different from the expected length"); 994 } 995 do 996 { 997 immutable srcLen = source.length; 998 if (srcLen == 0) 999 return []; 1000 static if (Padding != NoPadding) 1001 enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); 1002 1003 immutable blocks = srcLen / 4; 1004 auto srcptr = source.ptr; 1005 auto bufptr = buffer.ptr; 1006 1007 foreach (Unused; 0 .. blocks) 1008 { 1009 immutable v1 = decodeChar(*srcptr++); 1010 immutable v2 = decodeChar(*srcptr++); 1011 1012 *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); 1013 1014 immutable v3 = decodeChar(*srcptr++); 1015 if (v3 == -1) 1016 break; 1017 1018 *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff); 1019 1020 immutable v4 = decodeChar(*srcptr++); 1021 if (v4 == -1) 1022 break; 1023 1024 *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff); 1025 } 1026 1027 static if (Padding == NoPadding) 1028 { 1029 immutable remain = srcLen % 4; 1030 1031 if (remain) 1032 { 1033 immutable v1 = decodeChar(*srcptr++); 1034 immutable v2 = decodeChar(*srcptr++); 1035 1036 *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); 1037 1038 if (remain == 3) 1039 *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff); 1040 } 1041 } 1042 1043 return buffer[0 .. bufptr - buffer.ptr]; 1044 } 1045 1046 /// 1047 @safe unittest 1048 { 1049 auto encoded = "Gis8TV1u"; 1050 ubyte[32] buffer; // much bigger than necessary 1051 1052 // Just to be sure... 1053 auto decodedLength = Base64.decodeLength(encoded.length); 1054 assert(buffer.length >= decodedLength); 1055 1056 // decode() returns a slice of the given buffer. 1057 auto decoded = Base64.decode(encoded, buffer[]); 1058 assert(decoded is buffer[0 .. decodedLength]); 1059 assert(decoded == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); 1060 } 1061 1062 // InputRange to ubyte[] 1063 1064 1065 /** 1066 * ditto 1067 */ 1068 ubyte[] decode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 && 1069 is(ElementType!R1 : dchar) && hasLength!R1 && 1070 is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) 1071 in 1072 { 1073 assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding"); 1074 } 1075 do 1076 { 1077 immutable srcLen = source.length; 1078 if (srcLen == 0) 1079 return []; 1080 static if (Padding != NoPadding) 1081 enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); 1082 1083 immutable blocks = srcLen / 4; 1084 auto bufptr = buffer.ptr; 1085 1086 foreach (Unused; 0 .. blocks) 1087 { 1088 immutable v1 = decodeChar(source.front); source.popFront(); 1089 immutable v2 = decodeChar(source.front); source.popFront(); 1090 1091 *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); 1092 1093 immutable v3 = decodeChar(source.front); 1094 if (v3 == -1) 1095 break; 1096 1097 *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff); 1098 source.popFront(); 1099 1100 immutable v4 = decodeChar(source.front); 1101 if (v4 == -1) 1102 break; 1103 1104 *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff); 1105 source.popFront(); 1106 } 1107 1108 static if (Padding == NoPadding) 1109 { 1110 immutable remain = srcLen % 4; 1111 1112 if (remain) 1113 { 1114 immutable v1 = decodeChar(source.front); source.popFront(); 1115 immutable v2 = decodeChar(source.front); 1116 1117 *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); 1118 1119 if (remain == 3) 1120 { 1121 source.popFront(); 1122 *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff); 1123 } 1124 } 1125 } 1126 1127 // We need to do the check here because we have consumed the length 1128 version (StdUnittest) 1129 assert( 1130 (bufptr - buffer.ptr) >= (decodeLength(srcLen) - 2), 1131 "The length of result is smaller than expected length" 1132 ); 1133 1134 return buffer[0 .. bufptr - buffer.ptr]; 1135 } 1136 1137 1138 // char[] to OutputRange 1139 1140 1141 /** 1142 * Decodes $(D_PARAM source) into a given 1143 * $(REF_ALTTEXT output range, isOutputRange, std,range,primitives). 1144 * 1145 * Params: 1146 * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 1147 * to _decode. 1148 * range = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) 1149 * to store the decoded result. 1150 * 1151 * Returns: 1152 * The number of times the output range's `put` method was invoked. 1153 * 1154 * Throws: 1155 * `Base64Exception` if $(D_PARAM source) contains characters outside the 1156 * base alphabet of the current Base64 encoding scheme. 1157 */ 1158 size_t decode(R1, R2)(in R1 source, auto ref R2 range) 1159 if (isArray!R1 && is(ElementType!R1 : dchar) && 1160 !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) 1161 out(result) 1162 { 1163 immutable expect = realDecodeLength(source); 1164 assert(result == expect, "The result of decode is different from the expected"); 1165 } 1166 do 1167 { 1168 immutable srcLen = source.length; 1169 if (srcLen == 0) 1170 return 0; 1171 static if (Padding != NoPadding) 1172 enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); 1173 1174 immutable blocks = srcLen / 4; 1175 auto srcptr = source.ptr; 1176 size_t pcount; 1177 1178 foreach (Unused; 0 .. blocks) 1179 { 1180 immutable v1 = decodeChar(*srcptr++); 1181 immutable v2 = decodeChar(*srcptr++); 1182 1183 put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); 1184 pcount++; 1185 1186 immutable v3 = decodeChar(*srcptr++); 1187 if (v3 == -1) 1188 break; 1189 1190 put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff)); 1191 pcount++; 1192 1193 immutable v4 = decodeChar(*srcptr++); 1194 if (v4 == -1) 1195 break; 1196 1197 put(range, cast(ubyte)((v3 << 6 | v4) & 0xff)); 1198 pcount++; 1199 } 1200 1201 static if (Padding == NoPadding) 1202 { 1203 immutable remain = srcLen % 4; 1204 1205 if (remain) 1206 { 1207 immutable v1 = decodeChar(*srcptr++); 1208 immutable v2 = decodeChar(*srcptr++); 1209 1210 put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); 1211 pcount++; 1212 1213 if (remain == 3) 1214 { 1215 put(range, cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff)); 1216 pcount++; 1217 } 1218 } 1219 } 1220 1221 return pcount; 1222 } 1223 1224 /// 1225 @system unittest 1226 { 1227 struct OutputRange 1228 { 1229 ubyte[] result; 1230 void put(ubyte b) { result ~= b; } 1231 } 1232 OutputRange output; 1233 1234 // This overload of decode() returns the number of calls to put(). 1235 assert(Base64.decode("Gis8TV1u", output) == 6); 1236 assert(output.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); 1237 } 1238 1239 1240 // InputRange to OutputRange 1241 1242 1243 /** 1244 * ditto 1245 */ 1246 size_t decode(R1, R2)(R1 source, auto ref R2 range) 1247 if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : dchar) && 1248 hasLength!R1 && !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) 1249 out(result) 1250 { 1251 // @@@BUG@@@ Workaround for DbC problem. 1252 //immutable expect = decodeLength(source.length) - 2; 1253 //assert(result >= expect, "The length of result is smaller than expected length"); 1254 } 1255 do 1256 { 1257 immutable srcLen = source.length; 1258 if (srcLen == 0) 1259 return 0; 1260 static if (Padding != NoPadding) 1261 enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); 1262 1263 immutable blocks = srcLen / 4; 1264 size_t pcount; 1265 1266 foreach (Unused; 0 .. blocks) 1267 { 1268 immutable v1 = decodeChar(source.front); source.popFront(); 1269 immutable v2 = decodeChar(source.front); source.popFront(); 1270 1271 put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); 1272 pcount++; 1273 1274 immutable v3 = decodeChar(source.front); 1275 if (v3 == -1) 1276 break; 1277 1278 put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff)); 1279 source.popFront(); 1280 pcount++; 1281 1282 immutable v4 = decodeChar(source.front); 1283 if (v4 == -1) 1284 break; 1285 1286 put(range, cast(ubyte)((v3 << 6 | v4) & 0xff)); 1287 source.popFront(); 1288 pcount++; 1289 } 1290 1291 static if (Padding == NoPadding) 1292 { 1293 immutable remain = srcLen % 4; 1294 1295 if (remain) 1296 { 1297 immutable v1 = decodeChar(source.front); source.popFront(); 1298 immutable v2 = decodeChar(source.front); 1299 1300 put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); 1301 pcount++; 1302 1303 if (remain == 3) 1304 { 1305 source.popFront(); 1306 put(range, cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff)); 1307 pcount++; 1308 } 1309 } 1310 } 1311 1312 // @@@BUG@@@ Workaround for DbC problem. 1313 version (StdUnittest) 1314 assert( 1315 pcount >= (decodeLength(srcLen) - 2), 1316 "The length of result is smaller than expected length" 1317 ); 1318 1319 return pcount; 1320 } 1321 1322 1323 /** 1324 * Decodes $(D_PARAM source) into newly-allocated buffer. 1325 * 1326 * This convenience method alleviates the need to manually manage decoding 1327 * buffers. 1328 * 1329 * Params: 1330 * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 1331 * to _decode. 1332 * 1333 * Returns: 1334 * A newly-allocated `ubyte[]` buffer containing the decoded string. 1335 */ 1336 @safe 1337 pure ubyte[] decode(Range)(Range source) if (isArray!Range && is(ElementType!Range : dchar)) 1338 { 1339 return decode(source, new ubyte[decodeLength(source.length)]); 1340 } 1341 1342 /// 1343 @safe unittest 1344 { 1345 auto data = "Gis8TV1u"; 1346 assert(Base64.decode(data) == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); 1347 } 1348 1349 1350 /** 1351 * ditto 1352 */ 1353 ubyte[] decode(Range)(Range source) if (!isArray!Range && isInputRange!Range && 1354 is(ElementType!Range : dchar) && hasLength!Range) 1355 { 1356 return decode(source, new ubyte[decodeLength(source.length)]); 1357 } 1358 1359 1360 /** 1361 * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that 1362 * iterates over the decoded data of a range of Base64 encodings. 1363 * 1364 * This range will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 1365 * if the underlying data source is at least a forward range. 1366 * 1367 * Note: This struct is not intended to be created in user code directly; 1368 * use the $(LREF decoder) function instead. 1369 */ 1370 struct Decoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(char)[]) || 1371 is(ElementType!Range : const(ubyte)[]))) 1372 { 1373 private: 1374 Range range_; 1375 ubyte[] buffer_, decoded_; 1376 1377 1378 public: 1379 this(Range range) 1380 { 1381 range_ = range; 1382 if (!empty) 1383 doDecoding(); 1384 } 1385 1386 1387 /** 1388 * Returns: 1389 * true if there are no more elements to be iterated. 1390 */ 1391 @property @trusted 1392 bool empty() 1393 { 1394 return range_.empty; 1395 } 1396 1397 1398 /** 1399 * Returns: The decoding of the current element in the input. 1400 */ 1401 @property @safe 1402 nothrow ubyte[] front() 1403 { 1404 return decoded_; 1405 } 1406 1407 1408 /** 1409 * Advance to the next element in the input to be decoded. 1410 * 1411 * Throws: 1412 * `Base64Exception` if invoked when $(LREF2 .Base64Impl.Decoder.empty, 1413 * empty) returns `true`. 1414 */ 1415 void popFront() 1416 { 1417 assert(!empty, "Cannot call popFront on Decoder with no data remaining."); 1418 1419 range_.popFront(); 1420 1421 /* 1422 * I mentioned Encoder's popFront. 1423 */ 1424 if (!empty) 1425 doDecoding(); 1426 } 1427 1428 1429 static if (isForwardRange!Range) 1430 { 1431 /** 1432 * Saves the current iteration state. 1433 * 1434 * This method is only available if the underlying range is a 1435 * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 1436 * 1437 * Returns: A copy of `this`. 1438 */ 1439 @property 1440 typeof(this) save() 1441 { 1442 typeof(return) decoder; 1443 1444 decoder.range_ = range_.save; 1445 decoder.buffer_ = buffer_.dup; 1446 decoder.decoded_ = decoder.buffer_[0 .. decoded_.length]; 1447 1448 return decoder; 1449 } 1450 } 1451 1452 1453 private: 1454 void doDecoding() 1455 { 1456 auto data = cast(const(char)[])range_.front; 1457 1458 static if (Padding == NoPadding) 1459 { 1460 while (data.length % 4 == 1) 1461 { 1462 range_.popFront(); 1463 data ~= cast(const(char)[])range_.front; 1464 } 1465 } 1466 else 1467 { 1468 while (data.length % 4 != 0) 1469 { 1470 range_.popFront(); 1471 enforce(!range_.empty, new Base64Exception("Invalid length of encoded data")); 1472 data ~= cast(const(char)[])range_.front; 1473 } 1474 } 1475 1476 auto size = decodeLength(data.length); 1477 if (size > buffer_.length) 1478 buffer_.length = size; 1479 1480 decoded_ = decode(data, buffer_); 1481 } 1482 } 1483 1484 1485 /** 1486 * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that 1487 * iterates over the bytes of data decoded from a Base64 encoded string. 1488 * 1489 * This range will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 1490 * if the underlying data source is at least a forward range. 1491 * 1492 * Note: This struct is not intended to be created in user code directly; 1493 * use the $(LREF decoder) function instead. 1494 */ 1495 struct Decoder(Range) if (isInputRange!Range && is(ElementType!Range : char)) 1496 { 1497 private: 1498 Range range_; 1499 ubyte first; 1500 int pos; 1501 1502 1503 public: 1504 this(Range range) 1505 { 1506 range_ = range; 1507 static if (isForwardRange!Range) 1508 range_ = range_.save; 1509 1510 static if (Padding != NoPadding && hasLength!Range) 1511 enforce(range_.length % 4 == 0, new Base64Exception("Invalid length of encoded data")); 1512 1513 if (range_.empty) 1514 pos = -1; 1515 else 1516 popFront(); 1517 } 1518 1519 1520 /** 1521 * Returns: 1522 * true if there are no more elements to be iterated. 1523 */ 1524 @property @safe 1525 nothrow bool empty() const 1526 { 1527 return pos < 0; 1528 } 1529 1530 1531 /** 1532 * Returns: The current decoded byte. 1533 */ 1534 @property @safe 1535 nothrow ubyte front() 1536 { 1537 return first; 1538 } 1539 1540 1541 /** 1542 * Advance to the next decoded byte. 1543 * 1544 * Throws: 1545 * `Base64Exception` if invoked when $(LREF2 .Base64Impl.Decoder.empty, 1546 * empty) returns `true`. 1547 */ 1548 void popFront() 1549 { 1550 enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining")); 1551 1552 static if (Padding == NoPadding) 1553 { 1554 bool endCondition() 1555 { 1556 return range_.empty; 1557 } 1558 } 1559 else 1560 { 1561 bool endCondition() 1562 { 1563 enforce(!range_.empty, new Base64Exception("Missing padding")); 1564 return range_.front == Padding; 1565 } 1566 } 1567 1568 if (range_.empty || range_.front == Padding) 1569 { 1570 pos = -1; 1571 return; 1572 } 1573 1574 final switch (pos) 1575 { 1576 case 0: 1577 enforce(!endCondition(), new Base64Exception("Premature end of data found")); 1578 1579 immutable t = DecodeMap[range_.front] << 2; 1580 range_.popFront(); 1581 1582 enforce(!endCondition(), new Base64Exception("Premature end of data found")); 1583 first = cast(ubyte)(t | (DecodeMap[range_.front] >> 4)); 1584 break; 1585 case 1: 1586 immutable t = (DecodeMap[range_.front] & 0b1111) << 4; 1587 range_.popFront(); 1588 1589 if (endCondition()) 1590 { 1591 pos = -1; 1592 return; 1593 } 1594 else 1595 { 1596 first = cast(ubyte)(t | (DecodeMap[range_.front] >> 2)); 1597 } 1598 break; 1599 case 2: 1600 immutable t = (DecodeMap[range_.front] & 0b11) << 6; 1601 range_.popFront(); 1602 1603 if (endCondition()) 1604 { 1605 pos = -1; 1606 return; 1607 } 1608 else 1609 { 1610 first = cast(ubyte)(t | DecodeMap[range_.front]); 1611 } 1612 1613 range_.popFront(); 1614 break; 1615 } 1616 1617 ++pos %= 3; 1618 } 1619 1620 1621 static if (isForwardRange!Range) 1622 { 1623 /** 1624 * Saves the current iteration state. 1625 * 1626 * This method is only available if the underlying range is a 1627 * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 1628 * 1629 * Returns: A copy of `this`. 1630 */ 1631 @property 1632 typeof(this) save() 1633 { 1634 auto decoder = this; 1635 decoder.range_ = decoder.range_.save; 1636 return decoder; 1637 } 1638 } 1639 } 1640 1641 1642 /** 1643 * Construct a `Decoder` that iterates over the decoding of the given 1644 * Base64 encoded data. 1645 * 1646 * Params: 1647 * range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 1648 * over the data to be decoded, or a `char` array. Will not accept 1649 * `wchar[]` nor `dchar[]`. 1650 * 1651 * Returns: 1652 * If $(D_PARAM range) is a range or array of `char`, a `Decoder` that 1653 * iterates over the bytes of the corresponding Base64 decoding. 1654 * 1655 * If $(D_PARAM range) is a range of ranges of characters, a `Decoder` 1656 * that iterates over the decoded strings corresponding to each element of 1657 * the range. 1658 * 1659 * In both cases, the returned `Decoder` will be a 1660 * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) if the 1661 * given `range` is at least a forward range, otherwise it will be only 1662 * an input range. 1663 * 1664 * If the input data contains characters not found in the base alphabet of 1665 * the current Base64 encoding scheme, the returned range may throw a 1666 * `Base64Exception`. 1667 * 1668 * Example: 1669 * This example shows decoding over a range of input data lines. 1670 * ----- 1671 * foreach (decoded; Base64.decoder(stdin.byLine())) 1672 * { 1673 * writeln(decoded); 1674 * } 1675 * ----- 1676 * 1677 * This example shows decoding one byte at a time. 1678 * ----- 1679 * auto encoded = Base64.encoder(cast(ubyte[])"0123456789"); 1680 * foreach (n; map!q{a - '0'}(Base64.decoder(encoded))) 1681 * { 1682 * writeln(n); 1683 * } 1684 * ----- 1685 */ 1686 Decoder!(Range) decoder(Range)(Range range) if (isInputRange!Range) 1687 { 1688 return typeof(return)(range); 1689 } 1690 1691 /// ditto 1692 Decoder!(const(ubyte)[]) decoder()(const(char)[] range) 1693 { 1694 import std.string : representation; 1695 return typeof(return)(range.representation); 1696 } 1697 1698 /// 1699 @safe pure unittest 1700 { 1701 import std.algorithm.comparison : equal; 1702 string encoded = 1703 "VGhvdSBzaGFsdCBuZXZlciBjb250aW51ZSBhZnRlciBhc3NlcnRpbmcgbnVsbA=="; 1704 1705 assert(Base64.decoder(encoded) 1706 .equal("Thou shalt never continue after asserting null")); 1707 } 1708 1709 1710 private: 1711 @safe 1712 pure int decodeChar()(char chr) 1713 { 1714 immutable val = DecodeMap[chr]; 1715 1716 // enforce can't be a pure function, so I use trivial check. 1717 if (val == 0 && chr != 'A') 1718 throw new Base64Exception("Invalid character: " ~ chr); 1719 1720 return val; 1721 } 1722 1723 1724 @safe 1725 pure int decodeChar()(dchar chr) 1726 { 1727 // See above comment. 1728 if (chr > 0x7f) 1729 throw new Base64Exception("Base64-encoded character must be a single byte"); 1730 1731 return decodeChar(cast(char) chr); 1732 } 1733 } 1734 1735 /// 1736 @safe unittest 1737 { 1738 import std.string : representation; 1739 1740 // pre-defined: alias Base64 = Base64Impl!('+', '/'); 1741 ubyte[] emptyArr; 1742 assert(Base64.encode(emptyArr) == ""); 1743 assert(Base64.encode("f".representation) == "Zg=="); 1744 assert(Base64.encode("foo".representation) == "Zm9v"); 1745 1746 alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); 1747 assert(Base64Re.encode("f".representation) == "Zg"); 1748 assert(Base64Re.encode("foo".representation) == "Zm9v"); 1749 } 1750 1751 /** 1752 * Exception thrown upon encountering Base64 encoding or decoding errors. 1753 */ 1754 class Base64Exception : Exception 1755 { 1756 @safe pure nothrow 1757 this(string s, string fn = __FILE__, size_t ln = __LINE__) 1758 { 1759 super(s, fn, ln); 1760 } 1761 } 1762 1763 /// 1764 @safe unittest 1765 { 1766 import std.exception : assertThrown; 1767 assertThrown!Base64Exception(Base64.decode("ab|c")); 1768 } 1769 1770 @system unittest 1771 { 1772 import std.algorithm.comparison : equal; 1773 import std.algorithm.sorting : sort; 1774 import std.conv; 1775 import std.exception : assertThrown; 1776 import std.file; 1777 import std.stdio; 1778 1779 alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); 1780 1781 // Test vectors from RFC 4648 1782 ubyte[][string] tv = [ 1783 "" :cast(ubyte[])"", 1784 "f" :cast(ubyte[])"f", 1785 "fo" :cast(ubyte[])"fo", 1786 "foo" :cast(ubyte[])"foo", 1787 "foob" :cast(ubyte[])"foob", 1788 "fooba" :cast(ubyte[])"fooba", 1789 "foobar":cast(ubyte[])"foobar" 1790 ]; 1791 1792 { // Base64 1793 // encode 1794 assert(Base64.encodeLength(tv[""].length) == 0); 1795 assert(Base64.encodeLength(tv["f"].length) == 4); 1796 assert(Base64.encodeLength(tv["fo"].length) == 4); 1797 assert(Base64.encodeLength(tv["foo"].length) == 4); 1798 assert(Base64.encodeLength(tv["foob"].length) == 8); 1799 assert(Base64.encodeLength(tv["fooba"].length) == 8); 1800 assert(Base64.encodeLength(tv["foobar"].length) == 8); 1801 1802 assert(Base64.encode(tv[""]) == ""); 1803 assert(Base64.encode(tv["f"]) == "Zg=="); 1804 assert(Base64.encode(tv["fo"]) == "Zm8="); 1805 assert(Base64.encode(tv["foo"]) == "Zm9v"); 1806 assert(Base64.encode(tv["foob"]) == "Zm9vYg=="); 1807 assert(Base64.encode(tv["fooba"]) == "Zm9vYmE="); 1808 assert(Base64.encode(tv["foobar"]) == "Zm9vYmFy"); 1809 1810 // decode 1811 assert(Base64.decodeLength(Base64.encode(tv[""]).length) == 0); 1812 assert(Base64.decodeLength(Base64.encode(tv["f"]).length) == 3); 1813 assert(Base64.decodeLength(Base64.encode(tv["fo"]).length) == 3); 1814 assert(Base64.decodeLength(Base64.encode(tv["foo"]).length) == 3); 1815 assert(Base64.decodeLength(Base64.encode(tv["foob"]).length) == 6); 1816 assert(Base64.decodeLength(Base64.encode(tv["fooba"]).length) == 6); 1817 assert(Base64.decodeLength(Base64.encode(tv["foobar"]).length) == 6); 1818 1819 assert(Base64.decode(Base64.encode(tv[""])) == tv[""]); 1820 assert(Base64.decode(Base64.encode(tv["f"])) == tv["f"]); 1821 assert(Base64.decode(Base64.encode(tv["fo"])) == tv["fo"]); 1822 assert(Base64.decode(Base64.encode(tv["foo"])) == tv["foo"]); 1823 assert(Base64.decode(Base64.encode(tv["foob"])) == tv["foob"]); 1824 assert(Base64.decode(Base64.encode(tv["fooba"])) == tv["fooba"]); 1825 assert(Base64.decode(Base64.encode(tv["foobar"])) == tv["foobar"]); 1826 1827 assertThrown!Base64Exception(Base64.decode("ab|c")); 1828 1829 // Test decoding incomplete strings. RFC does not specify the correct 1830 // behavior, but the code should never throw Errors on invalid input. 1831 1832 // decodeLength is nothrow 1833 assert(Base64.decodeLength(1) == 0); 1834 assert(Base64.decodeLength(2) <= 1); 1835 assert(Base64.decodeLength(3) <= 2); 1836 1837 // may throw Exceptions, may not throw Errors 1838 assertThrown!Base64Exception(Base64.decode("Zg")); 1839 assertThrown!Base64Exception(Base64.decode("Zg=")); 1840 assertThrown!Base64Exception(Base64.decode("Zm8")); 1841 assertThrown!Base64Exception(Base64.decode("Zg==;")); 1842 } 1843 1844 { // No padding 1845 // encode 1846 assert(Base64Re.encodeLength(tv[""].length) == 0); 1847 assert(Base64Re.encodeLength(tv["f"].length) == 2); 1848 assert(Base64Re.encodeLength(tv["fo"].length) == 3); 1849 assert(Base64Re.encodeLength(tv["foo"].length) == 4); 1850 assert(Base64Re.encodeLength(tv["foob"].length) == 6); 1851 assert(Base64Re.encodeLength(tv["fooba"].length) == 7); 1852 assert(Base64Re.encodeLength(tv["foobar"].length) == 8); 1853 1854 assert(Base64Re.encode(tv[""]) == ""); 1855 assert(Base64Re.encode(tv["f"]) == "Zg"); 1856 assert(Base64Re.encode(tv["fo"]) == "Zm8"); 1857 assert(Base64Re.encode(tv["foo"]) == "Zm9v"); 1858 assert(Base64Re.encode(tv["foob"]) == "Zm9vYg"); 1859 assert(Base64Re.encode(tv["fooba"]) == "Zm9vYmE"); 1860 assert(Base64Re.encode(tv["foobar"]) == "Zm9vYmFy"); 1861 1862 // decode 1863 assert(Base64Re.decodeLength(Base64Re.encode(tv[""]).length) == 0); 1864 assert(Base64Re.decodeLength(Base64Re.encode(tv["f"]).length) == 1); 1865 assert(Base64Re.decodeLength(Base64Re.encode(tv["fo"]).length) == 2); 1866 assert(Base64Re.decodeLength(Base64Re.encode(tv["foo"]).length) == 3); 1867 assert(Base64Re.decodeLength(Base64Re.encode(tv["foob"]).length) == 4); 1868 assert(Base64Re.decodeLength(Base64Re.encode(tv["fooba"]).length) == 5); 1869 assert(Base64Re.decodeLength(Base64Re.encode(tv["foobar"]).length) == 6); 1870 1871 assert(Base64Re.decode(Base64Re.encode(tv[""])) == tv[""]); 1872 assert(Base64Re.decode(Base64Re.encode(tv["f"])) == tv["f"]); 1873 assert(Base64Re.decode(Base64Re.encode(tv["fo"])) == tv["fo"]); 1874 assert(Base64Re.decode(Base64Re.encode(tv["foo"])) == tv["foo"]); 1875 assert(Base64Re.decode(Base64Re.encode(tv["foob"])) == tv["foob"]); 1876 assert(Base64Re.decode(Base64Re.encode(tv["fooba"])) == tv["fooba"]); 1877 assert(Base64Re.decode(Base64Re.encode(tv["foobar"])) == tv["foobar"]); 1878 1879 // decodeLength is nothrow 1880 assert(Base64.decodeLength(1) == 0); 1881 } 1882 1883 { // with OutputRange 1884 import std.array; 1885 1886 auto a = Appender!(char[])([]); 1887 auto b = Appender!(ubyte[])([]); 1888 1889 assert(Base64.encode(tv[""], a) == 0); 1890 assert(Base64.decode(a.data, b) == 0); 1891 assert(tv[""] == b.data); a.clear(); b.clear(); 1892 1893 assert(Base64.encode(tv["f"], a) == 4); 1894 assert(Base64.decode(a.data, b) == 1); 1895 assert(tv["f"] == b.data); a.clear(); b.clear(); 1896 1897 assert(Base64.encode(tv["fo"], a) == 4); 1898 assert(Base64.decode(a.data, b) == 2); 1899 assert(tv["fo"] == b.data); a.clear(); b.clear(); 1900 1901 assert(Base64.encode(tv["foo"], a) == 4); 1902 assert(Base64.decode(a.data, b) == 3); 1903 assert(tv["foo"] == b.data); a.clear(); b.clear(); 1904 1905 assert(Base64.encode(tv["foob"], a) == 8); 1906 assert(Base64.decode(a.data, b) == 4); 1907 assert(tv["foob"] == b.data); a.clear(); b.clear(); 1908 1909 assert(Base64.encode(tv["fooba"], a) == 8); 1910 assert(Base64.decode(a.data, b) == 5); 1911 assert(tv["fooba"] == b.data); a.clear(); b.clear(); 1912 1913 assert(Base64.encode(tv["foobar"], a) == 8); 1914 assert(Base64.decode(a.data, b) == 6); 1915 assert(tv["foobar"] == b.data); a.clear(); b.clear(); 1916 } 1917 1918 // https://issues.dlang.org/show_bug.cgi?id=9543 1919 // These tests were disabled because they actually relied on the input range having length. 1920 // The implementation (currently) doesn't support encoding/decoding from a length-less source. 1921 version (none) 1922 { // with InputRange 1923 // InputRange to ubyte[] or char[] 1924 auto encoded = Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"])); 1925 assert(encoded == "FPucA9l+"); 1926 assert(Base64.decode(map!q{a}(encoded)) == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); 1927 1928 // InputRange to OutputRange 1929 auto a = Appender!(char[])([]); 1930 auto b = Appender!(ubyte[])([]); 1931 assert(Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]), a) == 8); 1932 assert(a.data == "FPucA9l+"); 1933 assert(Base64.decode(map!q{a}(a.data), b) == 6); 1934 assert(b.data == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); 1935 } 1936 1937 { // Encoder and Decoder 1938 { 1939 string encode_file = std.file.deleteme ~ "-testingEncoder"; 1940 std.file.write(encode_file, "\nf\nfo\nfoo\nfoob\nfooba\nfoobar"); 1941 1942 auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]; 1943 auto f = File(encode_file); 1944 scope(exit) 1945 { 1946 f.close(); 1947 assert(!f.isOpen); 1948 std.file.remove(encode_file); 1949 } 1950 1951 size_t i; 1952 foreach (encoded; Base64.encoder(f.byLine())) 1953 assert(encoded == witness[i++]); 1954 1955 assert(i == witness.length); 1956 } 1957 1958 { 1959 string decode_file = std.file.deleteme ~ "-testingDecoder"; 1960 std.file.write(decode_file, "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy"); 1961 1962 auto witness = sort(tv.keys); 1963 auto f = File(decode_file); 1964 scope(exit) 1965 { 1966 f.close(); 1967 assert(!f.isOpen); 1968 std.file.remove(decode_file); 1969 } 1970 1971 size_t i; 1972 foreach (decoded; Base64.decoder(f.byLine())) 1973 assert(decoded == witness[i++]); 1974 1975 assert(i == witness.length); 1976 } 1977 1978 { // ForwardRange 1979 { 1980 auto encoder = Base64.encoder(sort(tv.values)); 1981 auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]; 1982 size_t i; 1983 1984 assert(encoder.front == witness[i++]); encoder.popFront(); 1985 assert(encoder.front == witness[i++]); encoder.popFront(); 1986 assert(encoder.front == witness[i++]); encoder.popFront(); 1987 1988 foreach (encoded; encoder.save) 1989 assert(encoded == witness[i++]); 1990 } 1991 1992 { 1993 auto decoder = Base64.decoder(["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]); 1994 auto witness = sort(tv.values); 1995 size_t i; 1996 1997 assert(decoder.front == witness[i++]); decoder.popFront(); 1998 assert(decoder.front == witness[i++]); decoder.popFront(); 1999 assert(decoder.front == witness[i++]); decoder.popFront(); 2000 2001 foreach (decoded; decoder.save) 2002 assert(decoded == witness[i++]); 2003 } 2004 } 2005 } 2006 2007 { // Encoder and Decoder for single character encoding and decoding 2008 alias Base64NoPadding = Base64Impl!('+', '/', Base64.NoPadding); 2009 2010 auto tests = [ 2011 "" : ["", "", "", ""], 2012 "f" : ["Zg==", "Zg==", "Zg", "Zg"], 2013 "fo" : ["Zm8=", "Zm8=", "Zm8", "Zm8"], 2014 "foo" : ["Zm9v", "Zm9v", "Zm9v", "Zm9v"], 2015 "foob" : ["Zm9vYg==", "Zm9vYg==", "Zm9vYg", "Zm9vYg"], 2016 "fooba" : ["Zm9vYmE=", "Zm9vYmE=", "Zm9vYmE", "Zm9vYmE"], 2017 "foobar" : ["Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy"], 2018 ]; 2019 2020 foreach (u, e; tests) 2021 { 2022 assert(equal(Base64.encoder(cast(ubyte[]) u), e[0])); 2023 assert(equal(Base64.decoder(Base64.encoder(cast(ubyte[]) u)), u)); 2024 2025 assert(equal(Base64URL.encoder(cast(ubyte[]) u), e[1])); 2026 assert(equal(Base64URL.decoder(Base64URL.encoder(cast(ubyte[]) u)), u)); 2027 2028 assert(equal(Base64NoPadding.encoder(cast(ubyte[]) u), e[2])); 2029 assert(equal(Base64NoPadding.decoder(Base64NoPadding.encoder(cast(ubyte[]) u)), u)); 2030 2031 assert(equal(Base64Re.encoder(cast(ubyte[]) u), e[3])); 2032 assert(equal(Base64Re.decoder(Base64Re.encoder(cast(ubyte[]) u)), u)); 2033 } 2034 } 2035 } 2036 2037 // Regression control for the output range ref bug in encode. 2038 @safe unittest 2039 { 2040 struct InputRange 2041 { 2042 @(imported!"core.attribute".mutableRefInit) ubyte[] impl = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; 2043 @property bool empty() { return impl.length == 0; } 2044 @property ubyte front() { return impl[0]; } 2045 void popFront() { impl = impl[1 .. $]; } 2046 @property size_t length() { return impl.length; } 2047 } 2048 2049 struct OutputRange 2050 { 2051 char[] result; 2052 void put(char b) { result ~= b; } 2053 } 2054 2055 InputRange ir; 2056 OutputRange or; 2057 assert(Base64.encode(ir, or) == 8); 2058 assert(or.result == "Gis8TV1u"); 2059 2060 // Verify that any existing workaround that uses & still works. 2061 InputRange ir2; 2062 OutputRange or2; 2063 () @trusted { 2064 assert(Base64.encode(ir2, &or2) == 8); 2065 }(); 2066 assert(or2.result == "Gis8TV1u"); 2067 } 2068 2069 // Regression control for the output range ref bug in decode. 2070 @safe unittest 2071 { 2072 struct InputRange 2073 { 2074 const(char)[] impl = "Gis8TV1u"; 2075 @property bool empty() { return impl.length == 0; } 2076 @property dchar front() { return impl[0]; } 2077 void popFront() { impl = impl[1 .. $]; } 2078 @property size_t length() { return impl.length; } 2079 } 2080 2081 struct OutputRange 2082 { 2083 ubyte[] result; 2084 void put(ubyte b) { result ~= b; } 2085 } 2086 2087 InputRange ir; 2088 OutputRange or; 2089 assert(Base64.decode(ir, or) == 6); 2090 assert(or.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); 2091 2092 // Verify that any existing workaround that uses & still works. 2093 InputRange ir2; 2094 OutputRange or2; 2095 () @trusted { 2096 assert(Base64.decode(ir2, &or2) == 6); 2097 }(); 2098 assert(or2.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); 2099 } 2100 2101 // https://issues.dlang.org/show_bug.cgi?id=21679 2102 // https://issues.dlang.org/show_bug.cgi?id=21706 2103 @safe unittest 2104 { 2105 ubyte[][] input; 2106 assert(Base64.encoder(input).empty); 2107 assert(Base64.decoder(input).empty); 2108 } 2109 2110 @safe unittest 2111 { 2112 struct InputRange(ubyte[] data) 2113 { 2114 ubyte[] impl = data; 2115 bool empty() { return impl.length == 0; } 2116 ubyte front() { return impl[0]; } 2117 void popFront() { impl = impl[1 .. $]; } 2118 size_t length() { return impl.length; } 2119 } 2120 2121 struct OutputRange 2122 { 2123 ubyte[] result; 2124 void put(ubyte b) { result ~= b; } 2125 } 2126 2127 void test_encode(ubyte[] data, string result)() 2128 { 2129 InputRange!data ir; 2130 OutputRange or; 2131 assert(Base64.encode(ir, or) == result.length); 2132 assert(or.result == result); 2133 } 2134 2135 void test_decode(ubyte[] data, string result)() 2136 { 2137 InputRange!data ir; 2138 OutputRange or; 2139 assert(Base64.decode(ir, or) == result.length); 2140 assert(or.result == result); 2141 } 2142 2143 test_encode!([], ""); 2144 test_encode!(['x'], "eA=="); 2145 test_encode!([123, 45], "ey0="); 2146 2147 test_decode!([], ""); 2148 test_decode!(['e', 'A', '=', '='], "x"); 2149 test_decode!(['e', 'y', '0', '='], "{-"); 2150 } 2151 2152 @system unittest 2153 { 2154 // checking forward range 2155 auto item = Base64.decoder(Base64.encoder(cast(ubyte[]) "foobar")); 2156 auto copy = item.save(); 2157 item.popFront(); 2158 assert(item.front == 'o'); 2159 assert(copy.front == 'f'); 2160 } 2161 2162 @system unittest 2163 { 2164 // checking invalid dchar 2165 dchar[] c = cast(dchar[]) "ääää"; 2166 2167 import std.exception : assertThrown; 2168 assertThrown!Base64Exception(Base64.decode(c)); 2169 } 2170 2171 @safe unittest 2172 { 2173 import std.array : array; 2174 2175 char[][] input = [['e', 'y'], ['0', '=']]; 2176 assert(Base64.decoder(input).array == [[123, 45]]); 2177 } 2178 2179 // https://issues.dlang.org/show_bug.cgi?id=21707 2180 @safe unittest 2181 { 2182 import std.exception : assertThrown; 2183 2184 char[][] t1 = [[ 'Z', 'g', '=' ]]; 2185 assertThrown!Base64Exception(Base64.decoder(t1)); 2186 2187 char[][] t2 = [[ 'e', 'y', '0' ], ['=', '=']]; 2188 assertThrown!Base64Exception(Base64.decoder(t2)); 2189 }