index.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. 'use strict';
  2. const Punycode = require('punycode');
  3. const Code = require('code');
  4. const Address = require('..');
  5. const Lab = require('lab');
  6. const internals = {};
  7. const { describe, it } = exports.lab = Lab.script();
  8. const expect = Code.expect;
  9. describe('email', () => {
  10. describe('analyze()', () => {
  11. it('identifies error', () => {
  12. const tests = [
  13. ['', 'Address must be a non-empty string'],
  14. ['êjness@iana.org', 'Address contains forbidden Unicode characters', { allowUnicode: false }],
  15. ['test@test@test', 'Address cannot contain more than one @ character'],
  16. ['test', 'Address must contain one @ character'],
  17. ['@example.com', 'Address local part cannot be empty'],
  18. ['test@', 'Domain cannot be empty'],
  19. ['1234567890@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.com', 'Address too long'],
  20. ['1234567890123456789012345678901234567890123456789012345678901234567890@example.com', 'Address local part too long'],
  21. ['x..y@example.com', 'Address local part contains empty dot-separated segment'],
  22. ['x:y@example.com', 'Address local part contains invalid character'],
  23. ['ê:y@example.com', 'Address local part contains invalid character'],
  24. ['test@com', 'Domain lacks the minimum required number of segments'],
  25. ['test@x.no-such-tld', 'Domain uses forbidden TLD'],
  26. ['test@example..com', 'Domain contains empty dot-separated segment'],
  27. ['test@1234567890123456789012345678901234567890123456789012345678901234567890.com', 'Domain contains dot-separated segment that is too long'],
  28. ['test@example+.com', 'Domain contains invalid character', { tlds: false }],
  29. ['test@example.com_', 'Domain contains invalid tld character', { tlds: false }]
  30. ];
  31. for (let i = 0; i < tests.length; ++i) {
  32. const email = tests[i];
  33. const output = Address.email.analyze(email[0], email[2]);
  34. const result = email[1];
  35. if (!output ||
  36. output.error !== result) {
  37. console.log(i, email[0]);
  38. }
  39. expect(output.error).to.equal(result);
  40. }
  41. });
  42. it('validates options', () => {
  43. const tests = [
  44. ['test@example.com', 'Invalid options: tlds must be a boolean or an object', { tlds: 1 }],
  45. ['test@example.com', 'Invalid options: tlds.allow must be a Set object or true', { tlds: { allow: ['test'] } }],
  46. ['test@example.com', 'Invalid options: tlds.deny must be a Set object', { tlds: { deny: ['test'] } }],
  47. ['test@example.com', 'Invalid options: cannot specify both tlds.allow and tlds.deny lists', { tlds: { allow: new Set(), deny: new Set() } }],
  48. [1, 'Invalid input: value must be a string']
  49. ];
  50. for (let i = 0; i < tests.length; ++i) {
  51. const email = tests[i];
  52. expect(() => Address.email.analyze(email[0], email[2])).to.throw(email[1]);
  53. }
  54. });
  55. describe('validated TLD', () => {
  56. it('applies built-in list', () => {
  57. expect(Address.email.analyze('test@example.com')).to.not.exist();
  58. expect(Address.email.analyze('test@example.com', { tlds: true })).to.not.exist();
  59. expect(Address.email.analyze('test@example.com', { tlds: { allow: true } })).to.not.exist();
  60. });
  61. it('ignores built-in list', () => {
  62. expect(Address.email.analyze('test@example.invalid-top', { tlds: false })).to.not.exist();
  63. });
  64. it('denies listed tls', () => {
  65. expect(Address.email.analyze('test@example.com', { tlds: { deny: new Set(['test']) } })).to.not.exist();
  66. expect(Address.email.analyze('test@example.com', { tlds: { deny: new Set(['com']) } })).to.equal({ error: 'Domain uses forbidden TLD' });
  67. });
  68. });
  69. });
  70. describe('isValid()', () => {
  71. it('validates email', () => {
  72. // Tests adapted from https://github.com/hapijs/isemail
  73. // Copyright (c) 2008-2019, Eli Skeggs, Dominic Sayers, GlobeSherpa
  74. const tests = [
  75. ['\r', false],
  76. ['test', false],
  77. ['@', false],
  78. ['test@', false],
  79. ['test@io', false],
  80. ['test@io', true, { minDomainSegments: 1 }],
  81. ['@io', false],
  82. ['@iana.org', false],
  83. ['test@iana.org', true],
  84. ['test@nominet.org.uk', true],
  85. ['test@about.museum', true],
  86. ['a@iana.org', true],
  87. ['êjness@iana.org', true],
  88. ['ñoñó1234@iana.org', true],
  89. ['ñoñó1234@something.com', true],
  90. ['伊昭傑@郵件.商務', true, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  91. ['\ud801\udc37\ud852\udf62@iana.org', true],
  92. ['test.test@iana.org', true],
  93. ['.test@iana.org', false],
  94. ['test.@iana.org', false],
  95. ['test..iana.org', false],
  96. ['test_exa-mple.com', false],
  97. ['!#$%&`*+/=?^`{|}~@iana.org', true],
  98. ['test\\@test@iana.org', false],
  99. ['123@iana.org', true],
  100. ['test@123.com', true],
  101. ['test@iana.123', false],
  102. ['test@255.255.255.255', false],
  103. ['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org', true],
  104. ['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org', false],
  105. ['\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06@iana.org', false],
  106. ['test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm', false],
  107. ['test@\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06.org', true],
  108. ['test@abcdefghijklmnopqrstuvwxyzabcdefghijklmno\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06.org', false],
  109. ['test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com', false],
  110. ['test@mason-dixon.com', true],
  111. ['test@-iana.org', false],
  112. ['test@iana-.com', false],
  113. ['test@.iana.org', false],
  114. ['test@iana.org.', false],
  115. ['test@iana..com', false],
  116. ['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmno', false],
  117. ['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.\ud83d\ude06\ud83d\ude06\ud83d\ude06\ud83d\ude06', false],
  118. ['abcdef@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef.hijklmnopqrstuv', false],
  119. ['abcdef@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghi.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd\ud83d\ude06', false],
  120. ['abcdef@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghi.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\ud83d\ude06', false],
  121. ['a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijkl.hijk', false],
  122. ['a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijkl.\ud83d\ude06', false],
  123. ['\"\r', false],
  124. ['\"test\"@iana.org', false],
  125. ['\"\"@iana.org', false],
  126. ['\"\"\"@iana.org', false],
  127. ['\"\\a\"@iana.org', false],
  128. ['\"\\\"\"@iana.org', false],
  129. ['\"\\\"@iana.org', false],
  130. ['\"\\\\\"@iana.org', false],
  131. ['test\"@iana.org', false],
  132. ['\"test@iana.org', false],
  133. ['\"test\"test@iana.org', false],
  134. ['test\"text\"@iana.org', false],
  135. ['\"test\"\"test\"@iana.org', false],
  136. ['\"test\".\"test\"@iana.org', false],
  137. ['\"test\\ test\"@iana.org', false],
  138. ['\"test\".test@iana.org', false],
  139. ['\"test\u0000\"@iana.org', false],
  140. ['\"test\\\u0000\"@iana.org', false],
  141. ['\"test\r\n test\"@iana.org', false],
  142. ['\"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj\"@iana.org', false],
  143. ['\"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\\h\"@iana.org', false],
  144. ['test@[255.255.255.255]', false],
  145. ['test@a[255.255.255.255]', false],
  146. ['test@[255.255.255]', false],
  147. ['test@[255.255.255.255.255]', false],
  148. ['test@[255.255.255.256]', false],
  149. ['test@[1111:2222:3333:4444:5555:6666:7777:8888]', false],
  150. ['test@[IPv6:1111:2222:3333:4444:5555:6666:7777]', false],
  151. ['test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]', false],
  152. ['test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]', false],
  153. ['test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]', false],
  154. ['test@[IPv6:1111:2222:3333:4444:5555:6666::8888]', false],
  155. ['test@[IPv6:1111:2222:3333:4444:5555::8888]', false],
  156. ['test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]', false],
  157. ['test@[IPv6::3333:4444:5555:6666:7777:8888]', false],
  158. ['test@[IPv6:::3333:4444:5555:6666:7777:8888]', false],
  159. ['test@[IPv6:1111::4444:5555::8888]', false],
  160. ['test@[IPv6:::]', false],
  161. ['test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]', false],
  162. ['test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]', false],
  163. ['test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]', false],
  164. ['test@[IPv6:1111:2222:3333:4444::255.255.255.255]', false],
  165. ['test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]', false],
  166. ['test@[IPv6:1111:2222:3333:4444:::255.255.255.255]', false],
  167. ['test@[IPv6::255.255.255.255]', false],
  168. ['test@[255.255.255.255].local', false],
  169. ['test@local.[255.255.255.255]', false],
  170. ['test@local.[255.255.255.255].local', false],
  171. ['test@local.(comment)[255.255.255.255].local', false],
  172. ['test@local. [255.255.255.255].local', false],
  173. ['test@local.[255.255.255.255](comment).local', false],
  174. ['test@local.[255.255.255.255] .local', false],
  175. [' test @iana.org', false],
  176. ['test@ iana .com', false],
  177. ['test . test@iana.org', false],
  178. ['\r\n test@iana.org', false],
  179. ['\r\n \r\n test@iana.org', false],
  180. ['(\r', false],
  181. ['(comment)test@iana.org', false],
  182. ['((comment)test@iana.org', false],
  183. ['(comment(comment))test@iana.org', false],
  184. ['test@(comment)iana.org', false],
  185. ['test(comment)@iana.org', false],
  186. ['test(comment)test@iana.org', false],
  187. ['test@(comment)[255.255.255.255]', false],
  188. ['(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org', false],
  189. ['test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.com', false],
  190. ['(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrst', false],
  191. ['test@iana.org\n', false],
  192. ['xn--test@iana.org', true],
  193. ['test@iana.org-', false],
  194. ['\"test@iana.org', false],
  195. ['(test@iana.org', false],
  196. ['test@(iana.org', false],
  197. ['test@[1.2.3.4', false],
  198. ['\"test\\\"@iana.org', false],
  199. ['(comment\\)test@iana.org', false],
  200. ['test@iana.org(comment\\)', false],
  201. ['test@iana.org(comment\\', false],
  202. ['test@[RFC-5322-domain-literal]', false],
  203. ['test@[RFC-5322-郵件ñó-domain-literal]', false],
  204. ['test@[RFC-5322]-domain-literal]', false],
  205. ['test@[RFC-5322].domain-literal]', false],
  206. ['test@[RFC-5322-[domain-literal]', false],
  207. ['test@[€', false],
  208. ['test@[\u0007]', false],
  209. ['test@[RFC-5322-\\\u0007-domain-literal]', false],
  210. ['test@[RFC-5322-\\\t-domain-literal]', false],
  211. ['test@[RFC-5322-\\]-domain-literal]', false],
  212. ['test@[RFC-5322-\\郵-no-domain-literal]', false],
  213. ['test@[RFC-5322--domain-literal]', false],
  214. ['test@[RFC-5322-domain-literal\\]', false],
  215. ['test@[RFC-5322-domain-literal\\', false],
  216. ['test@[RFC 5322 domain literal]', false],
  217. ['test@[RFC-5322-domain-literal] (comment)', false],
  218. ['@iana.org', false],
  219. ['test@.org', false],
  220. ['\"\"@iana.org', false],
  221. ['\"€\"@iana.org', false],
  222. ['\"\\\"@iana.org', false],
  223. ['()test@iana.org', false],
  224. ['(€)test@iana.org', false],
  225. ['test@iana.org\r', false],
  226. ['\rtest@iana.org', false],
  227. ['\"\rtest\"@iana.org', false],
  228. ['(\r)test@iana.org', false],
  229. ['test@iana.org(\r)', false],
  230. ['test@<iana>.org', false],
  231. ['\ntest@iana.org', false],
  232. ['\"\n\"@iana.org', false],
  233. ['\"\\\n\"@iana.org', false],
  234. ['(\n)test@iana.org', false],
  235. ['\u0007@iana.org', false],
  236. ['test@\u0007.org', false],
  237. ['\"\u0007\"@iana.org', false],
  238. ['\"\\\u0007\"@iana.org', false],
  239. ['(\u0007)test@iana.org', false],
  240. ['\r\ntest@iana.org', false],
  241. ['\r\n \r\ntest@iana.org', false],
  242. [' \r\ntest@iana.org', false],
  243. [' \r\n test@iana.org', false],
  244. [' \r\n \r\ntest@iana.org', false],
  245. [' \r\n\r\ntest@iana.org', false],
  246. [' \r\n\r\n test@iana.org', false],
  247. ['test@iana.org\r\n ', false],
  248. ['test@iana.org\r\n \r\n ', false],
  249. ['test@iana.org\r\n', false],
  250. ['test@iana.org \r', false],
  251. ['test@iana.org\r\n \r\n', false],
  252. ['test@iana.org \r\n', false],
  253. ['test@iana.org \r\n ', false],
  254. ['test@iana.org \r\n \r\n', false],
  255. ['test@iana.org \r\n\r\n', false],
  256. ['test@iana.org \r\n\r\n ', false],
  257. ['test@iana. org', false],
  258. ['test@[\r', false],
  259. ['test@[\r\n', false],
  260. [' test@iana.org', false],
  261. ['test@iana.org ', false],
  262. ['test@[IPv6:1::2:]', false],
  263. ['\"test\\\u0094\"@iana.org', false],
  264. ['test@iana/icann.org', false],
  265. ['test@iana!icann.org', false],
  266. ['test@iana?icann.org', false],
  267. ['test@iana^icann.org', false],
  268. ['test@iana{icann}.org', false],
  269. ['test.(comment)test@iana.org', false],
  270. ['test@iana.(comment)org', false],
  271. ['test@iana(comment)iana.org', false],
  272. ['(comment\r\n comment)test@iana.org', false],
  273. ['test@org', true, { minDomainSegments: 1 }],
  274. ['test\ud800@invalid', false],
  275. ['\"\ud800\"@invalid', false],
  276. ['\"\\\ud800\"@invalid', false],
  277. ['(\ud800)thing@invalid', false],
  278. ['\"\\\ud800\"@invalid', false],
  279. ['test@\ud800\udfffñoñó郵件ñoñó郵件.郵件ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.noñó郵件.商務', true, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  280. ['test@\ud800\udfffñoñó郵件ñoñó郵件.郵件ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.noñó郵件ñoñó郵.商務', false, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  281. ['test@\ud800\udfffñoñó郵件ñoñó郵件.郵件ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.ñoñó郵件ñoñó郵件.oñó郵件ñoñó郵件ñoñó郵件.商務', false, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  282. ['test@ñoñoñó郵件\ud83d\ude06ñoñ.oñó郵件\uc138ñoñ.oñó郵件\u0644\u4eec\u010dñoñoñó郵件\u05dcño.ñoñó郵件\u092f\u672cñoñoñó郵件\uc138añoñ.oñó郵件\ud83d\ude06bc\uc138郵\ud83d\ude06ño.ñoñó郵件ñoñoñó郵件\ud83d\ude06ñoñoñó郵件\uc138ñoñ.oñó郵件\u0644\u4eecñoñoñó.郵件\ud83d\ude06ñoñoñó郵件郵\uc138ñoñoñó郵件\u0644\u4eecñoñoñó郵件.\ud83d\ude06ñoñoñó郵件郵\uc138\u0644\u4eec.郵件\ud83d\ude06ñoñoñó郵.件郵\uc138\u4eec\ud83d\ude06ñoñoñó件郵\uc138ñoñoñó郵件', false, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  283. ['test@ñoñó郵件ñoñó郵件ñoñó郵件ñoñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件ñoñó郵件.商務', false, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  284. ['\ud83d\ude06ñoñó郵件ñoñó郵件ñoñó\ud83d\ude06郵件ñoñoñó郵@\ud83d\ude06郵件ñoñó郵件ñoñó.\ud83d\ude06郵件ñoñó郵件ñoñó.\ud83d\ude06郵件ñoñó郵件ñoñó.郵件ñoñó郵件ñoñó\ud83d\ude06.郵件ñoñó郵件ñoñó.郵件ñoñó郵件.ñoñó郵件ñoñó.郵件ñoñó郵件.\ud83d\ude06郵件ñoñó郵件ñoñó.\ud83d\ude06郵件ñoñó郵件ñoñó.\ud83d\ude06商務.郵件ñoñó郵件ñoñó郵件.\ud83d\ude06商務.\ud83d\ude06商務.\ud83d\ude06商務', false, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  285. ['test@[\0', false],
  286. ['(\0)test@example.com', false],
  287. ['shouldbe@invalid', false],
  288. ['shouldbe@INVALID', false],
  289. ['shouldbe@example.com', true],
  290. ['shouldbe@example.COM', true],
  291. ['apple-touch-icon-60x60@2x.png', false],
  292. ['shouldbe@XN--UNUP4Y', true, { minDomainSegments: 1 }],
  293. ['shouldbe@xn--unup4y', true, { minDomainSegments: 1 }],
  294. ['shouldbe@\u6e38\u620f', true, { minDomainSegments: 1 }]
  295. ];
  296. for (let i = 0; i < tests.length; ++i) {
  297. const email = tests[i];
  298. const valid = Address.email.isValid(email[0], email[2]);
  299. const result = email[1];
  300. if (valid !== result) {
  301. const outcome = Address.email.analyze(email[0], email[2]);
  302. if (outcome) {
  303. console.log(i, email[0], outcome.error);
  304. }
  305. else {
  306. console.log(i, email[0]);
  307. }
  308. }
  309. expect(valid).to.equal(result);
  310. }
  311. });
  312. });
  313. });
  314. describe('domain', () => {
  315. describe('analyze()', () => {
  316. it('identifies error', () => {
  317. const tests = [
  318. ['', 'Domain must be a non-empty string'],
  319. ['êiana.org', 'Domain contains forbidden Unicode characters', { allowUnicode: false }],
  320. ['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.com', 'Domain too long'],
  321. ['com', 'Domain lacks the minimum required number of segments'],
  322. ['x.no-such-tld', 'Domain uses forbidden TLD'],
  323. ['example..com', 'Domain contains empty dot-separated segment'],
  324. ['1234567890123456789012345678901234567890123456789012345678901234567890.com', 'Domain contains dot-separated segment that is too long'],
  325. ['example+.com', 'Domain contains invalid character', { tlds: false }],
  326. ['example.com_', 'Domain contains invalid tld character', { tlds: false }]
  327. ];
  328. for (let i = 0; i < tests.length; ++i) {
  329. const domain = tests[i];
  330. const output = Address.domain.analyze(domain[0], domain[2]);
  331. const result = domain[1];
  332. if (!output ||
  333. output.error !== result) {
  334. console.log(i, domain[0]);
  335. }
  336. expect(output.error).to.equal(result);
  337. }
  338. });
  339. });
  340. describe('isValid()', () => {
  341. it('validates domain', () => {
  342. const tests = [
  343. ['\r', false],
  344. ['test', false],
  345. ['@', false],
  346. ['iana.org', true],
  347. ['nominet.org.uk', true],
  348. ['about.museum', true],
  349. ['x.商務', true, { tlds: { allow: new Set([Punycode.toASCII('商務')]) } }],
  350. ['iana.123', false],
  351. ['255.255.255.255', false],
  352. ['XN--UNUP4Y', true, { minDomainSegments: 1 }]
  353. ];
  354. for (let i = 0; i < tests.length; ++i) {
  355. const domain = tests[i];
  356. const valid = Address.domain.isValid(domain[0], domain[2]);
  357. const result = domain[1];
  358. if (valid !== result) {
  359. const outcome = Address.domain.analyze(domain[0], domain[2]);
  360. if (outcome) {
  361. console.log(i, domain[0], outcome.error);
  362. }
  363. else {
  364. console.log(i, domain[0]);
  365. }
  366. }
  367. expect(valid).to.equal(result);
  368. }
  369. });
  370. });
  371. });