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