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