/std/net/isemail.d

http://github.com/jcd/phobos · D · 2073 lines · 1777 code · 222 blank · 74 comment · 280 complexity · a1982edccdc56ec3ff1ce8370f71a4cd MD5 · raw file

Large files are truncated click here to view the full file

  1. /**
  2. * Validates an email address according to RFCs 5321, 5322 and others.
  3. *
  4. * Authors: Dominic Sayers <dominic@sayers.cc>, Jacob Carlborg
  5. * Copyright: Dominic Sayers, Jacob Carlborg 2008-.
  6. * Test schema documentation: Copyright © 2011, Daniel Marschall
  7. * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
  8. * Version: 3.0.13 - Version 3.0 of the original PHP implementation: $(LINK http://www.dominicsayers.com/isemail)
  9. *
  10. * Standards:
  11. * $(UL
  12. * $(LI RFC 5321)
  13. * $(LI RFC 5322)
  14. * )
  15. *
  16. * References:
  17. * $(UL
  18. * $(LI $(LINK http://www.dominicsayers.com/isemail))
  19. * $(LI $(LINK http://tools.ietf.org/html/rfc5321))
  20. * $(LI $(LINK http://tools.ietf.org/html/rfc5322))
  21. * )
  22. *
  23. * Source: $(PHOBOSSRC std/net/_isemail.d)
  24. */
  25. module std.net.isemail;
  26. import std.algorithm : cmp, equal, uniq, filter, contains = canFind;
  27. import std.range : ElementType;
  28. import std.array;
  29. import std.ascii;
  30. import std.conv;
  31. import std.exception : enforce;
  32. import std.regex;
  33. import std.string;
  34. import std.traits;
  35. import std.utf;
  36. import std.uni;
  37. /**
  38. * Check that an email address conforms to RFCs 5321, 5322 and others.
  39. *
  40. * As of Version 3.0, we are now distinguishing clearly between a Mailbox as defined
  41. * by RFC 5321 and an addr-spec as defined by RFC 5322. Depending on the context,
  42. * either can be regarded as a valid email address. The RFC 5321 Mailbox specification
  43. * is more restrictive (comments, white space and obsolete forms are not allowed).
  44. *
  45. * Note: The DNS check is currently not implemented.
  46. *
  47. * Params:
  48. * email = The email address to check
  49. * checkDNS = If CheckDns.yes then a DNS check for MX records will be made
  50. * errorLevel = Determines the boundary between valid and invalid addresses.
  51. * Status codes above this number will be returned as-is,
  52. * status codes below will be returned as EmailStatusCode.valid.
  53. * Thus the calling program can simply look for EmailStatusCode.valid
  54. * if it is only interested in whether an address is valid or not. The
  55. * $(D_PARAM errorLevel) will determine how "picky" isEmail() is about
  56. * the address.
  57. *
  58. * If omitted or passed as EmailStatusCode.none then isEmail() will
  59. * not perform any finer grained error checking and an address is
  60. * either considered valid or not. Email status code will either be
  61. * EmailStatusCode.valid or EmailStatusCode.error.
  62. *
  63. * Returns: an EmailStatus, indicating the status of the email address.
  64. */
  65. EmailStatus isEmail (Char) (const(Char)[] email, CheckDns checkDNS = CheckDns.no,
  66. EmailStatusCode errorLevel = EmailStatusCode.none) if (isSomeChar!(Char))
  67. {
  68. alias const(Char)[] tstring;
  69. enum defaultThreshold = 16;
  70. int threshold;
  71. bool diagnose;
  72. if (errorLevel == EmailStatusCode.any || errorLevel == EmailStatusCode.none)
  73. {
  74. threshold = EmailStatusCode.valid;
  75. diagnose = errorLevel == EmailStatusCode.any;
  76. }
  77. else
  78. {
  79. diagnose = true;
  80. switch (errorLevel)
  81. {
  82. case EmailStatusCode.warning: threshold = defaultThreshold; break;
  83. case EmailStatusCode.error: threshold = EmailStatusCode.valid; break;
  84. default: threshold = errorLevel;
  85. }
  86. }
  87. auto returnStatus = [EmailStatusCode.valid];
  88. auto context = EmailPart.componentLocalPart;
  89. auto contextStack = [context];
  90. auto contextPrior = context;
  91. tstring token = "";
  92. tstring tokenPrior = "";
  93. tstring[EmailPart] parseData = [EmailPart.componentLocalPart : "", EmailPart.componentDomain : ""];
  94. tstring[][EmailPart] atomList = [EmailPart.componentLocalPart : [""], EmailPart.componentDomain : [""]];
  95. auto elementCount = 0;
  96. auto elementLength = 0;
  97. auto hyphenFlag = false;
  98. auto endOrDie = false;
  99. auto crlfCount = int.min; // int.min == not defined
  100. foreach (ref i, e ; email)
  101. {
  102. token = email.get(i, e);
  103. switch (context)
  104. {
  105. case EmailPart.componentLocalPart:
  106. switch (token)
  107. {
  108. case Token.openParenthesis:
  109. if (elementLength == 0)
  110. returnStatus ~= elementCount == 0 ? EmailStatusCode.comment :
  111. EmailStatusCode.deprecatedComment;
  112. else
  113. {
  114. returnStatus ~= EmailStatusCode.comment;
  115. endOrDie = true;
  116. }
  117. contextStack ~= context;
  118. context = EmailPart.contextComment;
  119. break;
  120. case Token.dot:
  121. if (elementLength == 0)
  122. returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart :
  123. EmailStatusCode.errorConsecutiveDots;
  124. else
  125. {
  126. if (endOrDie)
  127. returnStatus ~= EmailStatusCode.deprecatedLocalPart;
  128. }
  129. endOrDie = false;
  130. elementLength = 0;
  131. elementCount++;
  132. parseData[EmailPart.componentLocalPart] ~= token;
  133. if (elementCount >= atomList[EmailPart.componentLocalPart].length)
  134. atomList[EmailPart.componentLocalPart] ~= "";
  135. else
  136. atomList[EmailPart.componentLocalPart][elementCount] = "";
  137. break;
  138. case Token.doubleQuote:
  139. if (elementLength == 0)
  140. {
  141. returnStatus ~= elementCount == 0 ? EmailStatusCode.rfc5321QuotedString :
  142. EmailStatusCode.deprecatedLocalPart;
  143. parseData[EmailPart.componentLocalPart] ~= token;
  144. atomList[EmailPart.componentLocalPart][elementCount] ~= token;
  145. elementLength++;
  146. endOrDie = true;
  147. contextStack ~= context;
  148. context = EmailPart.contextQuotedString;
  149. }
  150. else
  151. returnStatus ~= EmailStatusCode.errorExpectingText;
  152. break;
  153. case Token.cr:
  154. case Token.space:
  155. case Token.tab:
  156. if ((token == Token.cr) && ((++i == email.length) || (email.get(i, e) != Token.lf)))
  157. {
  158. returnStatus ~= EmailStatusCode.errorCrNoLf;
  159. break;
  160. }
  161. if (elementLength == 0)
  162. returnStatus ~= elementCount == 0 ? EmailStatusCode.foldingWhitespace :
  163. EmailStatusCode.deprecatedFoldingWhitespace;
  164. else
  165. endOrDie = true;
  166. contextStack ~= context;
  167. context = EmailPart.contextFoldingWhitespace;
  168. tokenPrior = token;
  169. break;
  170. case Token.at:
  171. enforce(contextStack.length == 1, "Unexpected item on context stack");
  172. if (parseData[EmailPart.componentLocalPart] == "")
  173. returnStatus ~= EmailStatusCode.errorNoLocalPart;
  174. else if (elementLength == 0)
  175. returnStatus ~= EmailStatusCode.errorDotEnd;
  176. else if (parseData[EmailPart.componentLocalPart].length > 64)
  177. returnStatus ~= EmailStatusCode.rfc5322LocalTooLong;
  178. else if (contextPrior == EmailPart.contextComment ||
  179. contextPrior == EmailPart.contextFoldingWhitespace)
  180. returnStatus ~= EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt;
  181. context = EmailPart.componentDomain;
  182. contextStack = [context];
  183. elementCount = 0;
  184. elementLength = 0;
  185. endOrDie = false;
  186. break;
  187. default:
  188. if (endOrDie)
  189. {
  190. switch (contextPrior)
  191. {
  192. case EmailPart.contextComment:
  193. case EmailPart.contextFoldingWhitespace:
  194. returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace;
  195. break;
  196. case EmailPart.contextQuotedString:
  197. returnStatus ~= EmailStatusCode.errorTextAfterQuotedString;
  198. break;
  199. default:
  200. throw new Exception("More text found where none is allowed, but unrecognised prior "
  201. "context: " ~ to!(string)(contextPrior));
  202. }
  203. }
  204. else
  205. {
  206. contextPrior = context;
  207. auto c = token.front;
  208. if (c < '!' || c > '~' || c == '\n' || Token.specials.contains(token))
  209. returnStatus ~= EmailStatusCode.errorExpectingText;
  210. parseData[EmailPart.componentLocalPart] ~= token;
  211. atomList[EmailPart.componentLocalPart][elementCount] ~= token;
  212. elementLength++;
  213. }
  214. }
  215. break;
  216. case EmailPart.componentDomain:
  217. switch (token)
  218. {
  219. case Token.openParenthesis:
  220. if (elementLength == 0)
  221. returnStatus ~= elementCount == 0 ? EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt
  222. : EmailStatusCode.deprecatedComment;
  223. else
  224. {
  225. returnStatus ~= EmailStatusCode.comment;
  226. endOrDie = true;
  227. }
  228. contextStack ~= context;
  229. context = EmailPart.contextComment;
  230. break;
  231. case Token.dot:
  232. if (elementLength == 0)
  233. returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart :
  234. EmailStatusCode.errorConsecutiveDots;
  235. else if (hyphenFlag)
  236. returnStatus ~= EmailStatusCode.errorDomainHyphenEnd;
  237. else
  238. {
  239. if (elementLength > 63)
  240. returnStatus ~= EmailStatusCode.rfc5322LabelTooLong;
  241. }
  242. endOrDie = false;
  243. elementLength = 0,
  244. elementCount++;
  245. //atomList[EmailPart.componentDomain][elementCount] = "";
  246. atomList[EmailPart.componentDomain] ~= "";
  247. parseData[EmailPart.componentDomain] ~= token;
  248. break;
  249. case Token.openBracket:
  250. if (parseData[EmailPart.componentDomain] == "")
  251. {
  252. endOrDie = true;
  253. elementLength++;
  254. contextStack ~= context;
  255. context = EmailPart.componentLiteral;
  256. parseData[EmailPart.componentDomain] ~= token;
  257. atomList[EmailPart.componentDomain][elementCount] ~= token;
  258. parseData[EmailPart.componentLiteral] = "";
  259. }
  260. else
  261. returnStatus ~= EmailStatusCode.errorExpectingText;
  262. break;
  263. case Token.cr:
  264. case Token.space:
  265. case Token.tab:
  266. if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
  267. {
  268. returnStatus ~= EmailStatusCode.errorCrNoLf;
  269. break;
  270. }
  271. if (elementLength == 0)
  272. returnStatus ~= elementCount == 0 ? EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt
  273. : EmailStatusCode.deprecatedFoldingWhitespace;
  274. else
  275. {
  276. returnStatus ~= EmailStatusCode.foldingWhitespace;
  277. endOrDie = true;
  278. }
  279. contextStack ~= context;
  280. context = EmailPart.contextFoldingWhitespace;
  281. tokenPrior = token;
  282. break;
  283. default:
  284. if (endOrDie)
  285. {
  286. switch (contextPrior)
  287. {
  288. case EmailPart.contextComment:
  289. case EmailPart.contextFoldingWhitespace:
  290. returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace;
  291. break;
  292. case EmailPart.componentLiteral:
  293. returnStatus ~= EmailStatusCode.errorTextAfterDomainLiteral;
  294. break;
  295. default:
  296. throw new Exception("More text found where none is allowed, but unrecognised prior "
  297. "context: " ~ to!(string)(contextPrior));
  298. }
  299. }
  300. auto c = token.front;
  301. hyphenFlag = false;
  302. if (c < '!' || c > '~' || Token.specials.contains(token))
  303. returnStatus ~= EmailStatusCode.errorExpectingText;
  304. else if (token == Token.hyphen)
  305. {
  306. if (elementLength == 0)
  307. returnStatus ~= EmailStatusCode.errorDomainHyphenStart;
  308. hyphenFlag = true;
  309. }
  310. else if (!((c > '/' && c < ':') || (c > '@' && c < '[') || (c > '`' && c < '{')))
  311. returnStatus ~= EmailStatusCode.rfc5322Domain;
  312. parseData[EmailPart.componentDomain] ~= token;
  313. atomList[EmailPart.componentDomain][elementCount] ~= token;
  314. elementLength++;
  315. }
  316. break;
  317. case EmailPart.componentLiteral:
  318. switch (token)
  319. {
  320. case Token.closeBracket:
  321. if (returnStatus.max() < EmailStatusCode.deprecated_)
  322. {
  323. auto maxGroups = 8;
  324. size_t index = -1;
  325. auto addressLiteral = parseData[EmailPart.componentLiteral];
  326. enum regexStr = `\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}`~
  327. `(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`;
  328. auto matchesIp = array(addressLiteral.match(regex!tstring(regexStr)).captures);
  329. if (!matchesIp.empty)
  330. {
  331. index = addressLiteral.lastIndexOf(matchesIp.front);
  332. if (index != 0)
  333. addressLiteral = addressLiteral.substr(0, index) ~ "0:0";
  334. }
  335. if (index == 0)
  336. returnStatus ~= EmailStatusCode.rfc5321AddressLiteral;
  337. else if (addressLiteral.compareFirstN(Token.ipV6Tag, 5, true))
  338. returnStatus ~= EmailStatusCode.rfc5322DomainLiteral;
  339. else
  340. {
  341. auto ipV6 = addressLiteral.substr(5);
  342. matchesIp = ipV6.split(Token.colon);
  343. auto groupCount = matchesIp.length;
  344. index = ipV6.indexOf(Token.doubleColon);
  345. if (index == -1)
  346. {
  347. if (groupCount != maxGroups)
  348. returnStatus ~= EmailStatusCode.rfc5322IpV6GroupCount;
  349. }
  350. else
  351. {
  352. if (index != ipV6.lastIndexOf(Token.doubleColon))
  353. returnStatus ~= EmailStatusCode.rfc5322IpV6TooManyDoubleColons;
  354. else
  355. {
  356. if (index == 0 || index == (ipV6.length - 2))
  357. maxGroups++;
  358. if (groupCount > maxGroups)
  359. returnStatus ~= EmailStatusCode.rfc5322IpV6MaxGroups;
  360. else if (groupCount == maxGroups)
  361. returnStatus ~= EmailStatusCode.rfc5321IpV6Deprecated;
  362. }
  363. }
  364. if (ipV6.substr(0, 1) == Token.colon && ipV6.substr(1, 1) != Token.colon)
  365. returnStatus ~= EmailStatusCode.rfc5322IpV6ColonStart;
  366. else if (ipV6.substr(-1) == Token.colon && ipV6.substr(-2, -1) != Token.colon)
  367. returnStatus ~= EmailStatusCode.rfc5322IpV6ColonEnd;
  368. else if (!matchesIp.grep(regex!(tstring)(`^[0-9A-Fa-f]{0,4}$`), true).empty)
  369. returnStatus ~= EmailStatusCode.rfc5322IpV6BadChar;
  370. else
  371. returnStatus ~= EmailStatusCode.rfc5321AddressLiteral;
  372. }
  373. }
  374. else
  375. returnStatus ~= EmailStatusCode.rfc5322DomainLiteral;
  376. parseData[EmailPart.componentDomain] ~= token;
  377. atomList[EmailPart.componentDomain][elementCount] ~= token;
  378. elementLength++;
  379. contextPrior = context;
  380. context = contextStack.pop();
  381. break;
  382. case Token.backslash:
  383. returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText;
  384. contextStack ~= context;
  385. context = EmailPart.contextQuotedPair;
  386. break;
  387. case Token.cr:
  388. case Token.space:
  389. case Token.tab:
  390. if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
  391. {
  392. returnStatus ~= EmailStatusCode.errorCrNoLf;
  393. break;
  394. }
  395. returnStatus ~= EmailStatusCode.foldingWhitespace;
  396. contextStack ~= context;
  397. context = EmailPart.contextFoldingWhitespace;
  398. tokenPrior = token;
  399. break;
  400. default:
  401. auto c = token.front;
  402. if (c > AsciiToken.delete_ || c == '\0' || token == Token.openBracket)
  403. {
  404. returnStatus ~= EmailStatusCode.errorExpectingDomainText;
  405. break;
  406. }
  407. else if (c < '!' || c == AsciiToken.delete_ )
  408. returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText;
  409. parseData[EmailPart.componentLiteral] ~= token;
  410. parseData[EmailPart.componentDomain] ~= token;
  411. atomList[EmailPart.componentDomain][elementCount] ~= token;
  412. elementLength++;
  413. }
  414. break;
  415. case EmailPart.contextQuotedString:
  416. switch (token)
  417. {
  418. case Token.backslash:
  419. contextStack ~= context;
  420. context = EmailPart.contextQuotedPair;
  421. break;
  422. case Token.cr:
  423. case Token.tab:
  424. if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
  425. {
  426. returnStatus ~= EmailStatusCode.errorCrNoLf;
  427. break;
  428. }
  429. parseData[EmailPart.componentLocalPart] ~= Token.space;
  430. atomList[EmailPart.componentLocalPart][elementCount] ~= Token.space;
  431. elementLength++;
  432. returnStatus ~= EmailStatusCode.foldingWhitespace;
  433. contextStack ~= context;
  434. context = EmailPart.contextFoldingWhitespace;
  435. tokenPrior = token;
  436. break;
  437. case Token.doubleQuote:
  438. parseData[EmailPart.componentLocalPart] ~= token;
  439. atomList[EmailPart.componentLocalPart][elementCount] ~= token;
  440. elementLength++;
  441. contextPrior = context;
  442. context = contextStack.pop();
  443. break;
  444. default:
  445. auto c = token.front;
  446. if (c > AsciiToken.delete_ || c == '\0' || c == '\n')
  447. returnStatus ~= EmailStatusCode.errorExpectingQuotedText;
  448. else if (c < ' ' || c == AsciiToken.delete_)
  449. returnStatus ~= EmailStatusCode.deprecatedQuotedText;
  450. parseData[EmailPart.componentLocalPart] ~= token;
  451. atomList[EmailPart.componentLocalPart][elementCount] ~= token;
  452. elementLength++;
  453. }
  454. break;
  455. case EmailPart.contextQuotedPair:
  456. auto c = token.front;
  457. if (c > AsciiToken.delete_)
  458. returnStatus ~= EmailStatusCode.errorExpectingQuotedPair;
  459. else if (c < AsciiToken.unitSeparator && c != AsciiToken.horizontalTab || c == AsciiToken.delete_)
  460. returnStatus ~= EmailStatusCode.deprecatedQuotedPair;
  461. contextPrior = context;
  462. context = contextStack.pop();
  463. token = Token.backslash ~ token;
  464. switch (context)
  465. {
  466. case EmailPart.contextComment: break;
  467. case EmailPart.contextQuotedString:
  468. parseData[EmailPart.componentLocalPart] ~= token;
  469. atomList[EmailPart.componentLocalPart][elementCount] ~= token;
  470. elementLength += 2;
  471. break;
  472. case EmailPart.componentLiteral:
  473. parseData[EmailPart.componentDomain] ~= token;
  474. atomList[EmailPart.componentDomain][elementCount] ~= token;
  475. elementLength += 2;
  476. break;
  477. default:
  478. throw new Exception("Quoted pair logic invoked in an invalid context: " ~ to!(string)(context));
  479. }
  480. break;
  481. case EmailPart.contextComment:
  482. switch (token)
  483. {
  484. case Token.openParenthesis:
  485. contextStack ~= context;
  486. context = EmailPart.contextComment;
  487. break;
  488. case Token.closeParenthesis:
  489. contextPrior = context;
  490. context = contextStack.pop();
  491. break;
  492. case Token.backslash:
  493. contextStack ~= context;
  494. context = EmailPart.contextQuotedPair;
  495. break;
  496. case Token.cr:
  497. case Token.space:
  498. case Token.tab:
  499. if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
  500. {
  501. returnStatus ~= EmailStatusCode.errorCrNoLf;
  502. break;
  503. }
  504. returnStatus ~= EmailStatusCode.foldingWhitespace;
  505. contextStack ~= context;
  506. context = EmailPart.contextFoldingWhitespace;
  507. tokenPrior = token;
  508. break;
  509. default:
  510. auto c = token.front;
  511. if (c > AsciiToken.delete_ || c == '\0' || c == '\n')
  512. {
  513. returnStatus ~= EmailStatusCode.errorExpectingCommentText;
  514. break;
  515. }
  516. else if (c < ' ' || c == AsciiToken.delete_)
  517. returnStatus ~= EmailStatusCode.deprecatedCommentText;
  518. }
  519. break;
  520. case EmailPart.contextFoldingWhitespace:
  521. if (tokenPrior == Token.cr)
  522. {
  523. if (token == Token.cr)
  524. {
  525. returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrflX2;
  526. break;
  527. }
  528. if (crlfCount != int.min) // int.min == not defined
  529. {
  530. if (++crlfCount > 1)
  531. returnStatus ~= EmailStatusCode.deprecatedFoldingWhitespace;
  532. }
  533. else
  534. crlfCount = 1;
  535. }
  536. switch (token)
  537. {
  538. case Token.cr:
  539. if (++i == email.length || email.get(i, e) != Token.lf)
  540. returnStatus ~= EmailStatusCode.errorCrNoLf;
  541. break;
  542. case Token.space:
  543. case Token.tab:
  544. break;
  545. default:
  546. if (tokenPrior == Token.cr)
  547. {
  548. returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd;
  549. break;
  550. }
  551. crlfCount = int.min; // int.min == not defined
  552. contextPrior = context;
  553. context = contextStack.pop();
  554. i--;
  555. break;
  556. }
  557. tokenPrior = token;
  558. break;
  559. default:
  560. throw new Exception("Unkown context: " ~ to!(string)(context));
  561. }
  562. if (returnStatus.max() > EmailStatusCode.rfc5322)
  563. break;
  564. }
  565. if (returnStatus.max() < EmailStatusCode.rfc5322)
  566. {
  567. if (context == EmailPart.contextQuotedString)
  568. returnStatus ~= EmailStatusCode.errorUnclosedQuotedString;
  569. else if (context == EmailPart.contextQuotedPair)
  570. returnStatus ~= EmailStatusCode.errorBackslashEnd;
  571. else if (context == EmailPart.contextComment)
  572. returnStatus ~= EmailStatusCode.errorUnclosedComment;
  573. else if (context == EmailPart.componentLiteral)
  574. returnStatus ~= EmailStatusCode.errorUnclosedDomainLiteral;
  575. else if (token == Token.cr)
  576. returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd;
  577. else if (parseData[EmailPart.componentDomain] == "")
  578. returnStatus ~= EmailStatusCode.errorNoDomain;
  579. else if (elementLength == 0)
  580. returnStatus ~= EmailStatusCode.errorDotEnd;
  581. else if (hyphenFlag)
  582. returnStatus ~= EmailStatusCode.errorDomainHyphenEnd;
  583. else if (parseData[EmailPart.componentDomain].length > 255)
  584. returnStatus ~= EmailStatusCode.rfc5322DomainTooLong;
  585. else if ((parseData[EmailPart.componentLocalPart] ~ Token.at ~ parseData[EmailPart.componentDomain]).length >
  586. 254)
  587. returnStatus ~= EmailStatusCode.rfc5322TooLong;
  588. else if (elementLength > 63)
  589. returnStatus ~= EmailStatusCode.rfc5322LabelTooLong;
  590. }
  591. auto dnsChecked = false;
  592. if (checkDNS == CheckDns.yes && returnStatus.max() < EmailStatusCode.dnsWarning)
  593. {
  594. assert(false, "DNS check is currently not implemented");
  595. }
  596. if (!dnsChecked && returnStatus.max() < EmailStatusCode.dnsWarning)
  597. {
  598. if (elementCount == 0)
  599. returnStatus ~= EmailStatusCode.rfc5321TopLevelDomain;
  600. if (isNumeric(atomList[EmailPart.componentDomain][elementCount].front))
  601. returnStatus ~= EmailStatusCode.rfc5321TopLevelDomainNumeric;
  602. }
  603. returnStatus = array(uniq(returnStatus));
  604. auto finalStatus = returnStatus.max();
  605. if (returnStatus.length != 1)
  606. returnStatus.popFront();
  607. parseData[EmailPart.status] = to!(tstring)(returnStatus);
  608. if (finalStatus < threshold)
  609. finalStatus = EmailStatusCode.valid;
  610. if (!diagnose)
  611. finalStatus = finalStatus < threshold ? EmailStatusCode.valid : EmailStatusCode.error;
  612. auto valid = finalStatus == EmailStatusCode.valid;
  613. tstring localPart = "";
  614. tstring domainPart = "";
  615. if (auto value = EmailPart.componentLocalPart in parseData)
  616. localPart = *value;
  617. if (auto value = EmailPart.componentDomain in parseData)
  618. domainPart = *value;
  619. return EmailStatus(valid, to!(string)(localPart), to!(string)(domainPart), finalStatus);
  620. }
  621. unittest
  622. {
  623. assert(``.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
  624. assert(`test`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
  625. assert(`@`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart);
  626. assert(`test@`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
  627. // assert(`test@io`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid,
  628. // `io. currently has an MX-record (Feb 2011). Some DNS setups seem to find it, some don't.`
  629. // ` If you don't see the MX for io. then try setting your DNS server to 8.8.8.8 (the Google DNS server)`);
  630. assert(`@io`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart,
  631. `io. currently has an MX-record (Feb 2011)`);
  632. assert(`@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart);
  633. assert(`test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  634. assert(`test@nominet.org.uk`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  635. assert(`test@about.museum`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  636. assert(`a@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  637. //assert(`test@e.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
  638. // DNS check is currently not implemented
  639. //assert(`test@iana.a`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
  640. // DNS check is currently not implemented
  641. assert(`test.test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  642. assert(`.test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart);
  643. assert(`test.@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd);
  644. assert(`test..iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  645. EmailStatusCode.errorConsecutiveDots);
  646. assert(`test_exa-mple.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
  647. assert("!#$%&`*+/=?^`{|}~@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  648. assert(`test\@test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  649. EmailStatusCode.errorExpectingText);
  650. assert(`123@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  651. assert(`test@123.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  652. assert(`test@iana.123`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  653. EmailStatusCode.rfc5321TopLevelDomainNumeric);
  654. assert(`test@255.255.255.255`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  655. EmailStatusCode.rfc5321TopLevelDomainNumeric);
  656. assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(CheckDns.no,
  657. EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  658. assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org`.isEmail(CheckDns.no,
  659. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong);
  660. // assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(CheckDns.no,
  661. // EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
  662. // DNS check is currently not implemented
  663. assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com`.isEmail(CheckDns.no,
  664. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LabelTooLong);
  665. assert(`test@mason-dixon.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  666. assert(`test@-iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  667. EmailStatusCode.errorDomainHyphenStart);
  668. assert(`test@iana-.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  669. EmailStatusCode.errorDomainHyphenEnd);
  670. assert(`test@g--a.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
  671. //assert(`test@iana.co-uk`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  672. //EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented
  673. assert(`test@.iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart);
  674. assert(`test@iana.org.`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd);
  675. assert(`test@iana..com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  676. EmailStatusCode.errorConsecutiveDots);
  677. //assert(`a@a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z`
  678. // `.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z`
  679. // `.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  680. // EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented
  681. // assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`
  682. // `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`
  683. // `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi`.isEmail(CheckDns.no,
  684. // EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
  685. // DNS check is currently not implemented
  686. assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`
  687. `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`
  688. `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij`.isEmail(CheckDns.no,
  689. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong);
  690. assert(`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`
  691. `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`
  692. `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij`.isEmail(CheckDns.no,
  693. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong);
  694. assert(`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`
  695. `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`
  696. `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk`.isEmail(CheckDns.no,
  697. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322DomainTooLong);
  698. assert(`"test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  699. EmailStatusCode.rfc5321QuotedString);
  700. assert(`""@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
  701. assert(`"""@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
  702. assert(`"\a"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
  703. assert(`"\""@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
  704. assert(`"\"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  705. EmailStatusCode.errorUnclosedQuotedString);
  706. assert(`"\\"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
  707. assert(`test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
  708. assert(`"test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  709. EmailStatusCode.errorUnclosedQuotedString);
  710. assert(`"test"test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  711. EmailStatusCode.errorTextAfterQuotedString);
  712. assert(`test"text"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  713. EmailStatusCode.errorExpectingText);
  714. assert(`"test""test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  715. EmailStatusCode.errorExpectingText);
  716. assert(`"test"."test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  717. EmailStatusCode.deprecatedLocalPart);
  718. assert(`"test\ test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  719. EmailStatusCode.rfc5321QuotedString);
  720. assert(`"test".test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  721. EmailStatusCode.deprecatedLocalPart);
  722. assert("\"test\u0000\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  723. EmailStatusCode.errorExpectingQuotedText);
  724. assert("\"test\\\u0000\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  725. EmailStatusCode.deprecatedQuotedPair);
  726. assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj"@iana.org`.isEmail(CheckDns.no,
  727. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong,
  728. `Quotes are still part of the length restriction`);
  729. assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\h"@iana.org`.isEmail(CheckDns.no,
  730. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong,
  731. `Quoted pair is still part of the length restriction`);
  732. assert(`test@[255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  733. EmailStatusCode.rfc5321AddressLiteral);
  734. assert(`test@a[255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  735. EmailStatusCode.errorExpectingText);
  736. assert(`test@[255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  737. EmailStatusCode.rfc5322DomainLiteral);
  738. assert(`test@[255.255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  739. EmailStatusCode.rfc5322DomainLiteral);
  740. assert(`test@[255.255.255.256]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  741. EmailStatusCode.rfc5322DomainLiteral);
  742. assert(`test@[1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  743. EmailStatusCode.rfc5322DomainLiteral);
  744. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  745. EmailStatusCode.rfc5322IpV6GroupCount);
  746. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode
  747. == EmailStatusCode.rfc5321AddressLiteral);
  748. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]`.isEmail(CheckDns.no,
  749. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
  750. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]`.isEmail(CheckDns.no,
  751. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6BadChar);
  752. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(CheckDns.no,
  753. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321IpV6Deprecated);
  754. assert(`test@[IPv6:1111:2222:3333:4444:5555::8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  755. EmailStatusCode.rfc5321AddressLiteral);
  756. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]`.isEmail(CheckDns.no,
  757. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups);
  758. assert(`test@[IPv6::3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  759. EmailStatusCode.rfc5322IpV6ColonStart);
  760. assert(`test@[IPv6:::3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  761. EmailStatusCode.rfc5321AddressLiteral);
  762. assert(`test@[IPv6:1111::4444:5555::8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  763. EmailStatusCode.rfc5322IpV6TooManyDoubleColons);
  764. assert(`test@[IPv6:::]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  765. EmailStatusCode.rfc5321AddressLiteral);
  766. assert(`test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]`.isEmail(CheckDns.no,
  767. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
  768. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]`.isEmail(CheckDns.no,
  769. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral);
  770. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]`.isEmail(CheckDns.no,
  771. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
  772. assert(`test@[IPv6:1111:2222:3333:4444::255.255.255.255]`.isEmail(CheckDns.no,
  773. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral);
  774. assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]`.isEmail(CheckDns.no,
  775. EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups);
  776. assert(`test@[IPv6:1111:2222:3333:4444:::255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode
  777. == EmailStatusCode.rfc5322IpV6TooManyDoubleColons);
  778. assert(`test@[IPv6::255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  779. EmailStatusCode.rfc5322IpV6ColonStart);
  780. assert(` test @iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  781. EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
  782. assert(`test@ iana .com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  783. EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
  784. assert(`test . test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  785. EmailStatusCode.deprecatedFoldingWhitespace);
  786. assert("\u000D\u000A test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  787. EmailStatusCode.foldingWhitespace, `Folding whitespace`);
  788. assert("\u000D\u000A \u000D\u000A test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  789. EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP`
  790. ` -- only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`);
  791. assert(`(comment)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.comment);
  792. assert(`((comment)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  793. EmailStatusCode.errorUnclosedComment);
  794. assert(`(comment(comment))test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  795. EmailStatusCode.comment);
  796. assert(`test@(comment)iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  797. EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
  798. assert(`test(comment)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  799. EmailStatusCode.errorTextAfterCommentFoldingWhitespace);
  800. assert(`test@(comment)[255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  801. EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
  802. assert(`(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(CheckDns.no,
  803. EmailStatusCode.any).statusCode == EmailStatusCode.comment);
  804. assert(`test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(CheckDns.no,
  805. EmailStatusCode.any).statusCode == EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
  806. assert(`(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyz`
  807. `abcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.`
  808. `abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu`.isEmail(CheckDns.no,
  809. EmailStatusCode.any).statusCode == EmailStatusCode.comment);
  810. assert("test@iana.org\u000A".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  811. EmailStatusCode.errorExpectingText);
  812. assert(`test@xn--hxajbheg2az3al.xn--jxalpdlp`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  813. EmailStatusCode.valid, `A valid IDN from ICANN's <a href="http://idn.icann.org/#The_example.test_names">`
  814. `IDN TLD evaluation gateway</a>`);
  815. assert(`xn--test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid,
  816. `RFC 3490: "unless the email standards are revised to invite the use of IDNA for local parts, a domain label`
  817. ` that holds the local part of an email address SHOULD NOT begin with the ACE prefix, and even if it does,`
  818. ` it is to be interpreted literally as a local part that happens to begin with the ACE prefix"`);
  819. assert(`test@iana.org-`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  820. EmailStatusCode.errorDomainHyphenEnd);
  821. assert(`"test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  822. EmailStatusCode.errorUnclosedQuotedString);
  823. assert(`(test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  824. EmailStatusCode.errorUnclosedComment);
  825. assert(`test@(iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  826. EmailStatusCode.errorUnclosedComment);
  827. assert(`test@[1.2.3.4`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  828. EmailStatusCode.errorUnclosedDomainLiteral);
  829. assert(`"test\"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  830. EmailStatusCode.errorUnclosedQuotedString);
  831. assert(`(comment\)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  832. EmailStatusCode.errorUnclosedComment);
  833. assert(`test@iana.org(comment\)`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  834. EmailStatusCode.errorUnclosedComment);
  835. assert(`test@iana.org(comment\`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  836. EmailStatusCode.errorBackslashEnd);
  837. assert(`test@[RFC-5322-domain-literal]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  838. EmailStatusCode.rfc5322DomainLiteral);
  839. assert(`test@[RFC-5322]-domain-literal]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
  840. EmailStatusCode.errorTextAfterDomainLiteral);
  841. assert(`test@[RFC-5322-…