summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/mime/test/unit/test_parser.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/mime/test/unit/test_parser.js')
-rw-r--r--comm/mailnews/mime/test/unit/test_parser.js322
1 files changed, 322 insertions, 0 deletions
diff --git a/comm/mailnews/mime/test/unit/test_parser.js b/comm/mailnews/mime/test/unit/test_parser.js
new file mode 100644
index 0000000000..979e50975c
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_parser.js
@@ -0,0 +1,322 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is used to test the mime parser implemented in JS, mostly by means
+// of creating custom emitters and verifying that the methods on that emitter
+// are called in the correct order. This also tests that the various
+// HeaderParser methods are run correctly.
+
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+// Utility method to compare objects
+function compare_objects(real, expected) {
+ // real is a Map; convert it into an object for uneval purposes
+ if (typeof real == "object") {
+ var newreal = {};
+ for (let [k, v] of real) {
+ newreal[k] = v;
+ }
+ real = newreal;
+ }
+ var a = uneval(real),
+ b = uneval(expected);
+ // Very long strings don't get printed out fully (unless they're wrong)
+ if ((a.length > 100 || b.length > 100) && a == b) {
+ Assert.ok(a == b);
+ } else {
+ Assert.equal(a, b);
+ }
+}
+
+// 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).
+ */
+async function read_file(file, start, end) {
+ if (!(file in file_cache)) {
+ var realFile = do_get_file("../../../data/" + file);
+ file_cache[file] = (await IOUtils.readUTF8(realFile.path)).split(
+ /\r\n|[\r\n]/
+ );
+ }
+ var contents = file_cache[file];
+ 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]
+ */
+async function make_body_test(test, file, opts, partspec) {
+ let results = [];
+ for (let p of partspec) {
+ results.push([p[0], await read_file(file, p[1], p[2])]);
+ }
+
+ let msgcontents = await read_file(file);
+ return [test, msgcontents, opts, results];
+}
+
+async function make_bodydecode_test(test, file, opts, expected) {
+ let msgcontents = await read_file(file);
+ return [test, msgcontents, opts, expected];
+}
+
+// This is the expected part specifier for the multipart-complex1 test file,
+// specified here because it is used in several cases.
+var mpart_complex1 = [
+ ["1", 8, 10],
+ ["2", 14, 16],
+ ["3.1", 22, 24],
+ ["4", 29, 31],
+ ["5", 33, 35],
+];
+
+// Format of tests:
+// entry[0] = name of the test
+// entry[1] = message (a string or an array of packets)
+// entry[2] = options for the MIME parser
+// entry[3] = A checker result:
+// either a {partnum: header object} (to check headers)
+// or a [[partnum body], [partnum body], ...] (to check bodies)
+// (the partnums refer to the expected part numbers of the MIME test)
+// For body tests, unless you're testing decoding, use make_body_test.
+// For decoding tests, use make_bodydecode_test
+var 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],
+ ]),
+ make_body_test("Raw body", "multipart1", { bodyformat: "raw" }, [
+ ["", 4, 14],
+ ]),
+ make_bodydecode_test(
+ "Base64 decode 1",
+ "base64-1",
+ { bodyformat: "decode" },
+ [
+ [
+ "",
+ "\r\nHello, world! (Again...)\r\n\r\nLet's see how well base64 text" +
+ " is handled. Yay, lots of spaces! There" +
+ "'s even a CRLF at the end and one at the beginning, but the output" +
+ " shouldn't have it.\r\n",
+ ],
+ ]
+ ),
+ make_bodydecode_test(
+ "Base64 decode 2",
+ "base64-2",
+ { bodyformat: "decode" },
+ [
+ [
+ "",
+ "<html><body>This is base64 encoded HTML text, and the tags shouldn" +
+ "'t be stripped.\r\n<b>Bold text is bold!</b></body></html>\r\n",
+ ],
+ ]
+ ),
+ make_body_test("Base64 nodecode", "base64-1", {}, [["", 4, 9]]),
+ make_bodydecode_test(
+ "QP decode",
+ "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/h' +
+ 'tml; charset=us-ascii">\r\n\r\n\r\n<META content="MSHTML 6.00.600' +
+ '0.16735" name=GENERATOR></HEAD>\r\n<BODY> bbb\r\n</BODY></HTML>',
+ ],
+ ]
+ ),
+
+ // Comprehensive tests from the torture test
+ 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],
+ ]),
+];
+
+function test_parser(message, opts, results) {
+ var checkingHeaders = !(results instanceof Array);
+ var calls = 0,
+ dataCalls = 0;
+ var fusingParts = extract_field(opts, "_nofuseparts") === undefined;
+ var emitter = {
+ stack: [],
+ startMessage: function emitter_startMsg() {
+ Assert.equal(this.stack.length, 0, "no stack at start");
+ calls++;
+ this.partData = "";
+ },
+ endMessage: function emitter_endMsg() {
+ Assert.equal(this.stack.length, 0, "no stack at end");
+ calls++;
+ },
+ startPart: function emitter_startPart(partNum, headers) {
+ this.stack.push(partNum);
+ if (checkingHeaders) {
+ Assert.ok(partNum in results);
+ compare_objects(headers, results[partNum]);
+ if (fusingParts) {
+ Assert.equal(this.partData, "");
+ }
+ }
+ },
+ deliverPartData: function emitter_partData(partNum, data) {
+ Assert.equal(this.stack[this.stack.length - 1], partNum);
+ try {
+ if (!checkingHeaders) {
+ if (fusingParts) {
+ this.partData += data;
+ } else {
+ Assert.equal(partNum, results[dataCalls][0]);
+ compare_objects(data, results[dataCalls][1]);
+ }
+ }
+ } finally {
+ if (!fusingParts) {
+ dataCalls++;
+ }
+ }
+ },
+ endPart: function emitter_endPart(partNum) {
+ if (this.partData != "") {
+ Assert.equal(partNum, results[dataCalls][0]);
+ compare_objects(this.partData, results[dataCalls][1]);
+ dataCalls++;
+ this.partData = "";
+ }
+ Assert.equal(this.stack.pop(), partNum);
+ },
+ };
+ opts.onerror = function (e) {
+ throw e;
+ };
+ MimeParser.parseSync(message, emitter, opts);
+ Assert.equal(calls, 2);
+ if (!checkingHeaders) {
+ Assert.equal(dataCalls, results.length);
+ }
+}
+
+// Format of tests:
+// entry[0] = header
+// entry[1] = flags
+// entry[2] = result to match
+var header_tests = [
+ // Parameter passing
+ ["multipart/related", MimeParser.HEADER_PARAMETER, ["multipart/related", {}]],
+ ["a ; b=v", MimeParser.HEADER_PARAMETER, ["a", { b: "v" }]],
+ ["a ; b='v'", MimeParser.HEADER_PARAMETER, ["a", { b: "'v'" }]],
+ ['a; b = "v"', MimeParser.HEADER_PARAMETER, ["a", { b: "v" }]],
+ ["a;b=1;b=2", MimeParser.HEADER_PARAMETER, ["a", { b: "1" }]],
+ ["a;b=2;b=1", MimeParser.HEADER_PARAMETER, ["a", { b: "2" }]],
+ ['a;b="a;b"', MimeParser.HEADER_PARAMETER, ["a", { b: "a;b" }]],
+ ['a;b="\\\\"', MimeParser.HEADER_PARAMETER, ["a", { b: "\\" }]],
+ ['a;b="a\\b\\c"', MimeParser.HEADER_PARAMETER, ["a", { b: "abc" }]],
+ ["a;b=1;c=2", MimeParser.HEADER_PARAMETER, ["a", { b: "1", c: "2" }]],
+ ['a;b="a\\', MimeParser.HEADER_PARAMETER, ["a", { b: "a" }]],
+ ["a;b", MimeParser.HEADER_PARAMETER, ["a", {}]],
+ ['a;b=";";c=d', MimeParser.HEADER_PARAMETER, ["a", { b: ";", c: "d" }]],
+];
+
+function test_header(headerValue, flags, expected) {
+ let result = MimeParser.parseHeaderField(headerValue, flags);
+ Assert.equal(result.preSemi, expected[0]);
+ compare_objects(result, expected[1]);
+}
+
+add_task(async function testit() {
+ for (let test of parser_tests) {
+ test = await test;
+ dump("Testing message " + test[0]);
+ if (test[1] instanceof Array) {
+ dump(" using " + test[1].length + " packets");
+ }
+ dump("\n");
+ test_parser(test[1], test[2], test[3]);
+ }
+ for (let test of header_tests) {
+ dump("Testing value ->" + test[0] + "<- with flags " + test[1] + "\n");
+ test_header(test[0], test[1], test[2]);
+ }
+});