summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/mime/jsmime/test/unit/test_mime_tree.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/mime/jsmime/test/unit/test_mime_tree.js')
-rw-r--r--comm/mailnews/mime/jsmime/test/unit/test_mime_tree.js838
1 files changed, 838 insertions, 0 deletions
diff --git a/comm/mailnews/mime/jsmime/test/unit/test_mime_tree.js b/comm/mailnews/mime/jsmime/test/unit/test_mime_tree.js
new file mode 100644
index 0000000000..c1d8647b25
--- /dev/null
+++ b/comm/mailnews/mime/jsmime/test/unit/test_mime_tree.js
@@ -0,0 +1,838 @@
+"use strict";
+define(function (require) {
+ var assert = require("assert");
+ var jsmime = require("jsmime");
+ var fs = require("fs");
+
+ function arrayTest(data, fn) {
+ fn.toString = function () {
+ let text = Function.prototype.toString.call(this);
+ text = text.replace(/data\[([0-9]*)\]/g, function (m, p) {
+ return JSON.stringify(data[p]);
+ });
+ return text;
+ };
+ return test(data[0], fn);
+ }
+
+ // Returns and deletes object[field] if present, or undefined if not.
+ function extract_field(object, field) {
+ if (field in object) {
+ var result = object[field];
+ delete object[field];
+ return result;
+ }
+ return undefined;
+ }
+
+ // A file cache for read_file.
+ var file_cache = {};
+
+ /**
+ * Read a file into a string (all line endings become CRLF).
+ *
+ * @param file The name of the file to read, relative to the data/ directory.
+ * @param start The first line of the file to return, defaulting to 0
+ * @param end The last line of the file to return, defaulting to the number of
+ * lines in the file.
+ * @returns Promise<String> The contents of the file as a binary string.
+ */
+ function read_file(file, start, end) {
+ if (!(file in file_cache)) {
+ var realFile = new Promise(function (resolve, reject) {
+ fs.readFile("data/" + file, function (err, data) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+ });
+ });
+ var loader = realFile.then(function (contents) {
+ var inStrForm = "";
+ while (contents.length > 0) {
+ inStrForm += String.fromCharCode.apply(
+ null,
+ contents.subarray(0, 1024)
+ );
+ contents = contents.subarray(1024);
+ }
+ return inStrForm.split(/\r\n|[\r\n]/);
+ });
+ file_cache[file] = loader;
+ }
+ return file_cache[file].then(function (contents) {
+ if (start !== undefined) {
+ contents = contents.slice(start - 1, end - 1);
+ }
+ return contents.join("\r\n");
+ });
+ }
+
+ /**
+ * Helper for body tests.
+ *
+ * Some extra options are listed too:
+ * _split: The contents of the file will be passed in packets split by this
+ * regex. Be sure to include the split delimiter in a group so that they
+ * are included in the output packets!
+ * _eol: The CRLFs in the input file will be replaced with the given line
+ * ending instead.
+ *
+ * @param test The name of test
+ * @param file The name of the file to read (relative to mailnews/data)
+ * @param opts Options for the mime parser, as well as a few extras detailed
+ * above.
+ * @param partspec An array of [partnum, line start, line end] detailing the
+ * expected parts in the body. It will be expected that the
+ * accumulated body part data for partnum would be the contents
+ * of the file from [line start, line end) [1-based lines]
+ */
+ function make_body_test(test, file, opts, partspec) {
+ var results = Promise.all(
+ partspec.map(p => Promise.all([p[0], read_file(file, p[1], p[2])]))
+ );
+ var eol = extract_field(opts, "_eol");
+ var msgtext = read_file(file).then(function (msgcontents) {
+ var packetize = extract_field(opts, "_split");
+ if (packetize !== undefined) {
+ msgcontents = msgcontents.split(packetize);
+ }
+ if (eol !== undefined) {
+ msgcontents = msgcontents.replace(/\r\n/g, eol);
+ }
+ return msgcontents;
+ });
+ if (eol !== undefined) {
+ results = results.then(function (results_) {
+ for (let part of results_) {
+ part[1] = part[1].replace(/\r\n/g, eol);
+ }
+ return results_;
+ });
+ }
+ return [test, msgtext, opts, results];
+ }
+
+ /**
+ * Execute a single MIME tree test.
+ *
+ * @param message Either the text of the message, an array of textual message
+ * part data (imagine coming on different TCP packets), or a
+ * promise that resolves to any of the above.
+ * @param opts A set of options for the parser and for the test.
+ * @param results The expected results of the call. This may either be a
+ * dictionary of part number -> header -> values (to check
+ * headers), or an array of [partnum, partdata] for expected
+ * results to deliverPartData, or a promise for the above.
+ * @returns A promise containing the results of the test.
+ */
+ function testParser(message, opts, results) {
+ var uncheckedValues;
+ var checkingHeaders;
+ var calls = 0;
+ var fusingParts = extract_field(opts, "_nofuseparts") === undefined;
+ var emitter = {
+ stack: [],
+ startMessage: function emitter_startMsg() {
+ assert.equal(this.stack.length, 0);
+ calls++;
+ this.partData = "";
+ },
+ endMessage: function emitter_endMsg() {
+ assert.equal(this.stack.length, 0);
+ calls++;
+ },
+ startPart: function emitter_startPart(partNum, headers) {
+ this.stack.push(partNum);
+ if (checkingHeaders) {
+ assert.ok(partNum in uncheckedValues);
+ // Headers is a map, convert it to an object.
+ var objmap = {};
+ for (let pair of headers) {
+ objmap[pair[0]] = pair[1];
+ }
+ var expected = uncheckedValues[partNum];
+ var convresults = {};
+ for (let key in expected) {
+ try {
+ convresults[key] = jsmime.headerparser.parseStructuredHeader(
+ key,
+ expected[key]
+ );
+ } catch (e) {
+ convresults[key] = expected[key];
+ }
+ }
+ assert.deepEqual(objmap, convresults);
+ if (fusingParts) {
+ assert.equal(this.partData, "");
+ }
+ delete uncheckedValues[partNum];
+ }
+ },
+ deliverPartData: function emitter_partData(partNum, data) {
+ assert.equal(this.stack[this.stack.length - 1], partNum);
+ if (!checkingHeaders) {
+ if (fusingParts) {
+ this.partData += data;
+ } else {
+ let check = uncheckedValues.shift();
+ assert.equal(partNum, check[0]);
+ assert.equal(data, check[1]);
+ }
+ }
+ },
+ endPart: function emitter_endPart(partNum) {
+ if (this.partData != "") {
+ let check = uncheckedValues.shift();
+ assert.equal(partNum, check[0]);
+ assert.equal(this.partData, check[1]);
+ this.partData = "";
+ }
+ assert.equal(this.stack.pop(), partNum);
+ },
+ };
+ opts.onerror = function (e) {
+ throw e;
+ };
+
+ return Promise.all([message, results]).then(function (vals) {
+ let [message_, results_] = vals;
+ // Clone the results array into uncheckedValues
+ if (Array.isArray(results_)) {
+ uncheckedValues = Array.from(results_);
+ checkingHeaders = false;
+ } else {
+ uncheckedValues = {};
+ for (let key in results_) {
+ uncheckedValues[key] = results_[key];
+ }
+ checkingHeaders = true;
+ }
+ if (!Array.isArray(message_)) {
+ message_ = [message_];
+ }
+ var parser = new jsmime.MimeParser(emitter, opts);
+ message_.forEach(function (packet) {
+ parser.deliverData(packet);
+ });
+ parser.deliverEOF();
+ assert.equal(calls, 2);
+ if (!checkingHeaders) {
+ assert.equal(0, uncheckedValues.length);
+ } else {
+ assert.deepEqual({}, uncheckedValues);
+ }
+ });
+ }
+
+ suite("MimeParser", function () {
+ // This is the expected part specifier for the multipart-complex1 test file,
+ // specified here because it is used in several cases.
+ let mpart_complex1 = [
+ ["1", 8, 10],
+ ["2", 14, 16],
+ ["3.1", 22, 24],
+ ["4", 29, 31],
+ ["5", 33, 35],
+ ];
+
+ suite("Simple tests", function () {
+ let parser_tests = [
+ // The following tests are either degenerate or error cases that should
+ // work
+ ["Empty string", "", {}, { "": {} }],
+ ["No value for header", "Header", {}, { "": { Header: [""] } }],
+ [
+ "No trailing newline",
+ "To: eof@example.net",
+ {},
+ { "": { To: ["eof@example.net"] } },
+ ],
+ [
+ "Header no val",
+ "To: eof@example.net\r\n",
+ {},
+ { "": { To: ["eof@example.net"] } },
+ ],
+ ["No body no headers", "\r\n\r\n", {}, { "": {} }],
+ ["Body no headers", "\r\n\r\nA", {}, { "": {} }],
+ // Basic cases for headers
+ [
+ "Multiparts get headers",
+ read_file("multipart-complex1"),
+ {},
+ {
+ "": { "Content-Type": ['multipart/mixed; boundary="boundary"'] },
+ 1: {
+ "Content-Type": ["application/octet-stream"],
+ "Content-Transfer-Encoding": ["base64"],
+ },
+ 2: {
+ "Content-Type": ["image/png"],
+ "Content-Transfer-Encoding": ["base64"],
+ },
+ 3: {
+ "Content-Type": ['multipart/related; boundary="boundary2"'],
+ },
+ 3.1: { "Content-Type": ["text/html"] },
+ 4: { "Content-Type": ["text/plain"] },
+ 5: {},
+ },
+ ],
+ ];
+ parser_tests.forEach(function (data) {
+ arrayTest(data, function () {
+ return testParser(data[1], data[2], data[3]);
+ });
+ });
+ });
+
+ suite("Body tests", function () {
+ let parser_tests = [
+ // Body tests from data
+ // (Note: line numbers are 1-based. Also, to capture trailing EOF, add 2
+ // to the last line number of the file).
+ make_body_test("Basic body", "basic1", {}, [["", 3, 5]]),
+ make_body_test("Basic multipart", "multipart1", {}, [["1", 10, 12]]),
+ make_body_test("Basic multipart", "multipart2", {}, [["1", 8, 11]]),
+ make_body_test(
+ "Complex multipart",
+ "multipart-complex1",
+ {},
+ mpart_complex1
+ ),
+ make_body_test("Truncated multipart", "multipart-complex2", {}, [
+ ["1.1.1.1", 21, 25],
+ ["2", 27, 57],
+ ["3", 60, 62],
+ ]),
+ make_body_test("No LF multipart", "multipartmalt-detach", {}, [
+ ["1", 20, 21],
+ ["2.1", 27, 38],
+ ["2.2", 42, 43],
+ ["2.3", 47, 48],
+ ["3", 53, 54],
+ ]),
+ make_body_test("Raw body", "multipart1", { bodyformat: "raw" }, [
+ ["", 4, 14],
+ ]),
+ [
+ "Base64 decode 1",
+ read_file("base64-1"),
+ { bodyformat: "decode" },
+ [
+ [
+ "",
+ "\r\nHello, world! (Again...)\r\n\r\nLet's see how well base64 " +
+ "text is handled. Yay, lots of space" +
+ "s! There's even a CRLF at the end and one at the beginning, bu" +
+ "t the output shouldn't have it.\r\n",
+ ],
+ ],
+ ],
+ [
+ "Base64 decode 2",
+ read_file("base64-2"),
+ { bodyformat: "decode" },
+ [
+ [
+ "",
+ "<html><body>This is base64 encoded HTML text, and the tags sho" +
+ "uldn't be stripped.\r\n<b>Bold text is bold!</b></body></html>" +
+ "\r\n",
+ ],
+ ],
+ ],
+ [
+ "Base64 decode line issues",
+ read_file("base64-2").then(function (s) {
+ return s.split(/(\r\n)/);
+ }),
+ { bodyformat: "decode" },
+ [
+ [
+ "",
+ "<html><body>This is base64 encoded HTML text, and the tags sho" +
+ "uldn't be stripped.\r\n<b>Bold text is bold!</b></body></html>" +
+ "\r\n",
+ ],
+ ],
+ ],
+ make_body_test("Base64 nodecode", "base64-1", {}, [["", 4, 9]]),
+ [
+ "QP decode",
+ read_file("bug505221"),
+ { pruneat: "1", bodyformat: "decode" },
+ [
+ [
+ "1",
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"' +
+ '>\r\n<HTML><HEAD>\r\n<META HTTP-EQUIV="Content-Type" CONTENT=' +
+ '"text/html; charset=us-ascii">\r\n\r\n\r\n<META content="MSHT' +
+ 'ML 6.00.6000.16735" name=GENERATOR></HEAD>\r\n<BODY> bbb\r\n<' +
+ "/BODY></HTML>",
+ ],
+ ],
+ ],
+ [
+ "Nested messages",
+ read_file("message-encoded"),
+ { bodyformat: "decode" },
+ [
+ ["1$", "This is a plain-text message."],
+ ["2$", "I am a plain-text message."],
+ ["3$", "I am an encoded plain-text message."],
+ ],
+ ],
+ [
+ "Nested message headers",
+ read_file("message-encoded"),
+ {},
+ {
+ "": {
+ "Content-Type": ['multipart/mixed; boundary="iamaboundary"'],
+ },
+ 1: { "Content-Type": ["message/rfc822"] },
+ "1$": { Subject: ["I am a subject"] },
+ 2: {
+ "Content-Type": ["message/global"],
+ "Content-Transfer-Encoding": ["base64"],
+ },
+ "2$": { Subject: ["\u79c1\u306f\u3001\u4ef6\u540d\u5348\u524d"] },
+ 3: {
+ "Content-Type": ["message/news"],
+ "Content-Transfer-Encoding": ["quoted-printable"],
+ },
+ "3$": { Subject: ["\u79c1\u306f\u3001\u4ef6\u540d\u5348\u524d"] },
+ },
+ ],
+ ];
+ parser_tests.forEach(function (data) {
+ arrayTest(data, function () {
+ return testParser(data[1], data[2], data[3]);
+ });
+ });
+ });
+
+ suite("Torture tests", function () {
+ // Generate a very long message for tests
+ let teststr = "a";
+ for (let i = 0; i < 16; i++) {
+ teststr += teststr;
+ }
+ let parser_tests = [
+ [
+ "Base64 very long decode",
+ "Content-Transfer-Encoding: base64\r\n\r\n" + btoa(teststr) + "\r\n",
+ { bodyformat: "decode" },
+ [["", teststr]],
+ ],
+ make_body_test("Torture regular body", "mime-torture", {}, [
+ ["1", 17, 21],
+ ["2$.1", 58, 75],
+ ["2$.2.1", 83, 97],
+ ["2$.3", 102, 130],
+ ["3$", 155, 7742],
+ ["4", 7747, 8213],
+ ["5", 8218, 8242],
+ ["6$.1.1", 8284, 8301],
+ ["6$.1.2", 8306, 8733],
+ ["6$.2.1", 8742, 9095],
+ ["6$.2.2", 9100, 9354],
+ ["6$.2.3", 9357, 11794],
+ ["6$.2.4", 11797, 12155],
+ ["6$.3", 12161, 12809],
+ ["7$.1", 12844, 12845],
+ ["7$.2", 12852, 13286],
+ ["7$.3", 13288, 13297],
+ ["8$.1", 13331, 13358],
+ ["8$.2", 13364, 13734],
+ ["9$", 13757, 20179],
+ ["10", 20184, 21200],
+ ["11$.1", 21223, 22031],
+ ["11$.2", 22036, 22586],
+ ["12$.1", 22607, 23469],
+ ["12$.2", 23474, 23774],
+ ["12$.3$.1", 23787, 23795],
+ ["12$.3$.2.1", 23803, 23820],
+ ["12$.3$.2.2", 23825, 24633],
+ ["12$.3$.3", 24640, 24836],
+ ["12$.3$.4$", 24848, 25872],
+ ]),
+ make_body_test("Torture pruneat", "mime-torture", { pruneat: "4" }, [
+ ["4", 7747, 8213],
+ ]),
+
+ // Test packetization problems
+ make_body_test(
+ "Large packets",
+ "multipart-complex1",
+ { _split: /(.{30})/ },
+ mpart_complex1
+ ),
+ make_body_test(
+ "Split on newline",
+ "multipart-complex1",
+ { _split: /(\r\n)/ },
+ mpart_complex1
+ ),
+ make_body_test(
+ "Pathological splitting",
+ "multipart-complex1",
+ { _split: "" },
+ mpart_complex1
+ ),
+
+ // Non-CLRF line endings?
+ make_body_test(
+ "LF-based messages",
+ "multipart-complex1",
+ { _eol: "\n" },
+ mpart_complex1
+ ),
+ make_body_test(
+ "CR-based messages",
+ "multipart-complex1",
+ { _eol: "\r" },
+ mpart_complex1
+ ),
+ ];
+ parser_tests.forEach(function (data) {
+ arrayTest(data, function () {
+ return testParser(data[1], data[2], data[3]);
+ });
+ });
+ });
+
+ suite("Header tests", function () {
+ let parser_tests = [
+ // Basic cases for headers
+ [
+ "Multiparts get headers",
+ read_file("multipart-complex1"),
+ {},
+ {
+ "": { "Content-Type": ['multipart/mixed; boundary="boundary"'] },
+ 1: {
+ "Content-Type": ["application/octet-stream"],
+ "Content-Transfer-Encoding": ["base64"],
+ },
+ 2: {
+ "Content-Type": ["image/png"],
+ "Content-Transfer-Encoding": ["base64"],
+ },
+ 3: {
+ "Content-Type": ['multipart/related; boundary="boundary2"'],
+ },
+ 3.1: { "Content-Type": ["text/html"] },
+ 4: { "Content-Type": ["text/plain"] },
+ 5: {},
+ },
+ ],
+ // 'From ' is not an [iterable] header
+ [
+ "Exclude mbox delimiter",
+ read_file("bugmail11"),
+ {},
+ {
+ "": {
+ "X-Mozilla-Status": ["0001"],
+ "X-Mozilla-Status2": ["00000000"],
+ "X-Mozilla-Keys": [""],
+ "Return-Path": [
+ "<example@example.com>",
+ "<bugzilla-daemon@mozilla.org>",
+ ],
+ "Delivered-To": ["bugmail@example.org"],
+ Received: [
+ "by 10.114.166.12 with SMTP id o12cs163262wae;" +
+ " Fri, 11 Apr 2008 07:17:31 -0700 (PDT)",
+ "by 10.115.60.1 with SMTP id n1mr214763wak.181.1207923450166;" +
+ " Fri, 11 Apr 2008 07:17:30 -0700 (PDT)",
+ "from webapp-out.mozilla.org (webapp01.sj.mozilla.com [63.245.208.1" +
+ "46]) by mx.google.com with ESMTP id n38si6807242wag.2.2008." +
+ "04.11.07.17.29; Fri, 11 Apr 2008 07:17:30 -0700 (PDT)",
+ "from mrapp51.mozilla.org (mrapp51.mozilla.org [127.0.0.1])" +
+ "\tby webapp-out.mozilla.org (8.13.8/8.13.8) with ESMTP id m3BEHTGU" +
+ "030132\tfor <bugmail@example.org>; Fri, 11 Apr 2008 07:17:29 -0700",
+ "(from root@localhost)" +
+ "\tby mrapp51.mozilla.org (8.13.8/8.13.8/Submit) id m3BEHTk4030129;" +
+ "\tFri, 11 Apr 2008 07:17:29 -0700",
+ ],
+ "Received-Spf": [
+ "neutral (google.com: 63.245.208.146 is neither perm" +
+ "itted nor denied by best guess record for domain of bugzilla-daemo" +
+ "n@mozilla.org) client-ip=63.245.208.146;",
+ ],
+ "Authentication-Results": [
+ "mx.google.com; spf=neutral (google.com: 6" +
+ "3.245.208.146 is neither permitted nor denied by best guess record" +
+ " for domain of bugzilla-daemon@mozilla.org) smtp.mail=bugzilla-dae" +
+ "mon@mozilla.org",
+ ],
+ Date: ["Fri, 11 Apr 2008 07:17:29 -0700"],
+ "Message-ID": [
+ "<200804111417.m3BEHTk4030129@mrapp51.mozilla.org>",
+ ],
+ From: ["bugzilla-daemon@mozilla.org"],
+ To: ["bugmail@example.org"],
+ Subject: ["Bugzilla: confirm account creation"],
+ "X-Bugzilla-Type": ["admin"],
+ "Content-Type": ['text/plain; charset="UTF-8"'],
+ "MIME-Version": ["1.0"],
+ },
+ },
+ ],
+ ];
+ parser_tests.forEach(function (data) {
+ arrayTest(data, function () {
+ return testParser(data[1], data[2], data[3]);
+ });
+ });
+ });
+
+ suite("Charset tests", function () {
+ function buildTree(file, options) {
+ var tree = new Map();
+ var emitter = {
+ startPart(part, headers) {
+ tree.set(part, { headers, body: null });
+ },
+ deliverPartData(part, data) {
+ var obj = tree.get(part);
+ if (obj.body === null) {
+ obj.body = data;
+ } else if (typeof obj.body === "string") {
+ obj.body += data;
+ } else {
+ var newData = new Uint8Array(obj.body.length + data.length);
+ newData.set(obj.body);
+ newData.subarray(obj.body.length).set(data);
+ obj.body = newData;
+ }
+ },
+ };
+ return file.then(function (data) {
+ var parser = new jsmime.MimeParser(emitter, options);
+ parser.deliverData(data);
+ parser.deliverEOF();
+ return tree;
+ });
+ }
+ test("Unicode decoding", function () {
+ return buildTree(read_file("shift-jis-image"), {
+ strformat: "unicode",
+ bodyformat: "decode",
+ }).then(function (tree) {
+ // text/plain should be transcoded...
+ assert.equal(
+ tree.get("1").headers.get("Content-Type").get("charset"),
+ "Shift-JIS"
+ );
+ assert.equal(tree.get("1").headers.charset, "Shift-JIS");
+ assert.equal(
+ tree.get("1").headers.get("Content-Description"),
+ "\u30b1\u30c4\u30a1\u30eb\u30b3\u30a2\u30c8\u30eb"
+ );
+ assert.equal(
+ tree.get("1").body,
+ "Portable Network Graphics\uff08" +
+ "\u30dd\u30fc\u30bf\u30d6\u30eb\u30fb\u30cd\u30c3\u30c8\u30ef\u30fc" +
+ "\u30af\u30fb\u30b0\u30e9\u30d5\u30a3\u30c3\u30af\u30b9\u3001PNG" +
+ "\uff09\u306f\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u3067\u30d3\u30c3" +
+ "\u30c8\u30de\u30c3\u30d7\u753b\u50cf\u3092\u6271\u3046\u30d5\u30a1" +
+ "\u30a4\u30eb\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u3042\u308b" +
+ "\u3002\u5727\u7e2e\u30a2\u30eb\u30b4\u30ea\u30ba\u30e0\u3068\u3057" +
+ "\u3066Deflate\u3092\u63a1\u7528\u3057\u3066\u3044\u308b\u3001" +
+ "\u5727\u7e2e\u306b\u3088\u308b\u753b\u8cea\u306e\u52a3\u5316\u306e" +
+ "\u306a\u3044\u53ef\u9006\u5727\u7e2e\u306e\u753b\u50cf\u30d5\u30a1" +
+ "\u30a4\u30eb\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u3042\u308b" +
+ "\u3002\r\n"
+ );
+ // ... but not image/png
+ assert.ok(!tree.get("2").headers.get("Content-Type").has("charset"));
+ assert.equal(tree.get("2").headers.charset, "");
+ assert.equal(
+ tree.get("2").headers.get("Content-Description"),
+ "\ufffdP\ufffdc\ufffd@\ufffd\ufffd\ufffdR\ufffdA\ufffdg\ufffd\ufffd"
+ );
+ assert.equal(
+ tree.get("2").headers.getRawHeader("Content-Description"),
+ "\x83\x50\x83\x63\x83\x40\x83\x8b\x83\x52\x83\x41\x83\x67\x83\x8b"
+ );
+ var imageData =
+ "iVBORw0KGgoAAAANSUhEUgAAAIAAAABECAIAAADGJao+AAAAwklE" +
+ "QVR4Xu3UgQbDMBRA0bc03f//b7N0VuqJEmwoc+KqNEkDh9b+2HuJu1KNO4f+AQCAAA" +
+ "AQAAACAEAAAAgAAAEAIAAABACAAAAQAAACAEAAAAgAAAEAIAAAANReamRLlPWYfNH0" +
+ "klxcPs+cP3NxWF+vi3lb7pa2R+vx6tHOtuN1O+a5lY3HzgM5ya/GM5N7ZjfPq7/5yS" +
+ "8IgAAAEAAAAgBAAAAIAAABACAAAAQAgAAAEAAAAgBAAAAIAAABACAAAIw322gDIPvt" +
+ "lmUAAAAASUVORK5CYII=";
+ imageData = atob(imageData);
+ var asArray = new Uint8Array(imageData.length);
+ for (var i = 0; i < asArray.length; i++) {
+ asArray[i] = imageData.charCodeAt(i);
+ }
+ assert.deepEqual(tree.get("2").body, asArray);
+
+ // Touching the header charset should change the interpretation.
+ tree.get("1").headers.charset = "Shift-JIS";
+ assert.equal(tree.get("1").headers.charset, "Shift-JIS");
+ assert.equal(
+ tree.get("1").headers.get("Content-Description"),
+ "\u30b1\u30c4\u30a1\u30eb\u30b3\u30a2\u30c8\u30eb"
+ );
+ });
+ });
+ test("Fallback charset decoding", function () {
+ return buildTree(read_file("shift-jis-image"), {
+ strformat: "unicode",
+ charset: "ISO-8859-1",
+ bodyformat: "decode",
+ }).then(function (tree) {
+ // text/plain should be transcoded...
+ assert.equal(
+ tree.get("1").headers.get("Content-Type").get("charset"),
+ "Shift-JIS"
+ );
+ assert.equal(tree.get("1").headers.charset, "Shift-JIS");
+ assert.equal(
+ tree.get("1").headers.get("Content-Description"),
+ "\u30b1\u30c4\u30a1\u30eb\u30b3\u30a2\u30c8\u30eb"
+ );
+ assert.equal(
+ tree.get("1").body,
+ "Portable Network Graphics\uff08" +
+ "\u30dd\u30fc\u30bf\u30d6\u30eb\u30fb\u30cd\u30c3\u30c8\u30ef\u30fc" +
+ "\u30af\u30fb\u30b0\u30e9\u30d5\u30a3\u30c3\u30af\u30b9\u3001PNG" +
+ "\uff09\u306f\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u3067\u30d3\u30c3" +
+ "\u30c8\u30de\u30c3\u30d7\u753b\u50cf\u3092\u6271\u3046\u30d5\u30a1" +
+ "\u30a4\u30eb\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u3042\u308b" +
+ "\u3002\u5727\u7e2e\u30a2\u30eb\u30b4\u30ea\u30ba\u30e0\u3068\u3057" +
+ "\u3066Deflate\u3092\u63a1\u7528\u3057\u3066\u3044\u308b\u3001" +
+ "\u5727\u7e2e\u306b\u3088\u308b\u753b\u8cea\u306e\u52a3\u5316\u306e" +
+ "\u306a\u3044\u53ef\u9006\u5727\u7e2e\u306e\u753b\u50cf\u30d5\u30a1" +
+ "\u30a4\u30eb\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u3042\u308b" +
+ "\u3002\r\n"
+ );
+ // ... but not image/png
+ assert.ok(!tree.get("2").headers.get("Content-Type").has("charset"));
+ assert.equal(tree.get("2").headers.charset, "ISO-8859-1");
+ assert.equal(
+ tree.get("2").headers.get("Content-Description"),
+ "\u0192P\u0192c\u0192@\u0192\u2039\u0192R\u0192A\u0192g\u0192\u2039"
+ );
+ assert.equal(
+ tree.get("2").headers.getRawHeader("Content-Description"),
+ "\x83\x50\x83\x63\x83\x40\x83\x8b\x83\x52\x83\x41\x83\x67\x83\x8b"
+ );
+ var imageData =
+ "iVBORw0KGgoAAAANSUhEUgAAAIAAAABECAIAAADGJao+AAAAwklE" +
+ "QVR4Xu3UgQbDMBRA0bc03f//b7N0VuqJEmwoc+KqNEkDh9b+2HuJu1KNO4f+AQCAAA" +
+ "AQAAACAEAAAAgAAAEAIAAABACAAAAQAAACAEAAAAgAAAEAIAAAANReamRLlPWYfNH0" +
+ "klxcPs+cP3NxWF+vi3lb7pa2R+vx6tHOtuN1O+a5lY3HzgM5ya/GM5N7ZjfPq7/5yS" +
+ "8IgAAAEAAAAgBAAAAIAAABACAAAAQAgAAAEAAAAgBAAAAIAAABACAAAIw322gDIPvt" +
+ "lmUAAAAASUVORK5CYII=";
+ imageData = atob(imageData);
+ var asArray = new Uint8Array(imageData.length);
+ for (var i = 0; i < asArray.length; i++) {
+ asArray[i] = imageData.charCodeAt(i);
+ }
+ assert.deepEqual(tree.get("2").body, asArray);
+
+ // Touching the header charset should change the interpretation.
+ tree.get("1").headers.charset = "Shift-JIS";
+ assert.equal(tree.get("1").headers.charset, "Shift-JIS");
+ assert.equal(
+ tree.get("1").headers.get("Content-Description"),
+ "\u30b1\u30c4\u30a1\u30eb\u30b3\u30a2\u30c8\u30eb"
+ );
+ });
+ });
+ test("Forced charset decoding", function () {
+ return buildTree(read_file("shift-jis-image"), {
+ strformat: "unicode",
+ charset: "ISO-8859-1",
+ "force-charset": true,
+ bodyformat: "decode",
+ }).then(function (tree) {
+ // text/plain should be transcoded...
+ assert.equal(
+ tree.get("1").headers.get("Content-Type").get("charset"),
+ "Shift-JIS"
+ );
+ assert.equal(tree.get("1").headers.charset, "ISO-8859-1");
+ assert.equal(
+ tree.get("1").headers.get("Content-Description"),
+ "\u0192P\u0192c\u0192@\u0192\u2039\u0192R\u0192A\u0192g\u0192\u2039"
+ );
+ assert.equal(
+ tree.get("1").body,
+ "Portable Network Graphics\u0081i" +
+ "\u0192|\u0081[\u0192^\u0192u\u0192\u2039\u0081E\u0192l\u0192b" +
+ "\u0192g\u0192\u008f\u0081[\u0192N\u0081E\u0192O\u0192\u2030\u0192t" +
+ "\u0192B\u0192b\u0192N\u0192X\u0081APNG\u0081j\u201a\u00cd\u0192R" +
+ "\u0192\u201c\u0192s\u0192\u2026\u0081[\u0192^\u201a\u00c5\u0192r" +
+ "\u0192b\u0192g\u0192}\u0192b\u0192v\u2030\u00e6\u2018\u0153\u201a" +
+ "\u00f0\u02c6\u00b5\u201a\u00a4\u0192t\u0192@\u0192C\u0192\u2039" +
+ "\u0192t\u0192H\u0081[\u0192}\u0192b\u0192g\u201a\u00c5\u201a\u00a0" +
+ "\u201a\u00e9\u0081B\u02c6\u00b3\u008fk\u0192A\u0192\u2039\u0192S" +
+ "\u0192\u0160\u0192Y\u0192\u20ac\u201a\u00c6\u201a\u00b5\u201a" +
+ "\u00c4Deflate\u201a\u00f0\u008d\u00cc\u2014p\u201a\u00b5\u201a" +
+ "\u00c4\u201a\u00a2\u201a\u00e9\u0081A\u02c6\u00b3\u008fk\u201a" +
+ "\u00c9\u201a\u00e6\u201a\u00e9\u2030\u00e6\u017d\u00bf\u201a\u00cc" +
+ "\u2014\u00f2\u2030\u00bb\u201a\u00cc\u201a\u00c8\u201a\u00a2\u2030" +
+ "\u00c2\u2039t\u02c6\u00b3\u008fk\u201a\u00cc\u2030\u00e6\u2018" +
+ "\u0153\u0192t\u0192@\u0192C\u0192\u2039\u0192t\u0192H\u0081[\u0192" +
+ "}\u0192b\u0192g\u201a\u00c5\u201a\u00a0\u201a\u00e9\u0081B\r\n"
+ );
+ // ... but not image/png
+ assert.ok(!tree.get("2").headers.get("Content-Type").has("charset"));
+ assert.equal(tree.get("2").headers.charset, "ISO-8859-1");
+ assert.equal(
+ tree.get("2").headers.get("Content-Description"),
+ "\u0192P\u0192c\u0192@\u0192\u2039\u0192R\u0192A\u0192g\u0192\u2039"
+ );
+ assert.equal(
+ tree.get("2").headers.getRawHeader("Content-Description"),
+ "\x83\x50\x83\x63\x83\x40\x83\x8b\x83\x52\x83\x41\x83\x67\x83\x8b"
+ );
+ var imageData =
+ "iVBORw0KGgoAAAANSUhEUgAAAIAAAABECAIAAADGJao+AAAAwklE" +
+ "QVR4Xu3UgQbDMBRA0bc03f//b7N0VuqJEmwoc+KqNEkDh9b+2HuJu1KNO4f+AQCAAA" +
+ "AQAAACAEAAAAgAAAEAIAAABACAAAAQAAACAEAAAAgAAAEAIAAAANReamRLlPWYfNH0" +
+ "klxcPs+cP3NxWF+vi3lb7pa2R+vx6tHOtuN1O+a5lY3HzgM5ya/GM5N7ZjfPq7/5yS" +
+ "8IgAAAEAAAAgBAAAAIAAABACAAAAQAgAAAEAAAAgBAAAAIAAABACAAAIw322gDIPvt" +
+ "lmUAAAAASUVORK5CYII=";
+ imageData = atob(imageData);
+ var asArray = new Uint8Array(imageData.length);
+ for (var i = 0; i < asArray.length; i++) {
+ asArray[i] = imageData.charCodeAt(i);
+ }
+ assert.deepEqual(tree.get("2").body, asArray);
+
+ // Touching the header charset should change the interpretation.
+ tree.get("1").headers.charset = "Shift-JIS";
+ assert.equal(tree.get("1").headers.charset, "Shift-JIS");
+ assert.equal(
+ tree.get("1").headers.get("Content-Description"),
+ "\u30b1\u30c4\u30a1\u30eb\u30b3\u30a2\u30c8\u30eb"
+ );
+ });
+ });
+ test("Charset conversion", function () {
+ return buildTree(read_file("charsets"), {
+ strformat: "unicode",
+ bodyformat: "decode",
+ }).then(function (tree) {
+ var numParts = 12;
+ for (let i = 1; i < numParts; i += 2) {
+ assert.equal(tree.get("" + i).body, tree.get("" + (i + 1)).body);
+ }
+ assert.ok(!tree.has("" + (numParts + 1)));
+ });
+ });
+ });
+ });
+});