"use strict"; define(function (require) { var headerparser = require("jsmime").headerparser; var assert = require("assert"); function smartDeepEqual(actual, expected) { assert.deepEqual(actual, expected); // XXX: instanceof Map don't work for actual. Unclear why. if ("entries" in actual && "entries" in expected) { assert.deepEqual( Array.from(actual.entries()), Array.from(expected.entries()) ); } } 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); } suite("headerparser", function () { suite("parseParameterHeader", function () { let header_tests = [ ["multipart/related", ["multipart/related", {}]], ["a ; b=v", ["a", { b: "v" }]], ["a ; b='v'", ["a", { b: "'v'" }]], ['a; b = "v"', ["a", { b: "v" }]], ["a;b=1;b=2", ["a", { b: "1" }]], ["a;b=2;b=1", ["a", { b: "2" }]], ['a;b="a;b"', ["a", { b: "a;b" }]], ['a;b="\\\\"', ["a", { b: "\\" }]], ['a;b="a\\b\\c"', ["a", { b: "abc" }]], ["a;b=1;c=2", ["a", { b: "1", c: "2" }]], ['a;b="a\\', ["a", { b: "a" }]], ["a;b", ["a", {}]], ['a;b=";";c=d', ["a", { b: ";", c: "d" }]], ]; header_tests.forEach(function (data) { arrayTest(data, function () { let testMap = new Map(); for (let key in data[1][1]) { testMap.set(key, data[1][1][key]); } testMap.preSemi = data[1][0]; assert.deepEqual( headerparser.parseParameterHeader(data[0], false, false), testMap ); }); }); }); suite("parseParameterHeader (2231/2047 support)", function () { let header_tests = [ // Copied from test_MIME_params.js and adapted ["attachment;", ["attachment", {}]], ["attachment; filename=basic", ["attachment", { filename: "basic" }]], ['attachment; filename="\\""', ["attachment", { filename: '"' }]], ['attachment; filename="\\x"', ["attachment", { filename: "x" }]], ['attachment; filename=""', ["attachment", { filename: "" }]], ["attachment; filename=", ["attachment", { filename: "" }]], ["attachment; filename X", ["attachment", {}]], [ "attachment; filename = foo-A.html", ["attachment", { filename: "foo-A.html" }], ], ['attachment; filename="', ["attachment", { filename: "" }]], [ "attachment; filename=foo; trouble", ["attachment", { filename: "foo" }], ], [ "attachment; filename=foo; trouble ", ["attachment", { filename: "foo" }], ], ["attachment", ["attachment", {}]], ["attachment; filename=foo", ["attachment", { filename: "foo" }]], ['attachment; filename="foo"', ["attachment", { filename: "foo" }]], ["attachment; filename='foo'", ["attachment", { filename: "'foo'" }]], [ 'attachment; filename="=?UTF-8?Q?foo?="', ["attachment", { filename: "foo" }], ], [ "attachment; filename==?UTF-8?Q?foo?=", ["attachment", { filename: "foo" }], ], // 2231/5987 tests from test_MIME_params.js [ "attachment; filename*=UTF-8''extended", ["attachment", { filename: "extended" }], ], [ "attachment; filename=basic; filename*=UTF-8''extended", ["attachment", { filename: "extended" }], ], [ "attachment; filename*=UTF-8''extended; filename=basic", ["attachment", { filename: "extended" }], ], [ "attachment; filename*0=foo; filename*1=bar", ["attachment", { filename: "foobar" }], ], [ "attachment; filename*0=first; filename*0=wrong; filename=basic", ["attachment", { filename: "first" }], ], // or basic? [ "attachment; filename*0=first; filename*1=second; filename*0=wrong", ["attachment", { filename: "firstsecond" }], ], // or nothing? [ "attachment; filename=basic; filename*0=foo; filename*1=bar", ["attachment", { filename: "foobar" }], ], [ "attachment; filename=basic; filename*0=first; filename*0=wrong; " + "filename*=UTF-8''extended", ["attachment", { filename: "extended" }], ], [ "attachment; filename=basic; filename*=UTF-8''extended; filename*0=foo" + "; filename*1=bar", ["attachment", { filename: "extended" }], ], [ "attachment; filename*0=foo; filename*2=bar", ["attachment", { filename: "foo" }], ], [ "attachment; filename*0=foo; filename*01=bar", ["attachment", { filename: "foo" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''multi; filename*1=line" + "; filename*2*=%20extended", ["attachment", { filename: "multiline extended" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''multi; filename*1=line" + "; filename*3*=%20extended", ["attachment", { filename: "multiline" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''multi; filename*1=line" + "; filename*0*=UTF-8''wrong; filename*1=bad; filename*2=evil", ["attachment", { filename: "multiline" }], ], [ "attachment; filename=basic; filename*0=UTF-8''multi; filename*=UTF-8'" + "'extended; filename*1=line; filename*2*=%20extended", ["attachment", { filename: "extended" }], ], [ "attachment; filename*0=UTF-8''unescaped; filename*1*=%20so%20includes" + "%20UTF-8''%20in%20value", [ "attachment", { filename: "UTF-8''unescaped so includes UTF-8'' in value" }, ], ], [ "attachment; filename=basic; filename*0*=UTF-8''multi; filename*1=line" + "; filename*0*=UTF-8''wrong; filename*1=bad; filename*2=evil", ["attachment", { filename: "multiline" }], ], [ "attachment; filename=basic; filename*1=foo; filename*2=bar", ["attachment", { filename: "basic" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''0; filename*1=1; filen" + "ame*2=2;filename*3=3;filename*4=4;filename*5=5;filename*6=6;filename" + "*7=7;filename*8=8;filename*9=9;filename*10=a;filename*11=b;filename*" + "12=c;filename*13=d;filename*14=e;filename*15=f", ["attachment", { filename: "0123456789abcdef" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''0; filename*1=1; filen" + "ame*2=2;filename*3=3;filename*4=4;filename*5=5;filename*6=6;filename" + "*7=7;filename*8=8;filename*9=9;filename*10=a;filename*11=b;filename*" + "12=c;filename*14=e", ["attachment", { filename: "0123456789abc" }], ], [ "attachment; filename*1=multi; filename*2=line; filename*3*=%20extended", ["attachment", {}], ], [ "attachment; filename=basic; filename*0*=UTF-8''0; filename*1=1; filen" + "ame*2=2;filename*3=3;filename*4=4;filename*5=5;filename*6=6;filename" + "*7=7;filename*8=8;filename*9=9;filename*10=a;filename*11=b;filename*" + "12=c;filename*13=d;filename*15=f;filename*14=e", ["attachment", { filename: "0123456789abcdef" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''0; filename*1a=1", ["attachment", { filename: "0" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''0; filename*1111111111" + "1111111111111111111111111=1", ["attachment", { filename: "0" }], ], [ "attachment; filename=basic; filename*0*=UTF-8''0; filename*-1=1", ["attachment", { filename: "0" }], ], [ 'attachment; filename=basic; filename*0="0"; filename*1=1; filename*' + "2*=%32", ["attachment", { filename: "012" }], ], [ "attachment; filename=basic; filename**=UTF-8''0;", ["attachment", { filename: "basic" }], ], [ "attachment; filename=IT839\x04\xB5(m8)2.pdf;", ["attachment", { filename: "IT839\u00b5(m8)2.pdf" }], ], ["attachment; filename*=utf-8''%41", ["attachment", { filename: "A" }]], // See bug 651185 and bug 703015 [ "attachment; filename*=\"utf-8''%41\"", ["attachment", { filename: "A" }], ], ["attachment; filename *=utf-8''foo-%41", ["attachment", {}]], ["attachment; filename*=''foo", ["attachment", {}]], ["attachment; filename*=a''foo", ["attachment", {}]], // Bug 692574: we should ignore this one... [ "attachment; filename*=UTF-8'foo-%41", ["attachment", { filename: "foo-A" }], ], ["attachment; filename*=foo-%41", ["attachment", {}]], [ "attachment; filename*=UTF-8'foo-%41; filename=bar", ["attachment", { filename: "foo-A" }], ], [ "attachment; filename*=ISO-8859-1''%c3%a4", ["attachment", { filename: "\u00c3\u00a4" }], ], [ "attachment; filename*=ISO-8859-1''%e2%82%ac", ["attachment", { filename: "\u00e2\u201a\u00ac" }], ], ["attachment; filename*=UTF-8''A%e4B", ["attachment", {}]], [ "attachment; filename*=UTF-8''A%e4B; filename=fallback", ["attachment", { filename: "fallback" }], ], [ "attachment; filename*0*=UTF-8''A%e4B; filename=fallback", ["attachment", { filename: "fallback" }], ], [ "attachment; filename*0*=ISO-8859-15''euro-sign%3d%a4; filename*=ISO-8" + "859-1''currency-sign%3d%a4", ["attachment", { filename: "currency-sign=\u00a4" }], ], [ "attachment; filename*=ISO-8859-1''currency-sign%3d%a4; filename*0*=IS" + "O-8859-15''euro-sign%3d%a4", ["attachment", { filename: "currency-sign=\u00a4" }], ], [ 'attachment; filename=basic; filename*0="foo"; filename*1="\\b\\a\\' + 'r"', ["attachment", { filename: "foobar" }], ], [ 'attachment; filename=basic; filename*0="foo"; filename*1="\\b\\a\\', ["attachment", { filename: "fooba" }], ], ['attachment; filename="\\b\\a\\', ["attachment", { filename: "ba" }]], // According to comments and bugs, this works in necko, but it doesn't // appear that it ought to. See bug 732369 for more info. [ "attachment; extension=bla filename=foo", ["attachment", { extension: "bla" }], ], [ "attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=", ["attachment", { filename: "foo-\u00e4.html" }], ], [ 'attachment; filename="=?ISO-8859-1?Q?foo-=E4.html?="', ["attachment", { filename: "foo-\u00e4.html" }], ], [ 'attachment; filename="=?ISO-8859-1?Q?foo-=E4.html?="; filename*=UTF' + "-8''5987", ["attachment", { filename: "5987" }], ], // ABC\u202Etxt.zip dir switch char in middle. [ "attachment; filename*=UTF-8''%41%42%43%E2%80%AE%2E%74%78%74%2E%7A%69%70", ["attachment", { filename: "ABC .txt.zip" }], ], ]; header_tests.forEach(function (data) { arrayTest(data, function () { let testMap = new Map(); for (let key in data[1][1]) { testMap.set(key, data[1][1][key]); } testMap.preSemi = data[1][0]; smartDeepEqual( headerparser.parseParameterHeader(data[0], true, true), testMap ); }); }); }); suite("parseAddressingHeader", function () { let header_tests = [ ["", []], [ "Joe Schmoe ", [{ name: "Joe Schmoe", email: "jschmoe@invalid.invalid" }], ], [ "user@tinderbox.invalid", [{ name: "", email: "user@tinderbox.invalid" }], ], [ "Hello Kitty , No Kitty ", [ { name: "Hello Kitty", email: "a@b.c" }, { name: "No Kitty", email: "b@b.c" }, ], ], [ "undisclosed-recipients:;", [{ name: "undisclosed-recipients", group: [] }], ], ["me@[127.0.0.1]", [{ name: "", email: "me@[127.0.0.1]" }]], ['"me"@a.com', [{ name: "", email: "me@a.com" }]], ['"!"@a.com', [{ name: "", email: '"!"@a.com' }]], ['"\\!"@a.com', [{ name: "", email: '"!"@a.com' }]], ['"\\\\!"@a.com', [{ name: "", email: '"\\\\!"@a.com' }]], [ "Coward (not@email) ", [{ name: "Coward (not@email)", email: "real@email.com" }], ], [ '"y@example.com" ', [{ name: "y@example.com", email: "xx@example.com" }], ], ["@@@", [{ name: "", email: '"@@"@' }]], [ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", [{ name: "", email: '"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"@' }], ], [ "Nat Ex <{admin@example.com|stocks@example.com|stockmarket@example.com|trades@example.com|money@example.com|broker@example.com|deals@example.com|offers@example.com|news@example.com|binary@example.com|forex@example.com|marketing@example.com|mail@example.com|inboxdeals@example.com|packages@example.com>", [ { name: "Nat Ex", email: '"{admin@example.com|stocks@example.com|stockmarket@example.com|trades@example.com|money@example.com|broker@example.com|deals@example.com|offers@example.com|news@example.com|binary@example.com|forex@example.com|marketing@example.com|mail@example.com|inboxdeals@example.com|packages"@example.com', }, ], ], [ "Group: a@b.com, b@c.com;", [ { name: "Group", group: [ { name: "", email: "a@b.com" }, { name: "", email: "b@c.com" }, ], }, ], ], [ "a@invalid.invalid, Group: a@b.com;", [ { name: "", email: "a@invalid.invalid" }, { name: "Group", group: [{ name: "", email: "a@b.com" }] }, ], ], [ "Group A: a@b.com;, Group B: b@b.com;", [ { name: "Group A", group: [{ name: "", email: "a@b.com" }] }, { name: "Group B", group: [{ name: "", email: "b@b.com" }] }, ], ], [ 'Crazy (', [{ name: "Crazy (, Fake ", [ { name: "Group", group: [ { name: "Real", email: "a@b.com" }, { name: "Fake", email: "a@b.com" }, ], }, ], ], [ '"Joe Q. Public" ,' + 'Test <"abc!x.yz"@foo.invalid>, Test ,' + '"Giant; \\"Big\\" Box" ', [ { name: "Joe Q. Public", email: "john.q.public@example.com" }, { name: "Test", email: '"abc!x.yz"@foo.invalid' }, { name: "Test", email: "test@[xyz!]" }, { name: 'Giant; "Big" Box', email: "sysservices@example.net" }, ], ], [ "Unfortunate breaking < so . many . spaces @ here . invalid >", [ { name: "Unfortunate breaking", email: "so.many.spaces@here.invalid", }, ], ], [ "so . many . spaces @ here . invalid", [{ name: "", email: "so.many.spaces@here.invalid" }], ], ["abc@foo.invalid", [{ name: "", email: "abc@foo.invalid" }]], ["foo ", [{ name: "foo", email: "ghj@foo.invalid" }]], [ "abc@foo.invalid, foo ", [ { name: "", email: "abc@foo.invalid" }, { name: "foo", email: "ghj@foo.invalid" }, ], ], [ "foo bar ", [{ name: "foo bar", email: "foo@bar.invalid" }], ], [ "foo bar , abc@foo.invalid, foo ", [ { name: "foo bar", email: "foo@bar.invalid" }, { name: "", email: "abc@foo.invalid" }, { name: "foo", email: "ghj@foo.invalid" }, ], ], [ "foo\u00D0 bar , \u00F6foo ", [ { name: "foo\u00D0 bar", email: "foo@bar.invalid" }, { name: "\u00F6foo", email: "ghj@foo.invalid" }, ], ], [ "Undisclosed recipients:;", [{ name: "Undisclosed recipients", group: [] }], ], [ '" "@a a;b', [ { name: "", email: '" "@a a' }, { name: "b", email: "" }, ], ], [ "Undisclosed recipients:;\0:; foo ", [ { name: "Undisclosed recipients", group: [] }, { name: "foo", email: "ghj@veryveryveryverylongveryveryveryveryinvali" + "daddress.invalid", }, ], ], // XXX: test_nsIMsgHeaderParser2 has an empty one here... [ "', [{ name: "foo bar", email: "me@foo.invalid" }], ], [ '"foo bar" , "bar foo" ', [ { name: "foo bar", email: "me@foo.invalid" }, { name: "bar foo", email: "me2@foo.invalid" }, ], ], [ "A Group:Ed Jones ,joe@where.invalid,John ;", [ { name: "A Group", group: [ { name: "Ed Jones", email: "c@a.invalid" }, { name: "", email: "joe@where.invalid" }, { name: "John", email: "jdoe@one.invalid" }, ], }, ], ], [ "mygroup:;, empty:;, foo@foo.invalid, othergroup:bar@foo.invalid, bar2" + "@foo.invalid;, y@y.invalid, empty:;", [ { name: "mygroup", group: [] }, { name: "empty", group: [] }, { name: "", email: "foo@foo.invalid" }, { name: "othergroup", group: [ { name: "", email: "bar@foo.invalid" }, { name: "", email: "bar2@foo.invalid" }, ], }, { name: "", email: "y@y.invalid" }, { name: "empty", group: [] }, ], ], [ "Undisclosed recipients:;;;;;;;;;;;;;;;;,,,,,,,,,,,,,,,,", [{ name: "Undisclosed recipients", group: [] }], ], [ "a@xxx.invalid; b@xxx.invalid", [ { name: "", email: "a@xxx.invalid" }, { name: "", email: "b@xxx.invalid" }, ], ], [ "a@xxx.invalid; B ", [ { name: "", email: "a@xxx.invalid" }, { name: "B", email: "b@xxx.invalid" }, ], ], [ '"A " ; b@xxx.invalid', [ { name: "A", email: "a@xxx.invalid" }, { name: "", email: "b@xxx.invalid" }, ], ], [ "A ; B ", [ { name: "A", email: "a@xxx.invalid" }, { name: "B", email: "b@xxx.invalid" }, ], ], [ "A (this: is, a comment;) ; g: (this: is, comment;) C" + ", d.invalid;", [ { name: "A (this: is, a comment;)", email: "a.invalid" }, { name: "g", group: [ { name: "(this: is, comment;) C", email: "c.invalid" }, { name: "d.invalid", email: "" }, ], }, ], ], [ "Mary Smith , extra:;, group:jdoe@example.invalid; Who" + '? ; , "Giant; \\"Big\\" Box" , ", [ { name: "Mary Smith", email: "mary@x.invalid" }, { name: "extra", group: [] }, { name: "group", group: [{ name: "", email: "jdoe@example.invalid" }], }, { name: "Who?", email: "one@y.invalid" }, { name: "", email: "boss@nil.invalid" }, { name: 'Giant; "Big" Box', email: "sysservices@example.invalid" }, ], ], [ "Undisclosed recipients: a@foo.invalid ;;extra:;", [ { name: "Undisclosed recipients", group: [{ name: "", email: "a@foo.invalid" }], }, { name: "extra", group: [] }, ], ], [ "Undisclosed recipients:;;extra:a@foo.invalid;", [ { name: "Undisclosed recipients", group: [] }, { name: "extra", group: [{ name: "", email: "a@foo.invalid" }] }, ], ], ["a < ", [{ name: "a", email: "a@b.c" }]], [ "Name ", [{ name: "Name", email: '"space here"@email.invalid' }], ], ["Name ", [{ name: "Name", email: "not an email" }]], [ "=?UTF-8?Q?Simple?= ", [{ name: "=?UTF-8?Q?Simple?=", email: "a@b.c" }], ], ["No email address", [{ name: "No email address", email: "" }]], // Thought we were parsing an address, but it was a name. [ "name@example.com ", [{ name: "name@example.com", email: "receiver@example.com" }], ], [ "name@huhu.com ", [{ name: "name@huhu.com", email: "receiver@example.com" }], ], // Some names with quotes. [ '"name@huhu.com" ', [{ name: "name@huhu.com", email: "receiver@example.com" }], ], [ '"Chaplin, Charlie" ', [{ name: "Chaplin, Charlie", email: "receiver@example.com" }], ], [ '"name@huhu.com and name@haha.com" ', [ { name: "name@huhu.com and name@haha.com", email: "receiver@example.com", }, ], ], // Handling of comments and legacy display-names as per RFC 5322 §3.4 [ "(c1)n(c2) <(c3)a(c4)@(c5)b(c6).(c7)d(c8)> (c9(c10)c11)", [{ name: "(c1) n (c2) (c9(c10)c11)", email: "a@b.d" }], ], [ "<(c3)a(c4)@(c5)b(c6).(c7)d(c8)> (c9(c10)c11)", [{ name: "(c9(c10)c11)", email: "a@b.d" }], ], [ "(c3)a(c4)@(c5)b(c6).(c7)d(c8)(c9(c10)c11)", [{ name: "c9(c10)c11", email: "a@b.d" }], ], [ "(c1)n(c2) <(c3)a(c4)@(c5)b(c6).(c7)d(c8)> (c9(c10)c11)(c12)", [{ name: "(c1) n (c2) (c9(c10)c11) (c12)", email: "a@b.d" }], ], [ "<(c3)a(c4)@(c5)b(c6).(c7)d(c8)> (c9(c10)c11)(c12)", [{ name: "(c9(c10)c11) (c12)", email: "a@b.d" }], ], [ "(c3)a(c4)@(c5)b(c6).(c7)d(c8)(c9(c10)c11)(c12)", [{ name: "c12", email: "a@b.d" }], ], // Collapse extraneous whitespace and make sure unexpected characters aren't there. [ 'Friend "" \t \t \u00A0\u00A0\u2003 \u00AD \x20\u200B\x20\u200B\x20 \t \u034F \u2028 \uDB40\uDD01 \u2800 \t ', [{ name: "Friend ", email: "ws@example.com" }], ], // Collapse multiple "special" spaces like NBSP (\u00A0), EM space (\u2003), etc. [ "Foe \u00A0\u00A0\u2003 A ", [{ name: "Foe A", email: "foe@example.com" }], ], // Remove tabs. [ "Tabby \t \t A\t\tB ", [{ name: "Tabby A B", email: "tab@example.com" }], ], // Ensure it's the address in angles that is parsed as address. [ "friend@example.com\t, bystander@example.com,Someone (some@invalid) ,", [ { name: "", email: "attacker@example.com" }, { name: "", email: "bystander@example.com" }, { name: "Someone (some@invalid)", email: "someone@example.com" }, ], ], [ "me (via foo@example.com) friend2@example.com ", [ { name: "me (via foo@example.com)", email: "attacker2@example.com", }, ], ], [ " ,friend2@example.com", [ { name: "", email: "attacker@example.com", }, { name: "friend2@example.com", email: "attacker2@example.com", }, ], ], [ 'My "XX ><" YY ("mr >< YY (mr >", [{ name: "Simple", email: "a@b.c" }]], ["=?UTF-8?Q?Simple?= ", [{ name: "Simple", email: "a@b.c" }]], ["=?UTF-8?Q?=3C@b.c?= ", [{ name: "<@b.c", email: "a@b.c" }]], // RFC 2047 tokens should not interfere with lexical processing ["=?UTF-8?Q?a@b.c,?= ", [{ name: "a@b.c,", email: "b@b.c" }]], ["=?UTF-8?Q?a@b.c=2C?= ", [{ name: "a@b.c,", email: "b@b.c" }]], ["=?UTF-8?Q?", [{ name: "<", email: "a@b.c" }]], [ "Simple =?UTF-8?Q?", [{ name: "", email: '"Simple < a"@b.c' }], ], ["Tag <=?UTF-8?Q?email?=@b.c>", [{ name: "Tag", email: "email@b.c" }]], // handling of comments and legacy display-names as per RFC 5322 §3.4 [ "jl1@b.c (=?ISO-8859-1?Q?Joe_L=F6we?=)", [{ name: "Joe Löwe", email: "jl1@b.c" }], ], [ "(=?ISO-8859-1?Q?Joe_L=F6we?=) jl2@b.c", [{ name: "Joe Löwe", email: "jl2@b.c" }], ], [ "(=?ISO-8859-1?Q?Joe_L=F6we?=) jl3@b.c (c2)", [{ name: "c2", email: "jl3@b.c" }], ], [ "=?ISO-8859-1?Q?Joe_L=F6we?= (c2)", [{ name: "Joe Löwe (c2)", email: "jl3@b.c" }], ], [ "(=?ISO-8859-1?Q?Joe_L=F6we?=) (c2)", [{ name: "(Joe Löwe) (c2)", email: "jl3@b.c" }], ], // Bug 1141446: Malformed From addresses with erroneous quotes, // note: acute accents: a \u00E1, e \u00E9, i \u00ED, o \u00F3, u \u00FA. [ '"=?UTF-8?Q?Jazzy_Fern=C3=A1ndez_Nunoz?= jazzy.f.nunoz@example.com ' + '[BCN-FC]" ', [ { name: "Jazzy Fern\u00E1ndez Nunoz jazzy.f.nunoz@example.com [BCN-FC]", email: "Barcelona-Freecycle-noreply@yahoogroups.com", }, ], ], [ '"=?UTF-8?B?TWlyaWFtIEJlcm5hYsOpIFBlcmVsbMOz?= miriam@example.com ' + '[BCN-FC]" ', [ { name: "Miriam Bernab\u00E9 Perell\u00F3 miriam@example.com [BCN-FC]", email: "Barcelona-Freecycle-noreply@yahoogroups.com", }, ], ], [ '"=?iso-8859-1?Q?First_Mar=EDa_Furi=F3_Gancho?= mail@yahoo.es ' + '[BCN-FC]" ', [ { name: "First Mar\u00EDa Furi\u00F3 Gancho mail@yahoo.es [BCN-FC]", email: "Barcelona-Freecycle-noreply@yahoogroups.com", }, ], ], [ '"=?iso-8859-1?B?U29maWEgQ2FzdGVsbPMgUm9tZXJv?= sonia@example.com ' + '[BCN-FC]" ', [ { name: "Sofia Castell\u00F3 Romero sonia@example.com [BCN-FC]", email: "Barcelona-Freecycle-noreply@yahoogroups.com", }, ], ], [ "=?iso-8859-1?Q?Klaus_Eisschl=E4ger_=28k=2Eeisschlaeger=40t-onli?=" + "=?iso-8859-1?Q?ne=2Ede=29?= ", [ { name: "Klaus Eisschläger (k.eisschlaeger@t-online.de)", email: "k.eisschlaeger@t-online.de", }, ], ], [ '"=?UTF-8?Q?=22Claudia_R=C3=B6hschicht=22?= Claudia_Roehschicht@web.de [freecycle-berlin]" ' + "", [ { name: '"Claudia Röhschicht" Claudia_Roehschicht@web.de [freecycle-berlin]', email: "freecycle-berlin-noreply@yahoogroups.de", }, ], ], // Collapse multiple consecutive "special" spaces, like zero width space // etc. // \u00AD is soft hyphen. \u200B is zero width space. [ `invisiblespaceA@friend.example.com B \u200B\u00AD \u200B \u200B A. `, [ { name: "invisiblespaceA@friend.example.com B A.", email: "foeA@example.com", }, ], ], // Collapse multiple consecutive "special" spaces, like zero width space // etc. also when encoded. // \u00AD is soft hyphen. \u200B is zero width space. // unescape(encodeURIComponent(source)) encodes the JavaScript UTF-16 representation // of the string into UTF-8. Example: encodeURIComponent("ö") returns "%C3%B6", // unescape("%C3%B6") returns the bytes 0xC3B6 which is the UTF-8 encoding of "ö". // See bug 1551746 for other ways. [ //"=?UTF-8?B?IMKgIGJsw7YgPGludmlzaWJsZXNwYWNlQGZyaWVuZC5leGFtcGxlLmNvbT4g4oCLIOKAiyDigIsu=?= " `=?UTF-8?B?${btoa( unescape( encodeURIComponent( " \u00AD blö \u200B \u200B \u200B." ) ) )}?= `, [ { name: "blö .", email: "foe@example.com", }, ], ], ]; header_tests.forEach(function (data) { arrayTest(data, function () { assert.deepEqual( headerparser.parseAddressingHeader(data[0], true), data[1] ); }); }); }); suite("parseDateHeader", function () { let header_tests = [ // Some basic tests, derived from searching for Date headers in a mailing // list archive. ["Thu, 06 Sep 2012 08:08:21 -0700", "2012-09-06T08:08:21-0700"], ["Thu, 6 Sep 2012 14:49:05 -0400", "2012-09-06T14:49:05-0400"], ["Fri, 07 Sep 2012 07:30:11 -0700 (PDT)", "2012-09-07T07:30:11-0700"], ["9 Sep 2012 21:03:59 -0000", "2012-09-09T21:03:59Z"], ["Sun, 09 Sep 2012 19:10:59 -0400", "2012-09-09T19:10:59-0400"], ["Wed, 17 Jun 2009 10:12:25 +0530", "2009-06-17T10:12:25+0530"], // Exercise all the months. ["Mon, 28 Jan 2013 13:35:05 -0500", "2013-01-28T13:35:05-0500"], ["Wed, 29 Feb 2012 23:43:26 +0000", "2012-02-29T23:43:26+0000"], ["Sat, 09 Mar 2013 18:24:47 -0500", "2013-03-09T18:24:47-0500"], ["Sat, 27 Apr 2013 12:51:48 -0400", "2013-04-27T12:51:48-0400"], ["Tue, 28 May 2013 17:21:13 +0800", "2013-05-28T17:21:13+0800"], ["Mon, 17 Jun 2013 22:15:41 +0200", "2013-06-17T22:15:41+0200"], ["Wed, 18 Jul 2012 13:50:47 +0900", "2012-07-18T13:50:47+0900"], ["Mon, 13 Aug 2012 13:55:16 +0200", "2012-08-13T13:55:16+0200"], ["Thu, 06 Sep 2012 19:49:47 -0400", "2012-09-06T19:49:47-0400"], ["Mon, 22 Oct 2012 02:27:23 -0700", "2012-10-22T02:27:23-0700"], ["Thu, 22 Nov 2012 09:04:24 +0800", "2012-11-22T09:04:24+0800"], ["Sun, 25 Dec 2011 12:27:13 +0000", "2011-12-25T12:27:13+0000"], // Try out less common timezone offsets. ["Sun, 25 Dec 2011 12:27:13 +1337", "2011-12-25T12:27:13+1337"], ["Sun, 25 Dec 2011 12:27:13 -1337", "2011-12-25T12:27:13-1337"], // Leap seconds! Except that since dates in JS don't believe they exist, // they get shoved to the next second. ["30 Jun 2012 23:59:60 +0000", "2012-07-01T00:00:00Z"], ["31 Dec 2008 23:59:60 +0000", "2009-01-01T00:00:00Z"], // This one doesn't exist (they are added only as needed on an irregular // basis), but it's plausible... ["30 Jun 2030 23:59:60 +0000", "2030-07-01T00:00:00Z"], // ... and this one isn't. ["10 Jun 2030 13:39:60 +0000", "2030-06-10T13:40:00Z"], // How about leap seconds in other timezones? ["30 Jun 2012 18:59:60 -0500", "2012-07-01T00:00:00Z"], // RFC 5322 obsolete date productions ["Sun, 26 Jan 14 17:14:22 -0600", "2014-01-26T17:14:22-0600"], ["Tue, 26 Jan 49 17:14:22 -0600", "2049-01-26T17:14:22-0600"], ["Thu, 26 Jan 50 17:14:22 -0600", "1950-01-26T17:14:22-0600"], ["Sun, 26 Jan 2014 17:14:22 EST", "2014-01-26T17:14:22-0500"], ["Sun, 26 Jan 2014 17:14:22 CST", "2014-01-26T17:14:22-0600"], ["Sun, 26 Jan 2014 17:14:22 MST", "2014-01-26T17:14:22-0700"], ["Sun, 26 Jan 2014 17:14:22 PST", "2014-01-26T17:14:22-0800"], ["Sun, 26 Jan 2014 17:14:22 AST", "2014-01-26T17:14:22-0400"], ["Sun, 26 Jan 2014 17:14:22 NST", "2014-01-26T17:14:22-0330"], ["Sun, 26 Jan 2014 17:14:22 MET", "2014-01-26T17:14:22+0100"], ["Sun, 26 Jan 2014 17:14:22 EET", "2014-01-26T17:14:22+0200"], ["Sun, 26 Jan 2014 17:14:22 JST", "2014-01-26T17:14:22+0900"], ["Sun, 26 Jan 2014 17:14:22 GMT", "2014-01-26T17:14:22+0000"], ["Sun, 26 Jan 2014 17:14:22 UT", "2014-01-26T17:14:22+0000"], // Daylight savings timezones, even though these aren't actually during // daylight savings time for the relevant jurisdictions. ["Sun, 26 Jan 2014 17:14:22 EDT", "2014-01-26T17:14:22-0400"], ["Sun, 26 Jan 2014 17:14:22 CDT", "2014-01-26T17:14:22-0500"], ["Sun, 26 Jan 2014 17:14:22 MDT", "2014-01-26T17:14:22-0600"], ["Sun, 26 Jan 2014 17:14:22 PDT", "2014-01-26T17:14:22-0700"], ["Sun, 26 Jan 2014 17:14:22 BST", "2014-01-26T17:14:22+0100"], // Unknown time zone--assume UTC ["Sun, 26 Jan 2014 17:14:22 QMT", "2014-01-26T17:14:22+0000"], // The following days of the week are incorrect. ["Tue, 28 Jan 2013 13:35:05 -0500", "2013-01-28T13:35:05-0500"], ["Thu, 26 Jan 14 17:14:22 -0600", "2014-01-26T17:14:22-0600"], ["Fri, 26 Jan 49 17:14:22 -0600", "2049-01-26T17:14:22-0600"], ["Mon, 26 Jan 50 17:14:22 -0600", "1950-01-26T17:14:22-0600"], // And for these 2 digit years, they are correct for the other century. ["Mon, 26 Jan 14 17:14:22 -0600", "2014-01-26T17:14:22-0600"], ["Wed, 26 Jan 49 17:14:22 -0600", "2049-01-26T17:14:22-0600"], ["Wed, 26 Jan 50 17:14:22 -0600", "1950-01-26T17:14:22-0600"], // Try with some illegal names for days of the week or months of the year. ["Sam, 05 Apr 2014 15:04:13 -0500", "2014-04-05T15:04:13-0500"], ["Lun, 01 Apr 2014 15:04:13 -0500", "2014-04-01T15:04:13-0500"], ["Mar, 02 Apr 2014 15:04:13 -0500", "2014-04-02T15:04:13-0500"], ["Mar, 02 April 2014 15:04:13 -0500", "2014-04-02T15:04:13-0500"], ["Mar, 02 Avr 2014 15:04:13 -0500", NaN], ["Tue, 02 A 2014 15:04:13 -0500", NaN], // A truly invalid date ["Coincident with the rapture", NaN], ]; header_tests.forEach(function (data) { arrayTest(data, function () { assert.equal( headerparser.parseDateHeader(data[0]).toString(), new Date(data[1]).toString() ); }); }); }); suite("decodeRFC2047Words", function () { let header_tests = [ // Some basic sanity tests for the test process ["Test", "Test"], ["Test 2", "Test 2"], ["Basic words", "Basic words"], ["Not a =? word", "Not a =? word"], // Simple 2047 decodings ["=?UTF-8?Q?Encoded?=", "Encoded"], ["=?UTF-8?q?Encoded?=", "Encoded"], ["=?ISO-8859-1?Q?oxyg=e8ne?=", "oxyg\u00e8ne"], ["=?UTF-8?B?QmFzZTY0?=", "Base64"], ["=?UTF-8?b?QmFzZTY0?=", "Base64"], ["=?UTF-8?Q?A_space?=", "A space"], ["=?UTF-8?Q?A space?=", "A space"], ["A =?UTF-8?Q?B?= C", "A B C"], ["=?UTF-8?Q?A?= =?UTF-8?Q?B?=", "AB"], ["=?UTF-8?Q?oxyg=c3=a8ne?=", "oxyg\u00e8ne"], ["=?utf-8?Q?oxyg=C3=A8ne?=", "oxyg\u00e8ne"], ["=?UTF-8?B?b3h5Z8OobmU=?=", "oxyg\u00e8ne"], ["=?UTF-8*fr?B?b3h5Z8OobmU=?=", "oxyg\u00e8ne"], [ "=?BIG5?Q?=B9=CF=AE=D1=C0]SSCI=A4=CEJCR=B8=EA=AE=C6=AEw=C1=BF=B2=DF=A1A=A8" + "=F3=A7U=B1z=A1u=B4=A3=A4=C9=AC=E3=A8s=AF=C0=BD=E8=BBP=AE=C4=B2v=A5H=A4=CE" + "=A7=EB=BDZ=B5=A6=B2=A4=AA=BA=B9B=A5=CE=A1v=A1A=C5w=AA=EF=B3=F8=A6W=B0=D1" + "=A5[=A1C?=", "\u5716\u66F8\u9928SSCI\u53CAJCR\u8CC7\u6599\u5EAB\u8B1B" + "\u7FD2\uFF0C\u5354\u52A9\u60A8\u300C\u63D0\u5347\u7814\u7A76\u7D20\u8CEA" + "\u8207\u6548\u7387\u4EE5\u53CA\u6295\u7A3F\u7B56\u7565\u7684\u904B\u7528" + "\u300D\uFF0C\u6B61\u8FCE\u5831\u540D\u53C3\u52A0\u3002", ], // Invalid decodings ["=?UTF-8?Q?=f0ab?=", "\ufffdab"], ["=?UTF-8?Q?=f0?= ab", "\ufffd ab"], [ "=?UTF-8?Q?=ed=a0=bd=ed=b2=a9?=", "\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd", ], ["=?NoSuchCharset?Q?ab?=", "=?NoSuchCharset?Q?ab?="], ["=?UTF-8?U?Encoded?=", "=?UTF-8?U?Encoded?="], ["=?UTF-8?Q?Almost", "=?UTF-8?Q?Almost"], // Try some non-BMP characters in various charsets ["=?UTF-8?B?8J+SqQ==?=", "\ud83d\udca9"], // The goal for the next one is to be a non-BMP in a non-full-Unicode // charset. The only category where this exists is a small set of // characters in Big5, which were previously mapped to a PUA in an older // version but then reassigned to Plane 1. However, Big5 is really a set // of slightly different, slightly incompatible charsets. // TODO: This requires future investigation. Bug 912470 discusses the // changes to Big5 proposed within Mozilla. // ["=?Big5?Q?=87E?=", "\ud85c\ude67"], ["=?GB18030?B?lDnaMw==?=", "\ud83d\udca9"], // How to handle breaks in multi-byte encoding ["=?UTF-8?Q?=f0=9f?= =?UTF-8?Q?=92=a9?=", "\ud83d\udca9"], ["=?UTF-8?B?8J+S?= =?UTF-8?B?qQ==?=", "\ud83d\udca9"], ["=?UTF-8?B?8J+S?= =?UTF-8?Q?=a9?=", "\ud83d\udca9"], ["=?UTF-8?B?8J+S?= =?ISO-8859-1?B?qQ==?=", "\ufffd\u00a9"], ["=?UTF-8?Q?=f0?= =?UTF-8?Q?ab?=", "\ufffdab"], // This is a split non-BMP character. [ "=?UTF-8?B?YfCfkqnwn5Kp8J+SqfCfkqnwn5Kp8J+SqfCfkqnvv70=?= =?UTF-8?B?77+9?=", "a\uD83D\uDCA9\uD83D\uDCA9\uD83D\uDCA9\uD83D\uDCA9\uD83D\uDCA9\uD83D" + "\uDCA9\uD83D\uDCA9\uFFFD\uFFFD", ], // Spaces in RFC 2047 tokens ["=?UTF-8?Q?Invalid token?=", "Invalid token"], // More tests from bug 493544 [ "AAA =?UTF-8?Q?bbb?= CCC =?UTF-8?Q?ddd?= EEE =?UTF-8?Q?fff?= GGG", "AAA bbb CCC ddd EEE fff GGG", ], [ "=?UTF-8?B?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiC" + "Ag?=\n =?UTF-8?B?4oiJICDiiIogIOKIiyAg4oiMICDiiI0gIOKIjiAg4oiP?=", "\u2200 \u2201 \u2202 \u2203 \u2204 \u2205 \u2206 \u2207 " + "\u2208 \u2209 \u220a \u220b \u220c \u220d \u220e \u220f", ], [ "=?utf-8?Q?=E2=88=80__=E2=88=81__=E2=88=82__=E2=88=83__=E2=88=84__=E2" + "?=\n =?utf-8?Q?=88=85__=E2=88=86__=E2=88=87__=E2=88=88__=E2=88=89__" + "=E2=88?=\n =?utf-8?Q?=8A__=E2=88=8B__=E2=88=8C__=E2=88=8D__=E2=88=8" + "E__=E2=88=8F?=", "\u2200 \u2201 \u2202 \u2203 \u2204 \u2205 \u2206 \u2207 " + "\u2208 \u2209 \u220a \u220b \u220c \u220d \u220e \u220f", ], [ "=?UTF-8?B?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiA" + "==?=\n =?UTF-8?B?ICDiiIkgIOKIiiAg4oiLICDiiIwgIOKIjSAg4oiOICDiiI8=?=", "\u2200 \u2201 \u2202 \u2203 \u2204 \u2205 \u2206 \u2207 " + "\u2208 \u2209 \u220a \u220b \u220c \u220d \u220e \u220f", ], [ "=?UTF-8?b?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiA" + "==?=\n =?UTF-8?b?ICDiiIkgIOKIiiAg4oiLICDiiIwgIOKIjSAg4oiOICDiiI8=?=", "\u2200 \u2201 \u2202 \u2203 \u2204 \u2205 \u2206 \u2207 " + "\u2208 \u2209 \u220a \u220b \u220c \u220d \u220e \u220f", ], [ "=?utf-8?Q?=E2=88=80__=E2=88=81__=E2=88=82__=E2=88=83__=E2=88=84__?=\n" + " =?utf-8?Q?=E2=88=85__=E2=88=86__=E2=88=87__=E2=88=88__=E2=88=89__?=\n" + " =?utf-8?Q?=E2=88=8A__=E2=88=8B__=E2=88=8C__=E2=88=8D__=E2=88=8E__?=\n" + " =?utf-8?Q?=E2=88=8F?=", "\u2200 \u2201 \u2202 \u2203 \u2204 \u2205 \u2206 \u2207 " + "\u2208 \u2209 \u220a \u220b \u220c \u220d \u220e \u220f", ], [ "=?utf-8?q?=E2=88=80__=E2=88=81__=E2=88=82__=E2=88=83__=E2=88=84__?=\n" + " =?utf-8?q?=E2=88=85__=E2=88=86__=E2=88=87__=E2=88=88__=E2=88=89__?=\n" + " =?utf-8?q?=E2=88=8A__=E2=88=8B__=E2=88=8C__=E2=88=8D__=E2=88=8E__?=\n" + " =?utf-8?q?=E2=88=8F?=", "\u2200 \u2201 \u2202 \u2203 \u2204 \u2205 \u2206 \u2207 " + "\u2208 \u2209 \u220a \u220b \u220c \u220d \u220e \u220f", ], [ "=?UTF-8?B?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiA=" + "==?=\n =?UTF-8?B?ICDiiIkgIOKIiiAg4oiLICDiiIwgIOKIjSAg4oiOICDiiI8=?=", "\u2200 \u2201 \u2202 \u2203 \u2204 \u2205 \u2206 \u2207 " + "\u2208 \u2209 \u220a \u220b \u220c \u220d \u220e \u220f", ], // Some interesting headers found in the wild: // Bug 1498795: Tolerate spaces in base64-encoded RFC 2047 tokens. [ "=?UTF-8?B?Q29uc2lndW Ug dG9kbyBlbCBmw7p0Ym9sIA==?= =?iso-8859-1?" + "B?Y2 9uI EZVU0nTTiBjb24g?= =?windows-1252?B?ZX N 0ZSBvZmVydPNu?=", "Consigue todo el f\u00fatbol con FUSI\u00d3N con este ofert\u00f3n", ], ["=?us-ascii?Q?=09Edward_Rosten?=", "\tEdward Rosten"], [ "=?us-ascii?Q?=3D=3FUTF-8=3FQ=3Ff=3DC3=3DBCr=3F=3D?=", "=?UTF-8?Q?f=C3=BCr?=", ], // We don't decode unrecognized charsets (This one is actually UTF-8). ["=??B?Sy4gSC4gdm9uIFLDvGRlbg==?=", "=??B?Sy4gSC4gdm9uIFLDvGRlbg==?="], // Test for bug 1374149 with ISO-2022-JP where we shouldn't stream // if the first token ends in ESC(B. // GyRCJCIbKEI= is the base64 encoding of ESC$B$"ESC(B. [ "=?ISO-2022-JP?B?GyRCJCIbKEI=?==?ISO-2022-JP?B?GyRCJCIbKEI=?=", "ああ", ], // Tolerate invalid split of character, € = 0xE2 0x82 0xAC in UTF-8. [ "Split =?UTF-8?Q?=E2?= =?UTF-8?Q?=82=AC?= after first byte", "Split € after first byte", ], [ "Split =?UTF-8?Q?=E2=82?= =?UTF-8?Q?=AC?= after second byte", "Split € after second byte", ], ["Byte missing =?UTF-8?Q?=E2=82?=", "Byte missing \ufffd"], // Replacement character for invalid input. // Test for bug 1301989: Tolerate invalid base64 encoding. ["=?us-ascii?B?YWJjZA==?=", "abcd"], // correct ["=?us-ascii?B?YWJjZA=?=", "abc"], // not a multiple of 4 ["=?us-ascii?B?Y=WJjZA==?=", "abcd"], // invalid = ["=?us-ascii?B?Y=WJj==ZA==?=", "abcd"], // invalid = ["=?us-ascii?B?YWJjZA===?=", "abcd"], // excess = at the end, see bug 227290. // Test for bug 1437282: Tolerate extra = padding at the end. ["=?us-ascii?B?VGVzdA==?=", "Test"], // This is correct. ["=?us-ascii?B?VGVzdA===?=", "Test"], ["=?us-ascii?B?VGVzdA====?=", "Test"], ["=?us-ascii?B?VGVzdA=====?=", "Test"], ["=?us-ascii?B?VGVzdA======?=", "Test"], ["=?us-ascii?B?VGVzdA========?=", "Test"], ["=?us-ascii?B?VGVzdA=========?=", "Test"], ["=?us-ascii?B?VGVzdA==========?=", "Test"], ["=?us-ascii?B?VGVzdA===========?=", "Test"], ]; header_tests.forEach(function (data) { arrayTest(data, function () { assert.deepEqual(headerparser.decodeRFC2047Words(data[0]), data[1]); }); }); }); suite("8-bit header processing", function () { let header_tests = [ // Non-ASCII header values ["oxyg\xc3\xa8ne", "oxyg\u00e8ne", "UTF-8"], ["oxyg\xc3\xa8ne", "oxyg\u00e8ne", "ISO-8859-1"], // UTF-8 overrides ["oxyg\xc3\xa8ne", "oxyg\u00e8ne"], // default to UTF-8 if no charset ["oxyg\xe8ne", "oxyg\ufffdne", "UTF-8"], ["oxyg\xe8ne", "oxyg\u00e8ne", "ISO-8859-1"], ["\xc3\xa8\xe8", "\u00e8\ufffd", "UTF-8"], ["\xc3\xa8\xe8", "\u00c3\u00a8\u00e8", "ISO-8859-1"], // Don't fallback to UTF-16 or UTF-32 ["\xe8S!0", "\ufffdS!0", "UTF-16"], ["\xe8S!0", "\ufffdS!0", "UTF-16be"], ["\xe8S!0", "\ufffdS!0", "UTF-32"], ["\xe8S!0", "\ufffdS!0", "utf-32"], // Don't combine encoded-word and header charset decoding ["=?UTF-8?Q?=c3?= \xa8", "\ufffd \ufffd", "UTF-8"], ["=?UTF-8?Q?=c3?= \xa8", "\ufffd \u00a8", "ISO-8859-1"], ["\xc3 =?UTF-8?Q?=a8?=", "\ufffd \ufffd", "UTF-8"], ["\xc3 =?UTF-8?Q?=a8?=", "\u00c3 \ufffd", "ISO-8859-1"], ]; header_tests.forEach(function (data) { arrayTest(data, function () { assert.deepEqual( headerparser.decodeRFC2047Words( headerparser.convert8BitHeader( data[0], data.length > 2 ? data[2] : null ) ), data[1] ); }); }); }); }); });