1 |
|
2 | var test = require("tap").test;
|
3 |
|
4 | var addrs = require("../lib/email-addresses");
|
5 |
|
6 |
|
7 | test("simple one address function", function (t) {
|
8 | var fxn, result;
|
9 | fxn = addrs.parseOneAddress;
|
10 |
|
11 | result = fxn("ABC < a@b.c>") || {};
|
12 | t.notOk(result.node, "has no ast information");
|
13 | t.equal(result.address, "a@b.c", "full address, semantic only");
|
14 | t.equal(result.name, "ABC", "display name");
|
15 | t.equal(result.local, "a", "local part");
|
16 | t.equal(result.domain, "b.c", "domain");
|
17 |
|
18 | t.equal(fxn("bogus"), null, "bogus address > null");
|
19 | t.equal(fxn("a@b.c, d@e.f"), null, "address list > null");
|
20 |
|
21 | result = fxn("\"Françoise Lefèvre\"@example.com");
|
22 | t.ok(result, "RFC 6532 (Unicode support) is enabled by default");
|
23 | t.equal(result.parts.local.semantic, "Françoise Lefèvre");
|
24 |
|
25 | result = fxn("First Last <first@last.com>");
|
26 | t.equal(result.name, "First Last",
|
27 | "whitespace is not removed from display names without quotes");
|
28 |
|
29 | result = fxn(" First Last <first@last.com>");
|
30 | t.equal(result.name, "First Last",
|
31 | "whitespace in names is collapsed");
|
32 |
|
33 | t.end();
|
34 | });
|
35 |
|
36 | test("simple address list function", function (t) {
|
37 | var fxn, result;
|
38 | fxn = addrs.parseAddressList;
|
39 |
|
40 | result = fxn("\"A B C\" < a@b.c>, d@e") || [{}, {}];
|
41 | t.notOk(result[0].node, "has no ast information");
|
42 | t.equal(result[0].address, "a@b.c", "full address, semantic only");
|
43 | t.equal(result[0].name, "A B C", "display name");
|
44 | t.equal(result[0].local, "a", "local part");
|
45 | t.equal(result[0].domain, "b.c", "domain");
|
46 |
|
47 | t.notOk(result[1].node, "has no ast information");
|
48 | t.equal(result[1].address, "d@e", "second address");
|
49 | t.equal(result[1].name, null, "second display name");
|
50 | t.equal(result[1].local, "d", "second local part");
|
51 | t.equal(result[1].domain, "e", "second domain");
|
52 |
|
53 | t.equal(fxn("bogus"), null, "bogus address > null");
|
54 | t.equal(fxn("a@b.c").length, 1, "single address > ok");
|
55 |
|
56 | result = fxn("\"Françoise Lefèvre\"@example.com");
|
57 | t.ok(result, "RFC 6532 (Unicode support) is enabled by default");
|
58 |
|
59 | t.end();
|
60 | });
|
61 |
|
62 | test("rfc5322 parser", function (t) {
|
63 | var fxn, result;
|
64 | fxn = addrs;
|
65 |
|
66 | result = fxn("\"A B C\" < a@b.c>, d@e") || {};
|
67 | t.ok(result.ast, "has an ast");
|
68 | t.ok(result.addresses.length, "has the addresses");
|
69 |
|
70 | result = result.addresses;
|
71 | t.ok(result[0].node, "has link to node in ast");
|
72 | t.equal(result[0].address, "a@b.c", "full address, semantic only");
|
73 | t.equal(result[0].name, "A B C", "display name");
|
74 | t.equal(result[0].local, "a", "local part");
|
75 | t.equal(result[0].domain, "b.c", "domain");
|
76 |
|
77 | t.ok(result[1].node, "has link to node in ast");
|
78 | t.equal(result[1].address, "d@e", "second address");
|
79 | t.equal(result[1].name, null, "second display name");
|
80 | t.equal(result[1].local, "d", "second local part");
|
81 | t.equal(result[1].domain, "e", "second domain");
|
82 |
|
83 | t.equal(fxn("bogus"), null, "bogus address > null");
|
84 | t.equal(fxn("a@b bogus"), null, "not all input is an email list > null");
|
85 |
|
86 | result = fxn({ input: "a@b bogus", partial: true });
|
87 | t.ok(result, "can obtain partial results if at beginning of string");
|
88 |
|
89 | result = fxn("\"Françoise Lefèvre\"@example.com");
|
90 | t.notOk(result, "extended ascii characters are invalid according to RFC 5322");
|
91 |
|
92 | result = fxn({ input: "\"Françoise Lefèvre\"@example.com", rfc6532: true });
|
93 | t.ok(result, "but extended ascii is allowed starting with RFC 6532");
|
94 |
|
95 | t.end();
|
96 | });
|
97 |
|
98 | test("display-name semantic interpretation", function (t) {
|
99 | var fxn, result;
|
100 | fxn = addrs.parseOneAddress;
|
101 |
|
102 | function check(s, comment, expected) {
|
103 | t.equal(fxn(s).name, expected || "First Last", comment);
|
104 | }
|
105 |
|
106 | check(
|
107 | "First<foo@bar.com>",
|
108 | "single basic name is ok",
|
109 | "First");
|
110 |
|
111 | check(
|
112 | "First Last<foo@bar.com>",
|
113 | "no extra whitespace is ok");
|
114 |
|
115 | check(
|
116 | " First Last <foo@bar.com>",
|
117 | "single whitespace at beginning and end is removed");
|
118 |
|
119 | check(
|
120 | "First Last<foo@bar.com>",
|
121 | "whitespace in the middle is collapsed");
|
122 |
|
123 | check(
|
124 | " First Last <foo@bar.com>",
|
125 | "extra whitespace everywhere is collapsed");
|
126 |
|
127 | check(
|
128 | " First Middle Last <foo@bar.com>",
|
129 | "extra whitespace everywhere is collapsed, with more than 2 names",
|
130 | "First Middle Last");
|
131 |
|
132 | check(
|
133 | "\tFirst \t Last\t<foo@bar.com>",
|
134 | "extra whitespace everywhere is collapsed with a mix of tabs and spaces");
|
135 |
|
136 | check(
|
137 | "\"First Last\"<foo@bar.com>",
|
138 | "surrounding quotes are not semantic");
|
139 |
|
140 | check(
|
141 | " \t \"First Last\" <foo@bar.com>",
|
142 | "surrounding quotes are not semantic and whitespace is collapsed");
|
143 |
|
144 | check(
|
145 | " \t \"First \\\"The\t\tNickname\\\" Last\" <foo@bar.com>",
|
146 | "surrounding quotes are not semantic, but inner quotes are, and whitespace is collapsed",
|
147 | "First \"The Nickname\" Last");
|
148 |
|
149 | t.end();
|
150 | });
|
151 |
|
152 | test("address semantic interpretation", function (t) {
|
153 | var fxn, result;
|
154 | fxn = addrs.parseOneAddress;
|
155 |
|
156 | function check(s, comment, expected) {
|
157 | t.equal(fxn(s).address, expected || "foo@bar.com", comment);
|
158 | }
|
159 |
|
160 | check(
|
161 | "foo@bar.com",
|
162 | "plain address is ok");
|
163 |
|
164 | check(
|
165 | " foo@bar.com ",
|
166 | "plain address with whitespace at beginning and end");
|
167 |
|
168 | check(
|
169 | "foo @bar.com",
|
170 | "plain address with whitespace left of @ sign");
|
171 |
|
172 | check(
|
173 | "foo@ bar.com",
|
174 | "plain address with whitespace right of @ sign");
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | check(
|
180 | "\t foo\t\t@ \t bar.com \t ",
|
181 | "plain address with whitespace everywhere");
|
182 |
|
183 | check(
|
184 | "Bob <\t foo\t\t@ \t bar.com \t >",
|
185 | "angle-addr with whitespace everywhere");
|
186 |
|
187 | check(
|
188 | "\"foo\"@bar.com",
|
189 | "plain address with quoted-string local-part");
|
190 |
|
191 | check(
|
192 | "\"foo baz\"@bar.com",
|
193 | "plain address with quoted-string local-part including spaces" +
|
194 | " (Note: This is a confusing situation for 'semantic' local-parts, and" +
|
195 | " in this case we don't return a valid address. Don't use this. Just" +
|
196 | " take the raw tokens used for the address if you always want it to be equivalent.)",
|
197 | "foo baz@bar.com");
|
198 |
|
199 | t.end();
|
200 | });
|
201 |
|
202 | test("unicode support", function (t) {
|
203 | var fxn, result;
|
204 | fxn = addrs.parseOneAddress;
|
205 |
|
206 | result = fxn("\"Françoise Lefèvre\"@example.com");
|
207 | t.ok(result, "extended ascii characters are allowed");
|
208 |
|
209 | result = fxn("杨孝宇 <xiaoyu@example.com>");
|
210 | t.ok(result, "unicode support includes chinese characters (display-name, no quoted string)");
|
211 |
|
212 | result = fxn("\"杨孝宇\" <xiaoyu@example.com>");
|
213 | t.ok(result, "unicode support includes chinese characters (display-name, quoted-string)");
|
214 |
|
215 | t.end();
|
216 | });
|
217 |
|
218 | test("rejectTLD option", function (t) {
|
219 | var fxn, result;
|
220 | fxn = addrs.parseOneAddress;
|
221 |
|
222 | result = fxn({ input: "foo@bar.com", rejectTLD: false });
|
223 | t.ok(result, "a simple address is ok (rejectTLD false)");
|
224 |
|
225 | result = fxn({ input: "foo@bar.com", rejectTLD: true });
|
226 | t.ok(result, "a simple address is ok (rejectTLD true)");
|
227 |
|
228 | result = fxn({ input: "\"Foo Bar\" <foo@bar.com>", rejectTLD: false });
|
229 | t.ok(result, "a more complicated address is ok (rejectTLD false)");
|
230 |
|
231 | result = fxn({ input: "\"Foo Bar\" <foo@bar.com>", rejectTLD: true });
|
232 | t.ok(result, "a more complicated address is ok (rejectTLD true)");
|
233 |
|
234 | result = fxn({ input: "foo@bar", rejectTLD: false });
|
235 | t.ok(result, "an address with a TLD for its domain is allowed by rfc 5322");
|
236 |
|
237 | result = fxn({ input: "foo@bar", rejectTLD: true });
|
238 | t.notOk(result, "an address with a TLD for its domain is rejected when the option is set");
|
239 |
|
240 | result = fxn({ input: "\"Foo Bar\" <foo@bar>", rejectTLD: false });
|
241 | t.ok(result, "a more complicated address with a TLD for its domain is allowed by rfc 5322");
|
242 |
|
243 | result = fxn({ input: "\"Foo Bar\" <foo@bar>", rejectTLD: true });
|
244 | t.notOk(result, "a more complicated address with a TLD for its domain is rejected when the option is set");
|
245 |
|
246 | result = fxn({ input: "jack@", rejectTLD: true });
|
247 | t.notOk(result, "no domain is ok with rejectTLD set");
|
248 |
|
249 | t.end();
|
250 | });
|
251 |
|
252 | test("dots in unquoted display-names", function (t) {
|
253 | var fxn, result;
|
254 | fxn = addrs.parseOneAddress;
|
255 |
|
256 | result = fxn("H.P. Lovecraft <foo@bar.net>");
|
257 | t.ok(result, "dots in the middle of an unquoted display-name with spaces (obs-phrase production)");
|
258 |
|
259 | result = fxn("Hmm Yes Info. <foo@bar.net>");
|
260 | t.ok(result, "dots to end an unquoted display-name (obs-phrase production)");
|
261 |
|
262 | result = fxn("bar.net <foo@bar.net>");
|
263 | t.ok(result, "dots in the middle of an unquoted display-name without spaces (obs-phrase production)");
|
264 |
|
265 | result = fxn({ input: "H.P. Lovecraft <foo@bar.net>", strict: true });
|
266 | t.notOk(result, "dots without using 'obsolete' productions");
|
267 |
|
268 | result = fxn({ input: "Hmm Yes Info. <foo@bar.net>", strict: true });
|
269 | t.notOk(result, "dots without using 'obsolete' productions");
|
270 |
|
271 | result = fxn({ input: "bar.net <foo@bar.net>", strict: true });
|
272 | t.notOk(result, "dots without using 'obsolete' productions");
|
273 |
|
274 | t.end();
|
275 | });
|
276 |
|
277 | test("rfc6854 - from", function (t) {
|
278 | var fxn, result;
|
279 | fxn = addrs.parseFrom;
|
280 |
|
281 | result = fxn("Managing Partners:ben@example.com,carol@example.com;");
|
282 | t.ok(result, "Parse group for From:");
|
283 | t.equal(result[0].name, "Managing Partners", "Extract group name");
|
284 | t.equal(result[0].addresses.length, 2, "Extract group addresses");
|
285 | t.equal(result[0].addresses[0].address, "ben@example.com", "Group address 1");
|
286 | t.equal(result[0].addresses[1].address, "carol@example.com", "Group address 1")
|
287 |
|
288 | result = fxn("Managing Partners:ben@example.com,carol@example.com;, \"Foo\" <foo@example.com>");
|
289 | t.ok(result, "Group and mailbox");
|
290 | t.equal(result[0].name, "Managing Partners", "Extract group name");
|
291 | t.equal(result[1].name, "Foo", "Second address name");
|
292 | t.equal(result[1].local, "foo", "Second address local");
|
293 | t.equal(result[1].domain, "example.com", "Second address domain");
|
294 |
|
295 | t.end();
|
296 | });
|
297 |
|
298 | test("rfc6854 - sender", function (t) {
|
299 | var fxn, result;
|
300 | fxn = addrs.parseSender;
|
301 |
|
302 | result = fxn("Managing Partners:ben@example.com,carol@example.com;");
|
303 | t.ok(result, "Parse group for Sender:");
|
304 | t.equal(result.length, undefined, "Result is not an array");
|
305 | t.equal(result.name, "Managing Partners", "Result has name");
|
306 | t.equal(result.local, undefined, "Result has no local part");
|
307 | t.equal(result.addresses.length, 2, "Result has two addresses");
|
308 | t.equal(result.addresses[0].address, "ben@example.com", "Result first address match");
|
309 | t.equal(result.addresses[1].address, "carol@example.com", "Result first address match");
|
310 |
|
311 | t.end();
|
312 | });
|
313 |
|
314 | test("rfc6854 - reply-to", function (t) {
|
315 | var fxn, result;
|
316 | fxn = addrs.parseReplyTo;
|
317 |
|
318 | result = fxn("Managing Partners:ben@example.com,carol@example.com;");
|
319 | t.ok(result, "Parse group for Reply-To:");
|
320 | t.equal(result[0].name, "Managing Partners", "Extract group name");
|
321 | t.equal(result[0].addresses.length, 2, "Extract group addresses");
|
322 | t.equal(result[0].addresses[0].address, "ben@example.com", "Group address 1");
|
323 | t.equal(result[0].addresses[1].address, "carol@example.com", "Group address 1")
|
324 |
|
325 | result = fxn("Managing Partners:ben@example.com,carol@example.com;, \"Foo\" <foo@example.com>");
|
326 | t.ok(result, "Group and mailbox");
|
327 | t.equal(result[0].name, "Managing Partners", "Extract group name");
|
328 | t.equal(result[1].name, "Foo", "Second address name");
|
329 | t.equal(result[1].local, "foo", "Second address local");
|
330 | t.equal(result[1].domain, "example.com", "Second address domain");
|
331 |
|
332 | result = fxn("Managing Partners:ben@example.com,carol@example.com;, \"Foo\" <foo@example.com>, Group2:alice@example.com;");
|
333 | t.ok(result, "Group, mailbox, group");
|
334 | t.equal(result[0].name, "Managing Partners", "First: group name");
|
335 | t.equal(result[0].addresses[0].address, "ben@example.com");
|
336 | t.equal(result[0].addresses[1].address, "carol@example.com");
|
337 | t.equal(result[1].name, "Foo", "Second: address name");
|
338 | t.equal(result[2].name, "Group2", "Third: group name");
|
339 |
|
340 | t.end();
|
341 | });
|
342 |
|
343 | test("whitespace in domain", function (t) {
|
344 | var fxn, result;
|
345 | fxn = addrs.parseOneAddress;
|
346 |
|
347 | result = fxn('":sysmail"@ Some-Group. Some-Org');
|
348 | t.ok(result, "spaces in domain parses ok");
|
349 | t.equal(result.domain, "Some-Group.Some-Org", "domain parsing strips whitespace");
|
350 |
|
351 | t.end();
|
352 | })
|