email-addresses.js 13 KB

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