PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/std/base64.d

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