1 /** 2 * Validates an email address according to RFCs 5321, 5322 and others. 3 * 4 * Authors: Dominic Sayers $(LT)dominic@sayers.cc$(GT), 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 27 // FIXME 28 import std.range.primitives; // : ElementType; 29 import std.regex; 30 import std.traits; 31 import std.typecons : Flag, Yes, No; 32 33 /** 34 * Check that an email address conforms to RFCs 5321, 5322 and others. 35 * 36 * Distinguishes between a Mailbox as defined by RFC 5321 and an addr-spec as 37 * defined by RFC 5322. Depending on the context, either can be regarded as a 38 * valid email address. 39 * 40 * Note: The DNS check is currently not implemented. 41 * 42 * Params: 43 * email = The email address to check 44 * checkDNS = If $(D Yes.checkDns) then a DNS check for MX records will be made 45 * errorLevel = Determines the boundary between valid and invalid addresses. 46 * Status codes above this number will be returned as-is, 47 * status codes below will be returned as EmailStatusCode.valid. 48 * Thus the calling program can simply look for EmailStatusCode.valid 49 * if it is only interested in whether an address is valid or not. The 50 * $(D_PARAM errorLevel) will determine how "picky" isEmail() is about 51 * the address. 52 * 53 * If omitted or passed as EmailStatusCode.none then isEmail() will 54 * not perform any finer grained error checking and an address is 55 * either considered valid or not. Email status code will either be 56 * EmailStatusCode.valid or EmailStatusCode.error. 57 * 58 * Returns: 59 * An $(LREF EmailStatus), indicating the status of the email address. 60 */ 61 EmailStatus isEmail(Char)(const(Char)[] email, CheckDns checkDNS = No.checkDns, 62 EmailStatusCode errorLevel = EmailStatusCode.none) 63 if (isSomeChar!(Char)) 64 { 65 import std.algorithm.iteration : uniq, filter, map; 66 import std.algorithm.searching : canFind, maxElement; 67 import std.array : array, split; 68 import std.conv : to; 69 import std.exception : enforce; 70 import std.string : indexOf, lastIndexOf; 71 import std.uni : isNumber; 72 73 alias tstring = const(Char)[]; 74 alias Token = TokenImpl!(Char); 75 76 static ipRegex = ctRegex!(`\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}`~ 77 `(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`.to!(const(Char)[])); 78 static fourChars = ctRegex!(`^[0-9A-Fa-f]{0,4}$`.to!(const(Char)[])); 79 80 enum defaultThreshold = 16; 81 int threshold; 82 bool diagnose; 83 84 if (errorLevel == EmailStatusCode.any) 85 { 86 threshold = EmailStatusCode.valid; 87 diagnose = true; 88 } 89 90 else if (errorLevel == EmailStatusCode.none) 91 threshold = defaultThreshold; 92 93 else 94 { 95 diagnose = true; 96 97 switch (errorLevel) 98 { 99 case EmailStatusCode.warning: threshold = defaultThreshold; break; 100 case EmailStatusCode.error: threshold = EmailStatusCode.valid; break; 101 default: threshold = errorLevel; 102 } 103 } 104 105 auto returnStatus = [EmailStatusCode.valid]; 106 auto context = EmailPart.componentLocalPart; 107 auto contextStack = [context]; 108 auto contextPrior = context; 109 tstring token = ""; 110 tstring tokenPrior = ""; 111 tstring[EmailPart] parseData = [EmailPart.componentLocalPart : "", EmailPart.componentDomain : ""]; 112 tstring[][EmailPart] atomList = [EmailPart.componentLocalPart : [""], EmailPart.componentDomain : [""]]; 113 auto elementCount = 0; 114 auto elementLength = 0; 115 auto hyphenFlag = false; 116 auto endOrDie = false; 117 auto crlfCount = int.min; // int.min == not defined 118 foreach(ref i,e;email)119 foreach (ref i, e ; email) 120 { 121 token = email.get(i, e); 122 123 switch (context) 124 { 125 case EmailPart.componentLocalPart: 126 switch (token) 127 { 128 case Token.openParenthesis: 129 if (elementLength == 0) 130 returnStatus ~= elementCount == 0 ? EmailStatusCode.comment : 131 EmailStatusCode.deprecatedComment; 132 133 else 134 { 135 returnStatus ~= EmailStatusCode.comment; 136 endOrDie = true; 137 } 138 139 contextStack ~= context; 140 context = EmailPart.contextComment; 141 break; 142 143 case Token.dot: 144 if (elementLength == 0) 145 returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart : 146 EmailStatusCode.errorConsecutiveDots; 147 148 else 149 { 150 if (endOrDie) 151 returnStatus ~= EmailStatusCode.deprecatedLocalPart; 152 } 153 154 endOrDie = false; 155 elementLength = 0; 156 elementCount++; 157 parseData[EmailPart.componentLocalPart] ~= token; 158 159 if (elementCount >= atomList[EmailPart.componentLocalPart].length) 160 atomList[EmailPart.componentLocalPart] ~= ""; 161 162 else 163 atomList[EmailPart.componentLocalPart][elementCount] = ""; 164 break; 165 166 case Token.doubleQuote: 167 if (elementLength == 0) 168 { 169 returnStatus ~= elementCount == 0 ? EmailStatusCode.rfc5321QuotedString : 170 EmailStatusCode.deprecatedLocalPart; 171 172 parseData[EmailPart.componentLocalPart] ~= token; 173 atomList[EmailPart.componentLocalPart][elementCount] ~= token; 174 elementLength++; 175 endOrDie = true; 176 contextStack ~= context; 177 context = EmailPart.contextQuotedString; 178 } 179 180 else 181 returnStatus ~= EmailStatusCode.errorExpectingText; 182 break; 183 184 case Token.cr: 185 case Token.space: 186 case Token.tab: 187 if ((token == Token.cr) && ((++i == email.length) || (email.get(i, e) != Token.lf))) 188 { 189 returnStatus ~= EmailStatusCode.errorCrNoLf; 190 break; 191 } 192 193 if (elementLength == 0) 194 returnStatus ~= elementCount == 0 ? EmailStatusCode.foldingWhitespace : 195 EmailStatusCode.deprecatedFoldingWhitespace; 196 197 else 198 endOrDie = true; 199 200 contextStack ~= context; 201 context = EmailPart.contextFoldingWhitespace; 202 tokenPrior = token; 203 break; 204 205 case Token.at: 206 enforce(contextStack.length == 1, "Unexpected item on context stack"); 207 208 if (parseData[EmailPart.componentLocalPart] == "") 209 returnStatus ~= EmailStatusCode.errorNoLocalPart; 210 211 else if (elementLength == 0) 212 returnStatus ~= EmailStatusCode.errorDotEnd; 213 214 else if (parseData[EmailPart.componentLocalPart].length > 64) 215 returnStatus ~= EmailStatusCode.rfc5322LocalTooLong; 216 217 else if (contextPrior == EmailPart.contextComment || 218 contextPrior == EmailPart.contextFoldingWhitespace) 219 returnStatus ~= EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt; 220 221 context = EmailPart.componentDomain; 222 contextStack = [context]; 223 elementCount = 0; 224 elementLength = 0; 225 endOrDie = false; 226 break; 227 228 default: 229 if (endOrDie) 230 { 231 switch (contextPrior) 232 { 233 case EmailPart.contextComment: 234 case EmailPart.contextFoldingWhitespace: 235 returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace; 236 break; 237 238 case EmailPart.contextQuotedString: 239 returnStatus ~= EmailStatusCode.errorTextAfterQuotedString; 240 break; 241 242 default: 243 throw new Exception("More text found where none is allowed, but " 244 ~"unrecognised prior context: " ~ to!(string)(contextPrior)); 245 } 246 } 247 248 else 249 { 250 contextPrior = context; 251 immutable c = token.front; 252 253 if (c < '!' || c > '~' || c == '\n' || Token.specials.canFind(token)) 254 returnStatus ~= EmailStatusCode.errorExpectingText; 255 256 parseData[EmailPart.componentLocalPart] ~= token; 257 atomList[EmailPart.componentLocalPart][elementCount] ~= token; 258 elementLength++; 259 } 260 } 261 break; 262 263 case EmailPart.componentDomain: 264 switch (token) 265 { 266 case Token.openParenthesis: 267 if (elementLength == 0) 268 { 269 returnStatus ~= elementCount == 0 ? 270 EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt 271 : EmailStatusCode.deprecatedComment; 272 } 273 else 274 { 275 returnStatus ~= EmailStatusCode.comment; 276 endOrDie = true; 277 } 278 279 contextStack ~= context; 280 context = EmailPart.contextComment; 281 break; 282 283 case Token.dot: 284 if (elementLength == 0) 285 returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart : 286 EmailStatusCode.errorConsecutiveDots; 287 288 else if (hyphenFlag) 289 returnStatus ~= EmailStatusCode.errorDomainHyphenEnd; 290 291 else 292 { 293 if (elementLength > 63) 294 returnStatus ~= EmailStatusCode.rfc5322LabelTooLong; 295 } 296 297 endOrDie = false; 298 elementLength = 0; 299 elementCount++; 300 301 //atomList[EmailPart.componentDomain][elementCount] = ""; 302 atomList[EmailPart.componentDomain] ~= ""; 303 parseData[EmailPart.componentDomain] ~= token; 304 break; 305 306 case Token.openBracket: 307 if (parseData[EmailPart.componentDomain] == "") 308 { 309 endOrDie = true; 310 elementLength++; 311 contextStack ~= context; 312 context = EmailPart.componentLiteral; 313 parseData[EmailPart.componentDomain] ~= token; 314 atomList[EmailPart.componentDomain][elementCount] ~= token; 315 parseData[EmailPart.componentLiteral] = ""; 316 } 317 318 else 319 returnStatus ~= EmailStatusCode.errorExpectingText; 320 break; 321 322 case Token.cr: 323 case Token.space: 324 case Token.tab: 325 if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) 326 { 327 returnStatus ~= EmailStatusCode.errorCrNoLf; 328 break; 329 } 330 331 if (elementLength == 0) 332 { 333 returnStatus ~= elementCount == 0 ? 334 EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt 335 : EmailStatusCode.deprecatedFoldingWhitespace; 336 } 337 else 338 { 339 returnStatus ~= EmailStatusCode.foldingWhitespace; 340 endOrDie = true; 341 } 342 343 contextStack ~= context; 344 context = EmailPart.contextFoldingWhitespace; 345 tokenPrior = token; 346 break; 347 348 default: 349 if (endOrDie) 350 { 351 switch (contextPrior) 352 { 353 case EmailPart.contextComment: 354 case EmailPart.contextFoldingWhitespace: 355 returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace; 356 break; 357 358 case EmailPart.componentLiteral: 359 returnStatus ~= EmailStatusCode.errorTextAfterDomainLiteral; 360 break; 361 362 default: 363 throw new Exception("More text found where none is allowed, but " 364 ~"unrecognised prior context: " ~ to!(string)(contextPrior)); 365 } 366 367 } 368 369 immutable c = token.front; 370 hyphenFlag = false; 371 372 if (c < '!' || c > '~' || Token.specials.canFind(token)) 373 returnStatus ~= EmailStatusCode.errorExpectingText; 374 375 else if (token == Token.hyphen) 376 { 377 if (elementLength == 0) 378 returnStatus ~= EmailStatusCode.errorDomainHyphenStart; 379 380 hyphenFlag = true; 381 } 382 383 else if (!((c > '/' && c < ':') || (c > '@' && c < '[') || (c > '`' && c < '{'))) 384 returnStatus ~= EmailStatusCode.rfc5322Domain; 385 386 parseData[EmailPart.componentDomain] ~= token; 387 atomList[EmailPart.componentDomain][elementCount] ~= token; 388 elementLength++; 389 } 390 break; 391 392 case EmailPart.componentLiteral: 393 switch (token) 394 { 395 case Token.closeBracket: 396 if (returnStatus.maxElement() < EmailStatusCode.deprecated_) 397 { 398 auto maxGroups = 8; 399 size_t index = -1; 400 auto addressLiteral = parseData[EmailPart.componentLiteral]; 401 auto matchesIp = addressLiteral.matchAll(ipRegex).map!(a => a.hit).array; 402 403 if (!matchesIp.empty) 404 { 405 index = addressLiteral.lastIndexOf(matchesIp.front); 406 407 if (index != 0) 408 addressLiteral = addressLiteral[0 .. index] ~ "0:0"; 409 } 410 411 if (index == 0) 412 returnStatus ~= EmailStatusCode.rfc5321AddressLiteral; 413 414 else if (addressLiteral.compareFirstN(Token.ipV6Tag, 5)) 415 returnStatus ~= EmailStatusCode.rfc5322DomainLiteral; 416 417 else 418 { 419 auto ipV6 = addressLiteral[5 .. $]; 420 matchesIp = ipV6.split(Token.colon); 421 immutable groupCount = matchesIp.length; 422 index = ipV6.indexOf(Token.doubleColon); 423 424 if (index == -1) 425 { 426 if (groupCount != maxGroups) 427 returnStatus ~= EmailStatusCode.rfc5322IpV6GroupCount; 428 } 429 430 else 431 { 432 if (index != ipV6.lastIndexOf(Token.doubleColon)) 433 returnStatus ~= EmailStatusCode.rfc5322IpV6TooManyDoubleColons; 434 435 else 436 { 437 if (index == 0 || index == (ipV6.length - 2)) 438 maxGroups++; 439 440 if (groupCount > maxGroups) 441 returnStatus ~= EmailStatusCode.rfc5322IpV6MaxGroups; 442 443 else if (groupCount == maxGroups) 444 returnStatus ~= EmailStatusCode.rfc5321IpV6Deprecated; 445 } 446 } 447 448 if (ipV6[0 .. 1] == Token.colon && ipV6[1 .. 2] != Token.colon) 449 returnStatus ~= EmailStatusCode.rfc5322IpV6ColonStart; 450 451 else if (ipV6[$ - 1 .. $] == Token.colon && ipV6[$ - 2 .. $ - 1] != Token.colon) 452 returnStatus ~= EmailStatusCode.rfc5322IpV6ColonEnd; 453 454 else if (!matchesIp 455 .filter!(a => a.matchFirst(fourChars).empty) 456 .empty) 457 returnStatus ~= EmailStatusCode.rfc5322IpV6BadChar; 458 459 else 460 returnStatus ~= EmailStatusCode.rfc5321AddressLiteral; 461 } 462 } 463 464 else 465 returnStatus ~= EmailStatusCode.rfc5322DomainLiteral; 466 467 parseData[EmailPart.componentDomain] ~= token; 468 atomList[EmailPart.componentDomain][elementCount] ~= token; 469 elementLength++; 470 contextPrior = context; 471 context = contextStack.pop(); 472 break; 473 474 case Token.backslash: 475 returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText; 476 contextStack ~= context; 477 context = EmailPart.contextQuotedPair; 478 break; 479 480 case Token.cr: 481 case Token.space: 482 case Token.tab: 483 if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) 484 { 485 returnStatus ~= EmailStatusCode.errorCrNoLf; 486 break; 487 } 488 489 returnStatus ~= EmailStatusCode.foldingWhitespace; 490 contextStack ~= context; 491 context = EmailPart.contextFoldingWhitespace; 492 tokenPrior = token; 493 break; 494 495 default: 496 immutable c = token.front; 497 498 if (c > AsciiToken.delete_ || c == '\0' || token == Token.openBracket) 499 { 500 returnStatus ~= EmailStatusCode.errorExpectingDomainText; 501 break; 502 } 503 504 else if (c < '!' || c == AsciiToken.delete_ ) 505 returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText; 506 507 parseData[EmailPart.componentLiteral] ~= token; 508 parseData[EmailPart.componentDomain] ~= token; 509 atomList[EmailPart.componentDomain][elementCount] ~= token; 510 elementLength++; 511 } 512 break; 513 514 case EmailPart.contextQuotedString: 515 switch (token) 516 { 517 case Token.backslash: 518 contextStack ~= context; 519 context = EmailPart.contextQuotedPair; 520 break; 521 522 case Token.cr: 523 case Token.tab: 524 if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) 525 { 526 returnStatus ~= EmailStatusCode.errorCrNoLf; 527 break; 528 } 529 530 parseData[EmailPart.componentLocalPart] ~= Token.space; 531 atomList[EmailPart.componentLocalPart][elementCount] ~= Token.space; 532 elementLength++; 533 534 returnStatus ~= EmailStatusCode.foldingWhitespace; 535 contextStack ~= context; 536 context = EmailPart.contextFoldingWhitespace; 537 tokenPrior = token; 538 break; 539 540 case Token.doubleQuote: 541 parseData[EmailPart.componentLocalPart] ~= token; 542 atomList[EmailPart.componentLocalPart][elementCount] ~= token; 543 elementLength++; 544 contextPrior = context; 545 context = contextStack.pop(); 546 break; 547 548 default: 549 immutable c = token.front; 550 551 if (c > AsciiToken.delete_ || c == '\0' || c == '\n') 552 returnStatus ~= EmailStatusCode.errorExpectingQuotedText; 553 554 else if (c < ' ' || c == AsciiToken.delete_) 555 returnStatus ~= EmailStatusCode.deprecatedQuotedText; 556 557 parseData[EmailPart.componentLocalPart] ~= token; 558 atomList[EmailPart.componentLocalPart][elementCount] ~= token; 559 elementLength++; 560 } 561 break; 562 563 case EmailPart.contextQuotedPair: 564 immutable c = token.front; 565 566 if (c > AsciiToken.delete_) 567 returnStatus ~= EmailStatusCode.errorExpectingQuotedPair; 568 569 else if (c < AsciiToken.unitSeparator && c != AsciiToken.horizontalTab || c == AsciiToken.delete_) 570 returnStatus ~= EmailStatusCode.deprecatedQuotedPair; 571 572 contextPrior = context; 573 context = contextStack.pop(); 574 token = Token.backslash ~ token; 575 576 switch (context) 577 { 578 case EmailPart.contextComment: break; 579 580 case EmailPart.contextQuotedString: 581 parseData[EmailPart.componentLocalPart] ~= token; 582 atomList[EmailPart.componentLocalPart][elementCount] ~= token; 583 elementLength += 2; 584 break; 585 586 case EmailPart.componentLiteral: 587 parseData[EmailPart.componentDomain] ~= token; 588 atomList[EmailPart.componentDomain][elementCount] ~= token; 589 elementLength += 2; 590 break; 591 592 default: 593 throw new Exception("Quoted pair logic invoked in an invalid context: " ~ to!(string)(context)); 594 } 595 break; 596 597 case EmailPart.contextComment: 598 switch (token) 599 { 600 case Token.openParenthesis: 601 contextStack ~= context; 602 context = EmailPart.contextComment; 603 break; 604 605 case Token.closeParenthesis: 606 contextPrior = context; 607 context = contextStack.pop(); 608 break; 609 610 case Token.backslash: 611 contextStack ~= context; 612 context = EmailPart.contextQuotedPair; 613 break; 614 615 case Token.cr: 616 case Token.space: 617 case Token.tab: 618 if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) 619 { 620 returnStatus ~= EmailStatusCode.errorCrNoLf; 621 break; 622 } 623 624 returnStatus ~= EmailStatusCode.foldingWhitespace; 625 626 contextStack ~= context; 627 context = EmailPart.contextFoldingWhitespace; 628 tokenPrior = token; 629 break; 630 631 default: 632 immutable c = token.front; 633 634 if (c > AsciiToken.delete_ || c == '\0' || c == '\n') 635 { 636 returnStatus ~= EmailStatusCode.errorExpectingCommentText; 637 break; 638 } 639 640 else if (c < ' ' || c == AsciiToken.delete_) 641 returnStatus ~= EmailStatusCode.deprecatedCommentText; 642 } 643 break; 644 645 case EmailPart.contextFoldingWhitespace: 646 if (tokenPrior == Token.cr) 647 { 648 if (token == Token.cr) 649 { 650 returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrflX2; 651 break; 652 } 653 654 if (crlfCount != int.min) // int.min == not defined 655 { 656 if (++crlfCount > 1) 657 returnStatus ~= EmailStatusCode.deprecatedFoldingWhitespace; 658 } 659 660 else 661 crlfCount = 1; 662 } 663 664 switch (token) 665 { 666 case Token.cr: 667 if (++i == email.length || email.get(i, e) != Token.lf) 668 returnStatus ~= EmailStatusCode.errorCrNoLf; 669 break; 670 671 case Token.space: 672 case Token.tab: 673 break; 674 675 default: 676 if (tokenPrior == Token.cr) 677 { 678 returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd; 679 break; 680 } 681 682 crlfCount = int.min; // int.min == not defined 683 contextPrior = context; 684 context = contextStack.pop(); 685 i--; 686 break; 687 } 688 689 tokenPrior = token; 690 break; 691 692 default: 693 throw new Exception("Unkown context: " ~ to!(string)(context)); 694 } 695 696 if (returnStatus.maxElement() > EmailStatusCode.rfc5322) 697 break; 698 } 699 700 if (returnStatus.maxElement() < EmailStatusCode.rfc5322) 701 { 702 if (context == EmailPart.contextQuotedString) 703 returnStatus ~= EmailStatusCode.errorUnclosedQuotedString; 704 705 else if (context == EmailPart.contextQuotedPair) 706 returnStatus ~= EmailStatusCode.errorBackslashEnd; 707 708 else if (context == EmailPart.contextComment) 709 returnStatus ~= EmailStatusCode.errorUnclosedComment; 710 711 else if (context == EmailPart.componentLiteral) 712 returnStatus ~= EmailStatusCode.errorUnclosedDomainLiteral; 713 714 else if (token == Token.cr) 715 returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd; 716 717 else if (parseData[EmailPart.componentDomain] == "") 718 returnStatus ~= EmailStatusCode.errorNoDomain; 719 720 else if (elementLength == 0) 721 returnStatus ~= EmailStatusCode.errorDotEnd; 722 723 else if (hyphenFlag) 724 returnStatus ~= EmailStatusCode.errorDomainHyphenEnd; 725 726 else if (parseData[EmailPart.componentDomain].length > 255) 727 returnStatus ~= EmailStatusCode.rfc5322DomainTooLong; 728 729 else if ((parseData[EmailPart.componentLocalPart] ~ Token.at ~ parseData[EmailPart.componentDomain]).length > 730 254) 731 returnStatus ~= EmailStatusCode.rfc5322TooLong; 732 733 else if (elementLength > 63) 734 returnStatus ~= EmailStatusCode.rfc5322LabelTooLong; 735 } 736 737 auto dnsChecked = false; 738 739 if (checkDNS == Yes.checkDns && returnStatus.maxElement() < EmailStatusCode.dnsWarning) 740 { 741 assert(false, "DNS check is currently not implemented"); 742 } 743 744 if (!dnsChecked && returnStatus.maxElement() < EmailStatusCode.dnsWarning) 745 { 746 if (elementCount == 0) 747 returnStatus ~= EmailStatusCode.rfc5321TopLevelDomain; 748 749 if (isNumber(atomList[EmailPart.componentDomain][elementCount].front)) 750 returnStatus ~= EmailStatusCode.rfc5321TopLevelDomainNumeric; 751 } 752 753 returnStatus = array(uniq(returnStatus)); 754 auto finalStatus = returnStatus.maxElement(); 755 756 if (returnStatus.length != 1) 757 returnStatus.popFront(); 758 759 parseData[EmailPart.status] = to!(tstring)(returnStatus); 760 761 if (finalStatus < threshold) 762 finalStatus = EmailStatusCode.valid; 763 764 if (!diagnose) 765 finalStatus = finalStatus < threshold ? EmailStatusCode.valid : EmailStatusCode.error; 766 767 auto valid = finalStatus == EmailStatusCode.valid; 768 tstring localPart = ""; 769 tstring domainPart = ""; 770 771 if (auto value = EmailPart.componentLocalPart in parseData) 772 localPart = *value; 773 774 if (auto value = EmailPart.componentDomain in parseData) 775 domainPart = *value; 776 777 return EmailStatus(valid, to!(string)(localPart), to!(string)(domainPart), finalStatus); 778 } 779 780 @safe unittest 781 { 782 assert(`test.test@iana.org`.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); 783 assert(`test.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.valid); 784 785 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(No.checkDns, 786 EmailStatusCode.none).statusCode == EmailStatusCode.valid); 787 788 assert(`test`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.error); 789 assert(`(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.error); 790 791 assert(``.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); 792 assert(`test`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); 793 assert(`@`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart); 794 assert(`test@`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); 795 796 // assert(`test@io`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid, 797 // `io. currently has an MX-record (Feb 2011). Some DNS setups seem to find it, some don't.` 798 // ` If you don't see the MX for io. then try setting your DNS server to 8.8.8.8 (the Google DNS server)`); 799 800 assert(`@io`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart, 801 `io. currently has an MX-record (Feb 2011)`); 802 803 assert(`@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart); 804 assert(`test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 805 assert(`test@nominet.org.uk`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 806 assert(`test@about.museum`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 807 assert(`a@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 808 809 //assert(`test@e.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); 810 // DNS check is currently not implemented 811 812 //assert(`test@iana.a`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); 813 // DNS check is currently not implemented 814 815 assert(`test.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 816 assert(`.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart); 817 assert(`test.@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd); 818 819 assert(`test .. iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 820 EmailStatusCode.errorConsecutiveDots); 821 822 assert(`test_exa-mple.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); 823 assert("!#$%&`*+/=?^`{|}~@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 824 825 assert(`test\@test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 826 EmailStatusCode.errorExpectingText); 827 828 assert(`123@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 829 assert(`test@123.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 830 831 assert(`test@iana.123`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 832 EmailStatusCode.rfc5321TopLevelDomainNumeric); 833 assert(`test@255.255.255.255`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 834 EmailStatusCode.rfc5321TopLevelDomainNumeric); 835 836 assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(No.checkDns, 837 EmailStatusCode.any).statusCode == EmailStatusCode.valid); 838 839 assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org`.isEmail(No.checkDns, 840 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong); 841 842 // assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(No.checkDns, 843 // EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); 844 // DNS check is currently not implemented 845 846 assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com`.isEmail(No.checkDns, 847 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LabelTooLong); 848 849 assert(`test@mason-dixon.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 850 851 assert(`test@-iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 852 EmailStatusCode.errorDomainHyphenStart); 853 854 assert(`test@iana-.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 855 EmailStatusCode.errorDomainHyphenEnd); 856 857 assert(`test@g--a.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); 858 859 //assert(`test@iana.co-uk`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 860 //EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented 861 862 assert(`test@.iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart); 863 assert(`test@iana.org.`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd); 864 assert(`test@iana .. com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 865 EmailStatusCode.errorConsecutiveDots); 866 867 //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` 868 // `.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` 869 // `.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 870 // EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented 871 872 // assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz` 873 // `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.` 874 // `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi`.isEmail(No.checkDns, 875 // EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); 876 // DNS check is currently not implemented 877 878 assert((`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`~ 879 `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~ 880 `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij`).isEmail(No.checkDns, 881 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong); 882 883 assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~ 884 `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~ 885 `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij`).isEmail(No.checkDns, 886 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong); 887 888 assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~ 889 `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~ 890 `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk`).isEmail(No.checkDns, 891 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322DomainTooLong); 892 893 assert(`"test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 894 EmailStatusCode.rfc5321QuotedString); 895 896 assert(`""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); 897 assert(`"""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText); 898 assert(`"\a"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); 899 assert(`"\""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); 900 901 assert(`"\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 902 EmailStatusCode.errorUnclosedQuotedString); 903 904 assert(`"\\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); 905 assert(`test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText); 906 907 assert(`"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 908 EmailStatusCode.errorUnclosedQuotedString); 909 910 assert(`"test"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 911 EmailStatusCode.errorTextAfterQuotedString); 912 913 assert(`test"text"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 914 EmailStatusCode.errorExpectingText); 915 916 assert(`"test""test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 917 EmailStatusCode.errorExpectingText); 918 919 assert(`"test"."test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 920 EmailStatusCode.deprecatedLocalPart); 921 922 assert(`"test\ test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 923 EmailStatusCode.rfc5321QuotedString); 924 925 assert(`"test".test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 926 EmailStatusCode.deprecatedLocalPart); 927 928 assert("\"test\u0000\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 929 EmailStatusCode.errorExpectingQuotedText); 930 931 assert("\"test\\\u0000\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 932 EmailStatusCode.deprecatedQuotedPair); 933 934 assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj"@iana.org`.isEmail(No.checkDns, 935 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong, 936 `Quotes are still part of the length restriction`); 937 938 assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\h"@iana.org`.isEmail(No.checkDns, 939 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong, 940 `Quoted pair is still part of the length restriction`); 941 942 assert(`test@[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 943 EmailStatusCode.rfc5321AddressLiteral); 944 945 assert(`test@a[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 946 EmailStatusCode.errorExpectingText); 947 948 assert(`test@[255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 949 EmailStatusCode.rfc5322DomainLiteral); 950 951 assert(`test@[255.255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 952 EmailStatusCode.rfc5322DomainLiteral); 953 954 assert(`test@[255.255.255.256]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 955 EmailStatusCode.rfc5322DomainLiteral); 956 957 assert(`test@[1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 958 EmailStatusCode.rfc5322DomainLiteral); 959 960 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 961 EmailStatusCode.rfc5322IpV6GroupCount); 962 963 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode 964 == EmailStatusCode.rfc5321AddressLiteral); 965 966 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]`.isEmail(No.checkDns, 967 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount); 968 969 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]`.isEmail(No.checkDns, 970 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6BadChar); 971 972 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(No.checkDns, 973 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321IpV6Deprecated); 974 975 assert(`test@[IPv6:1111:2222:3333:4444:5555::8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 976 EmailStatusCode.rfc5321AddressLiteral); 977 978 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]`.isEmail(No.checkDns, 979 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups); 980 981 assert(`test@[IPv6::3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 982 EmailStatusCode.rfc5322IpV6ColonStart); 983 984 assert(`test@[IPv6:::3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 985 EmailStatusCode.rfc5321AddressLiteral); 986 987 assert(`test@[IPv6:1111::4444:5555::8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 988 EmailStatusCode.rfc5322IpV6TooManyDoubleColons); 989 990 assert(`test@[IPv6:::]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 991 EmailStatusCode.rfc5321AddressLiteral); 992 993 assert(`test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]`.isEmail(No.checkDns, 994 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount); 995 996 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]`.isEmail(No.checkDns, 997 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral); 998 999 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]`.isEmail(No.checkDns, 1000 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount); 1001 1002 assert(`test@[IPv6:1111:2222:3333:4444::255.255.255.255]`.isEmail(No.checkDns, 1003 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral); 1004 1005 assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]`.isEmail(No.checkDns, 1006 EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups); 1007 1008 assert(`test@[IPv6:1111:2222:3333:4444:::255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode 1009 == EmailStatusCode.rfc5322IpV6TooManyDoubleColons); 1010 1011 assert(`test@[IPv6::255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1012 EmailStatusCode.rfc5322IpV6ColonStart); 1013 1014 assert(` test @iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1015 EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); 1016 1017 assert(`test@ iana .com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1018 EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); 1019 1020 assert(`test . test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1021 EmailStatusCode.deprecatedFoldingWhitespace); 1022 1023 assert("\u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1024 EmailStatusCode.foldingWhitespace, `Folding whitespace`); 1025 1026 assert("\u000D\u000A \u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1027 EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP`~ 1028 ` -- only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`); 1029 1030 assert(`(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.comment); 1031 assert(`((comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1032 EmailStatusCode.errorUnclosedComment); 1033 1034 assert(`(comment(comment))test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1035 EmailStatusCode.comment); 1036 1037 assert(`test@(comment)iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1038 EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); 1039 1040 assert(`test(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1041 EmailStatusCode.errorTextAfterCommentFoldingWhitespace); 1042 1043 assert(`test@(comment)[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1044 EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); 1045 1046 assert(`(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(No.checkDns, 1047 EmailStatusCode.any).statusCode == EmailStatusCode.comment); 1048 1049 assert(`test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(No.checkDns, 1050 EmailStatusCode.any).statusCode == EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); 1051 1052 assert((`(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyz`~ 1053 `abcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.`~ 1054 `abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu`).isEmail(No.checkDns, 1055 EmailStatusCode.any).statusCode == EmailStatusCode.comment); 1056 1057 assert("test@iana.org\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1058 EmailStatusCode.errorExpectingText); 1059 1060 assert(`test@xn--hxajbheg2az3al.xn--jxalpdlp`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1061 EmailStatusCode.valid, `A valid IDN from ICANN's <a href="http://idn.icann.org/#The_example.test_names">`~ 1062 `IDN TLD evaluation gateway</a>`); 1063 1064 assert(`xn--test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid, 1065 `RFC 3490: "unless the email standards are revised to invite the use of IDNA for local parts, a domain label`~ 1066 ` that holds the local part of an email address SHOULD NOT begin with the ACE prefix, and even if it does,`~ 1067 ` it is to be interpreted literally as a local part that happens to begin with the ACE prefix"`); 1068 1069 assert(`test@iana.org-`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1070 EmailStatusCode.errorDomainHyphenEnd); 1071 1072 assert(`"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1073 EmailStatusCode.errorUnclosedQuotedString); 1074 1075 assert(`(test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1076 EmailStatusCode.errorUnclosedComment); 1077 1078 assert(`test@(iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1079 EmailStatusCode.errorUnclosedComment); 1080 1081 assert(`test@[1.2.3.4`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1082 EmailStatusCode.errorUnclosedDomainLiteral); 1083 1084 assert(`"test\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1085 EmailStatusCode.errorUnclosedQuotedString); 1086 1087 assert(`(comment\)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1088 EmailStatusCode.errorUnclosedComment); 1089 1090 assert(`test@iana.org(comment\)`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1091 EmailStatusCode.errorUnclosedComment); 1092 1093 assert(`test@iana.org(comment\`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1094 EmailStatusCode.errorBackslashEnd); 1095 1096 assert(`test@[RFC-5322-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1097 EmailStatusCode.rfc5322DomainLiteral); 1098 1099 assert(`test@[RFC-5322]-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1100 EmailStatusCode.errorTextAfterDomainLiteral); 1101 1102 assert(`test@[RFC-5322-[domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1103 EmailStatusCode.errorExpectingDomainText); 1104 1105 assert("test@[RFC-5322-\\\u0007-domain-literal]".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1106 EmailStatusCode.rfc5322DomainLiteralObsoleteText, `obs-dtext <strong>and</strong> obs-qp`); 1107 1108 assert("test@[RFC-5322-\\\u0009-domain-literal]".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1109 EmailStatusCode.rfc5322DomainLiteralObsoleteText); 1110 1111 assert(`test@[RFC-5322-\]-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1112 EmailStatusCode.rfc5322DomainLiteralObsoleteText); 1113 1114 assert(`test@[RFC-5322-domain-literal\]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1115 EmailStatusCode.errorUnclosedDomainLiteral); 1116 1117 assert(`test@[RFC-5322-domain-literal\`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1118 EmailStatusCode.errorBackslashEnd); 1119 1120 assert(`test@[RFC 5322 domain literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1121 EmailStatusCode.rfc5322DomainLiteral, `Spaces are FWS in a domain literal`); 1122 1123 assert(`test@[RFC-5322-domain-literal] (comment)`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1124 EmailStatusCode.rfc5322DomainLiteral); 1125 1126 assert("\u007F@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1127 EmailStatusCode.errorExpectingText); 1128 assert("test@\u007F.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1129 EmailStatusCode.errorExpectingText); 1130 assert("\"\u007F\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1131 EmailStatusCode.deprecatedQuotedText); 1132 1133 assert("\"\\\u007F\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1134 EmailStatusCode.deprecatedQuotedPair); 1135 1136 assert("(\u007F)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1137 EmailStatusCode.deprecatedCommentText); 1138 1139 assert("test@iana.org\u000D".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, 1140 `No LF after the CR`); 1141 1142 assert("\u000Dtest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, 1143 `No LF after the CR`); 1144 1145 assert("\"\u000Dtest\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1146 EmailStatusCode.errorCrNoLf, `No LF after the CR`); 1147 1148 assert("(\u000D)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, 1149 `No LF after the CR`); 1150 1151 assert("(\u000D".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, 1152 `No LF after the CR`); 1153 1154 assert("test@iana.org(\u000D)".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, 1155 `No LF after the CR`); 1156 1157 assert("\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1158 EmailStatusCode.errorExpectingText); 1159 1160 assert("\"\u000A\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1161 EmailStatusCode.errorExpectingQuotedText); 1162 1163 assert("\"\\\u000A\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1164 EmailStatusCode.deprecatedQuotedPair); 1165 1166 assert("(\u000A)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1167 EmailStatusCode.errorExpectingCommentText); 1168 1169 assert("\u0007@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1170 EmailStatusCode.errorExpectingText); 1171 1172 assert("test@\u0007.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1173 EmailStatusCode.errorExpectingText); 1174 1175 assert("\"\u0007\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1176 EmailStatusCode.deprecatedQuotedText); 1177 1178 assert("\"\\\u0007\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1179 EmailStatusCode.deprecatedQuotedPair); 1180 1181 assert("(\u0007)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1182 EmailStatusCode.deprecatedCommentText); 1183 1184 assert("\u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1185 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`); 1186 1187 assert("\u000D\u000A \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1188 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`); 1189 1190 assert(" \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1191 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`); 1192 1193 assert(" \u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1194 EmailStatusCode.foldingWhitespace, `FWS`); 1195 1196 assert(" \u000D\u000A \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1197 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`); 1198 1199 assert(" \u000D\u000A\u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1200 EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`); 1201 1202 assert(" \u000D\u000A\u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1203 EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`); 1204 1205 assert("test@iana.org\u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1206 EmailStatusCode.foldingWhitespace, `FWS`); 1207 1208 assert("test@iana.org\u000D\u000A \u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1209 EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP -- `~ 1210 `only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`); 1211 1212 assert("test@iana.org\u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1213 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`); 1214 1215 assert("test@iana.org\u000D\u000A \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1216 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`); 1217 1218 assert("test@iana.org \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1219 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`); 1220 1221 assert("test@iana.org \u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1222 EmailStatusCode.foldingWhitespace, `FWS`); 1223 1224 assert("test@iana.org \u000D\u000A \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1225 EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`); 1226 1227 assert("test@iana.org \u000D\u000A\u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1228 EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`); 1229 1230 assert("test@iana.org \u000D\u000A\u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1231 EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`); 1232 1233 assert(" test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace); 1234 assert(`test@iana.org `.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace); 1235 1236 assert(`test@[IPv6:1::2:]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1237 EmailStatusCode.rfc5322IpV6ColonEnd); 1238 1239 assert("\"test\\\u00A9\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1240 EmailStatusCode.errorExpectingQuotedPair); 1241 1242 assert(`test@iana/icann.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322Domain); 1243 1244 assert(`test.(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1245 EmailStatusCode.deprecatedComment); 1246 1247 assert(`test@org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321TopLevelDomain); 1248 1249 // assert(`test@test.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == 1250 //EmailStatusCode.dnsWarningNoMXRecord, `test.com has an A-record but not an MX-record`); 1251 // DNS check is currently not implemented 1252 // 1253 // assert(`test@nic.no`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord, 1254 // `nic.no currently has no MX-records or A-records (Feb 2011). If you are seeing an A-record for nic.io then` 1255 // ` try setting your DNS server to 8.8.8.8 (the Google DNS server) - your DNS server may be faking an A-record` 1256 // ` (OpenDNS does this, for instance).`); // DNS check is currently not implemented 1257 } 1258 1259 // https://issues.dlang.org/show_bug.cgi?id=17217 1260 @safe unittest 1261 { 1262 wstring a = `test.test@iana.org`w; 1263 dstring b = `test.test@iana.org`d; 1264 const(wchar)[] c = `test.test@iana.org`w; 1265 const(dchar)[] d = `test.test@iana.org`d; 1266 1267 assert(a.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); 1268 assert(b.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); 1269 assert(c.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); 1270 assert(d.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); 1271 } 1272 1273 /** 1274 * Flag for indicating if the isEmail function should perform a DNS check or not. 1275 * 1276 * If set to $(D CheckDns.no), isEmail does not perform DNS checking. 1277 * 1278 * Otherwise if set to $(D CheckDns.yes), isEmail performs DNS checking. 1279 */ 1280 alias CheckDns = Flag!"checkDns"; 1281 1282 /// Represents the status of an email address 1283 struct EmailStatus 1284 { 1285 private 1286 { 1287 bool valid_; 1288 string localPart_; 1289 string domainPart_; 1290 EmailStatusCode statusCode_; 1291 } 1292 1293 /// Self aliases to a `bool` representing if the email is valid or not 1294 alias valid this; 1295 1296 /* 1297 * Params: 1298 * valid = indicates if the email address is valid or not 1299 * localPart = the local part of the email address 1300 * domainPart = the domain part of the email address 1301 * statusCode = the status code 1302 */ 1303 private this (bool valid, string localPart, string domainPart, EmailStatusCode statusCode) @safe @nogc pure nothrow 1304 { 1305 this.valid_ = valid; 1306 this.localPart_ = localPart; 1307 this.domainPart_ = domainPart; 1308 this.statusCode_ = statusCode; 1309 } 1310 1311 /// Returns: If the email address is valid or not. 1312 @property bool valid() const @safe @nogc pure nothrow 1313 { 1314 return valid_; 1315 } 1316 1317 /// Returns: The local part of the email address, that is, the part before the @ sign. 1318 @property string localPart() const @safe @nogc pure nothrow 1319 { 1320 return localPart_; 1321 } 1322 1323 /// Returns: The domain part of the email address, that is, the part after the @ sign. 1324 @property string domainPart() const @safe @nogc pure nothrow 1325 { 1326 return domainPart_; 1327 } 1328 1329 /// Returns: The email status code 1330 @property EmailStatusCode statusCode() const @safe @nogc pure nothrow 1331 { 1332 return statusCode_; 1333 } 1334 1335 /// Returns: A describing string of the status code 1336 @property string status() const @safe @nogc pure nothrow 1337 { 1338 return statusCodeDescription(statusCode_); 1339 } 1340 1341 /// Returns: A textual representation of the email status 1342 string toString() const @safe pure 1343 { 1344 import std.format : format; 1345 return format("EmailStatus\n{\n\tvalid: %s\n\tlocalPart: %s\n\tdomainPart: %s\n\tstatusCode: %s\n}", valid, 1346 localPart, domainPart, statusCode); 1347 } 1348 } 1349 1350 /** 1351 * Params: 1352 * statusCode = The $(LREF EmailStatusCode) to read 1353 * Returns: 1354 * A detailed string describing the given status code 1355 */ 1356 string statusCodeDescription(EmailStatusCode statusCode) @safe @nogc pure nothrow 1357 { 1358 final switch (statusCode) 1359 { 1360 // Categories 1361 case EmailStatusCode.validCategory: return "Address is valid"; 1362 case EmailStatusCode.dnsWarning: return "Address is valid but a DNS check was not successful"; 1363 case EmailStatusCode.rfc5321: return "Address is valid for SMTP but has unusual elements"; 1364 1365 case EmailStatusCode.cFoldingWhitespace: return "Address is valid within the message but cannot be used"~ 1366 " unmodified for the envelope"; 1367 1368 case EmailStatusCode.deprecated_: return "Address contains deprecated elements but may still be valid in"~ 1369 " restricted contexts"; 1370 1371 case EmailStatusCode.rfc5322: return "The address is only valid according to the broad definition of RFC 5322."~ 1372 " It is otherwise invalid"; 1373 1374 case EmailStatusCode.any: return ""; 1375 case EmailStatusCode.none: return ""; 1376 case EmailStatusCode.warning: return ""; 1377 case EmailStatusCode.error: return "Address is invalid for any purpose"; 1378 1379 // Diagnoses 1380 case EmailStatusCode.valid: return "Address is valid"; 1381 1382 // Address is valid but a DNS check was not successful 1383 case EmailStatusCode.dnsWarningNoMXRecord: return "Could not find an MX record for this domain but an A-record"~ 1384 " does exist"; 1385 1386 case EmailStatusCode.dnsWarningNoRecord: return "Could not find an MX record or an A-record for this domain"; 1387 1388 // Address is valid for SMTP but has unusual elements 1389 case EmailStatusCode.rfc5321TopLevelDomain: return "Address is valid but at a Top Level Domain"; 1390 1391 case EmailStatusCode.rfc5321TopLevelDomainNumeric: return "Address is valid but the Top Level Domain begins"~ 1392 " with a number"; 1393 1394 case EmailStatusCode.rfc5321QuotedString: return "Address is valid but contains a quoted string"; 1395 case EmailStatusCode.rfc5321AddressLiteral: return "Address is valid but at a literal address not a domain"; 1396 1397 case EmailStatusCode.rfc5321IpV6Deprecated: return "Address is valid but contains a :: that only elides one"~ 1398 " zero group"; 1399 1400 1401 // Address is valid within the message but cannot be used unmodified for the envelope 1402 case EmailStatusCode.comment: return "Address contains comments"; 1403 case EmailStatusCode.foldingWhitespace: return "Address contains Folding White Space"; 1404 1405 // Address contains deprecated elements but may still be valid in restricted contexts 1406 case EmailStatusCode.deprecatedLocalPart: return "The local part is in a deprecated form"; 1407 1408 case EmailStatusCode.deprecatedFoldingWhitespace: return "Address contains an obsolete form of"~ 1409 " Folding White Space"; 1410 1411 case EmailStatusCode.deprecatedQuotedText: return "A quoted string contains a deprecated character"; 1412 case EmailStatusCode.deprecatedQuotedPair: return "A quoted pair contains a deprecated character"; 1413 case EmailStatusCode.deprecatedComment: return "Address contains a comment in a position that is deprecated"; 1414 case EmailStatusCode.deprecatedCommentText: return "A comment contains a deprecated character"; 1415 1416 case EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt: return "Address contains a comment or"~ 1417 " Folding White Space around the @ sign"; 1418 1419 // The address is only valid according to the broad definition of RFC 5322 1420 case EmailStatusCode.rfc5322Domain: return "Address is RFC 5322 compliant but contains domain characters that"~ 1421 " are not allowed by DNS"; 1422 1423 case EmailStatusCode.rfc5322TooLong: return "Address is too long"; 1424 case EmailStatusCode.rfc5322LocalTooLong: return "The local part of the address is too long"; 1425 case EmailStatusCode.rfc5322DomainTooLong: return "The domain part is too long"; 1426 case EmailStatusCode.rfc5322LabelTooLong: return "The domain part contains an element that is too long"; 1427 case EmailStatusCode.rfc5322DomainLiteral: return "The domain literal is not a valid RFC 5321 address literal"; 1428 1429 case EmailStatusCode.rfc5322DomainLiteralObsoleteText: return "The domain literal is not a valid RFC 5321"~ 1430 " address literal and it contains obsolete characters"; 1431 1432 case EmailStatusCode.rfc5322IpV6GroupCount: 1433 return "The IPv6 literal address contains the wrong number of groups"; 1434 1435 case EmailStatusCode.rfc5322IpV6TooManyDoubleColons: 1436 return "The IPv6 literal address contains too many :: sequences"; 1437 1438 case EmailStatusCode.rfc5322IpV6BadChar: return "The IPv6 address contains an illegal group of characters"; 1439 case EmailStatusCode.rfc5322IpV6MaxGroups: return "The IPv6 address has too many groups"; 1440 case EmailStatusCode.rfc5322IpV6ColonStart: return "IPv6 address starts with a single colon"; 1441 case EmailStatusCode.rfc5322IpV6ColonEnd: return "IPv6 address ends with a single colon"; 1442 1443 // Address is invalid for any purpose 1444 case EmailStatusCode.errorExpectingDomainText: 1445 return "A domain literal contains a character that is not allowed"; 1446 1447 case EmailStatusCode.errorNoLocalPart: return "Address has no local part"; 1448 case EmailStatusCode.errorNoDomain: return "Address has no domain part"; 1449 case EmailStatusCode.errorConsecutiveDots: return "The address may not contain consecutive dots"; 1450 1451 case EmailStatusCode.errorTextAfterCommentFoldingWhitespace: 1452 return "Address contains text after a comment or Folding White Space"; 1453 1454 case EmailStatusCode.errorTextAfterQuotedString: return "Address contains text after a quoted string"; 1455 1456 case EmailStatusCode.errorTextAfterDomainLiteral: return "Extra characters were found after the end of"~ 1457 " the domain literal"; 1458 1459 case EmailStatusCode.errorExpectingQuotedPair: 1460 return "The address contains a character that is not allowed in a quoted pair"; 1461 1462 case EmailStatusCode.errorExpectingText: return "Address contains a character that is not allowed"; 1463 1464 case EmailStatusCode.errorExpectingQuotedText: 1465 return "A quoted string contains a character that is not allowed"; 1466 1467 case EmailStatusCode.errorExpectingCommentText: return "A comment contains a character that is not allowed"; 1468 case EmailStatusCode.errorBackslashEnd: return "The address cannot end with a backslash"; 1469 case EmailStatusCode.errorDotStart: return "Neither part of the address may begin with a dot"; 1470 case EmailStatusCode.errorDotEnd: return "Neither part of the address may end with a dot"; 1471 case EmailStatusCode.errorDomainHyphenStart: return "A domain or subdomain cannot begin with a hyphen"; 1472 case EmailStatusCode.errorDomainHyphenEnd: return "A domain or subdomain cannot end with a hyphen"; 1473 case EmailStatusCode.errorUnclosedQuotedString: return "Unclosed quoted string"; 1474 case EmailStatusCode.errorUnclosedComment: return "Unclosed comment"; 1475 case EmailStatusCode.errorUnclosedDomainLiteral: return "Domain literal is missing its closing bracket"; 1476 1477 case EmailStatusCode.errorFoldingWhitespaceCrflX2: 1478 return "Folding White Space contains consecutive CRLF sequences"; 1479 1480 case EmailStatusCode.errorFoldingWhitespaceCrLfEnd: return "Folding White Space ends with a CRLF sequence"; 1481 1482 case EmailStatusCode.errorCrNoLf: 1483 return "Address contains a carriage return that is not followed by a line feed"; 1484 } 1485 } 1486 1487 /** 1488 * An email status code, indicating if an email address is valid or not. 1489 * If it is invalid it also indicates why. 1490 */ 1491 enum EmailStatusCode 1492 { 1493 // Categories 1494 1495 /// Address is valid 1496 validCategory = 1, 1497 1498 /// Address is valid but a DNS check was not successful 1499 dnsWarning = 7, 1500 1501 /// Address is valid for SMTP but has unusual elements 1502 rfc5321 = 15, 1503 1504 /// Address is valid within the message but cannot be used unmodified for the envelope 1505 cFoldingWhitespace = 31, 1506 1507 /// Address contains deprecated elements but may still be valid in restricted contexts 1508 deprecated_ = 63, 1509 1510 /// The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid 1511 rfc5322 = 127, 1512 1513 /** 1514 * All finer grained error checking is turned on. Address containing errors or 1515 * warnings is considered invalid. A specific email status code will be 1516 * returned indicating the error/warning of the address. 1517 */ 1518 any = 252, 1519 1520 /** 1521 * Address is either considered valid or not, no finer grained error checking 1522 * is performed. Returned email status code will be either Error or Valid. 1523 */ 1524 none = 253, 1525 1526 /** 1527 * Address containing warnings is considered valid, that is, 1528 * any status code below 16 is considered valid. 1529 */ 1530 warning = 254, 1531 1532 /// Address is invalid for any purpose 1533 error = 255, 1534 1535 1536 1537 // Diagnoses 1538 1539 /// Address is valid 1540 valid = 0, 1541 1542 // Address is valid but a DNS check was not successful 1543 1544 /// Could not find an MX record for this domain but an A-record does exist 1545 dnsWarningNoMXRecord = 5, 1546 1547 /// Could not find an MX record or an A-record for this domain 1548 dnsWarningNoRecord = 6, 1549 1550 1551 1552 // Address is valid for SMTP but has unusual elements 1553 1554 /// Address is valid but at a Top Level Domain 1555 rfc5321TopLevelDomain = 9, 1556 1557 /// Address is valid but the Top Level Domain begins with a number 1558 rfc5321TopLevelDomainNumeric = 10, 1559 1560 /// Address is valid but contains a quoted string 1561 rfc5321QuotedString = 11, 1562 1563 /// Address is valid but at a literal address not a domain 1564 rfc5321AddressLiteral = 12, 1565 1566 /// Address is valid but contains a :: that only elides one zero group 1567 rfc5321IpV6Deprecated = 13, 1568 1569 1570 1571 // Address is valid within the message but cannot be used unmodified for the envelope 1572 1573 /// Address contains comments 1574 comment = 17, 1575 1576 /// Address contains Folding White Space 1577 foldingWhitespace = 18, 1578 1579 1580 1581 // Address contains deprecated elements but may still be valid in restricted contexts 1582 1583 /// The local part is in a deprecated form 1584 deprecatedLocalPart = 33, 1585 1586 /// Address contains an obsolete form of Folding White Space 1587 deprecatedFoldingWhitespace = 34, 1588 1589 /// A quoted string contains a deprecated character 1590 deprecatedQuotedText = 35, 1591 1592 /// A quoted pair contains a deprecated character 1593 deprecatedQuotedPair = 36, 1594 1595 /// Address contains a comment in a position that is deprecated 1596 deprecatedComment = 37, 1597 1598 /// A comment contains a deprecated character 1599 deprecatedCommentText = 38, 1600 1601 /// Address contains a comment or Folding White Space around the @ sign 1602 deprecatedCommentFoldingWhitespaceNearAt = 49, 1603 1604 1605 1606 // The address is only valid according to the broad definition of RFC 5322 1607 1608 /// Address is RFC 5322 compliant but contains domain characters that are not allowed by DNS 1609 rfc5322Domain = 65, 1610 1611 /// Address is too long 1612 rfc5322TooLong = 66, 1613 1614 /// The local part of the address is too long 1615 rfc5322LocalTooLong = 67, 1616 1617 /// The domain part is too long 1618 rfc5322DomainTooLong = 68, 1619 1620 /// The domain part contains an element that is too long 1621 rfc5322LabelTooLong = 69, 1622 1623 /// The domain literal is not a valid RFC 5321 address literal 1624 rfc5322DomainLiteral = 70, 1625 1626 /// The domain literal is not a valid RFC 5321 address literal and it contains obsolete characters 1627 rfc5322DomainLiteralObsoleteText = 71, 1628 1629 /// The IPv6 literal address contains the wrong number of groups 1630 rfc5322IpV6GroupCount = 72, 1631 1632 /// The IPv6 literal address contains too many :: sequences 1633 rfc5322IpV6TooManyDoubleColons = 73, 1634 1635 /// The IPv6 address contains an illegal group of characters 1636 rfc5322IpV6BadChar = 74, 1637 1638 /// The IPv6 address has too many groups 1639 rfc5322IpV6MaxGroups = 75, 1640 1641 /// IPv6 address starts with a single colon 1642 rfc5322IpV6ColonStart = 76, 1643 1644 /// IPv6 address ends with a single colon 1645 rfc5322IpV6ColonEnd = 77, 1646 1647 1648 1649 // Address is invalid for any purpose 1650 1651 /// A domain literal contains a character that is not allowed 1652 errorExpectingDomainText = 129, 1653 1654 /// Address has no local part 1655 errorNoLocalPart = 130, 1656 1657 /// Address has no domain part 1658 errorNoDomain = 131, 1659 1660 /// The address may not contain consecutive dots 1661 errorConsecutiveDots = 132, 1662 1663 /// Address contains text after a comment or Folding White Space 1664 errorTextAfterCommentFoldingWhitespace = 133, 1665 1666 /// Address contains text after a quoted string 1667 errorTextAfterQuotedString = 134, 1668 1669 /// Extra characters were found after the end of the domain literal 1670 errorTextAfterDomainLiteral = 135, 1671 1672 /// The address contains a character that is not allowed in a quoted pair 1673 errorExpectingQuotedPair = 136, 1674 1675 /// Address contains a character that is not allowed 1676 errorExpectingText = 137, 1677 1678 /// A quoted string contains a character that is not allowed 1679 errorExpectingQuotedText = 138, 1680 1681 /// A comment contains a character that is not allowed 1682 errorExpectingCommentText = 139, 1683 1684 /// The address cannot end with a backslash 1685 errorBackslashEnd = 140, 1686 1687 /// Neither part of the address may begin with a dot 1688 errorDotStart = 141, 1689 1690 /// Neither part of the address may end with a dot 1691 errorDotEnd = 142, 1692 1693 /// A domain or subdomain cannot begin with a hyphen 1694 errorDomainHyphenStart = 143, 1695 1696 /// A domain or subdomain cannot end with a hyphen 1697 errorDomainHyphenEnd = 144, 1698 1699 /// Unclosed quoted string 1700 errorUnclosedQuotedString = 145, 1701 1702 /// Unclosed comment 1703 errorUnclosedComment = 146, 1704 1705 /// Domain literal is missing its closing bracket 1706 errorUnclosedDomainLiteral = 147, 1707 1708 /// Folding White Space contains consecutive CRLF sequences 1709 errorFoldingWhitespaceCrflX2 = 148, 1710 1711 /// Folding White Space ends with a CRLF sequence 1712 errorFoldingWhitespaceCrLfEnd = 149, 1713 1714 /// Address contains a carriage return that is not followed by a line feed 1715 errorCrNoLf = 150, 1716 } 1717 1718 private: 1719 1720 // Email parts for the isEmail function 1721 enum EmailPart 1722 { 1723 // The local part of the email address, that is, the part before the @ sign 1724 componentLocalPart, 1725 1726 // The domain part of the email address, that is, the part after the @ sign. 1727 componentDomain, 1728 1729 componentLiteral, 1730 contextComment, 1731 contextFoldingWhitespace, 1732 contextQuotedString, 1733 contextQuotedPair, 1734 status 1735 } 1736 1737 // Miscellaneous string constants 1738 struct TokenImpl(Char) 1739 { 1740 enum : const(Char)[] 1741 { 1742 at = "@", 1743 backslash = `\`, 1744 dot = ".", 1745 doubleQuote = `"`, 1746 openParenthesis = "(", 1747 closeParenthesis = ")", 1748 openBracket = "[", 1749 closeBracket = "]", 1750 hyphen = "-", 1751 colon = ":", 1752 doubleColon = "::", 1753 space = " ", 1754 tab = "\t", 1755 cr = "\r", 1756 lf = "\n", 1757 ipV6Tag = "IPV6:", 1758 1759 // US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3) 1760 specials = `()<>[]:;@\\,."` 1761 } 1762 } 1763 1764 enum AsciiToken 1765 { 1766 horizontalTab = 9, 1767 unitSeparator = 31, 1768 delete_ = 127 1769 } 1770 1771 /* 1772 * Compare the two given strings lexicographically. An upper limit of the number of 1773 * characters, that will be used in the comparison, can be specified. Supports both 1774 * case-sensitive and case-insensitive comparison. 1775 * 1776 * Params: 1777 * s1 = the first string to be compared 1778 * s2 = the second string to be compared 1779 * length = the length of strings to be used in the comparison. 1780 * caseInsensitive = if true, a case-insensitive comparison will be made, 1781 * otherwise a case-sensitive comparison will be made 1782 * 1783 * Returns: (for $(D pred = "a < b")): 1784 * 1785 * $(BOOKTABLE, 1786 * $(TR $(TD $(D < 0)) $(TD $(D s1 < s2) )) 1787 * $(TR $(TD $(D = 0)) $(TD $(D s1 == s2))) 1788 * $(TR $(TD $(D > 0)) $(TD $(D s1 > s2))) 1789 * ) 1790 */ 1791 int compareFirstN(alias pred = "a < b", S1, S2) (S1 s1, S2 s2, size_t length) 1792 if (is(Unqual!(ElementType!(S1)) == dchar) && is(Unqual!(ElementType!(S2)) == dchar)) 1793 { 1794 import std.uni : icmp; 1795 auto s1End = length <= s1.length ? length : s1.length; 1796 auto s2End = length <= s2.length ? length : s2.length; 1797 1798 auto slice1 = s1[0 .. s1End]; 1799 auto slice2 = s2[0 .. s2End]; 1800 1801 return slice1.icmp(slice2); 1802 } 1803 1804 @safe unittest 1805 { 1806 assert("abc".compareFirstN("abcdef", 3) == 0); 1807 assert("abc".compareFirstN("Abc", 3) == 0); 1808 assert("abc".compareFirstN("abcdef", 6) < 0); 1809 assert("abcdef".compareFirstN("abc", 6) > 0); 1810 } 1811 1812 /* 1813 * Pops the last element of the given range and returns the element. 1814 * 1815 * Params: 1816 * range = the range to pop the element from 1817 * 1818 * Returns: the popped element 1819 */ 1820 ElementType!(A) pop (A) (ref A a) 1821 if (isDynamicArray!(A) && !isNarrowString!(A) && isMutable!(A) && !is(A == void[])) 1822 { 1823 auto e = a.back; 1824 a.popBack(); 1825 return e; 1826 } 1827 1828 @safe unittest 1829 { 1830 auto array = [0, 1, 2, 3]; 1831 auto result = array.pop(); 1832 1833 assert(array == [0, 1, 2]); 1834 assert(result == 3); 1835 } 1836 1837 /* 1838 * Returns the character at the given index as a string. The returned string will be a 1839 * slice of the original string. 1840 * 1841 * Params: 1842 * str = the string to get the character from 1843 * index = the index of the character to get 1844 * c = the character to return, or any other of the same length 1845 * 1846 * Returns: the character at the given index as a string 1847 */ 1848 const(T)[] get (T) (const(T)[] str, size_t index, dchar c) 1849 { 1850 import std.utf : codeLength; 1851 return str[index .. index + codeLength!(T)(c)]; 1852 } 1853 1854 @safe unittest 1855 { 1856 assert("abc".get(1, 'b') == "b"); 1857 assert("löv".get(1, 'ö') == "ö"); 1858 } 1859 1860 @safe unittest 1861 { 1862 assert("abc".get(1, 'b') == "b"); 1863 assert("löv".get(1, 'ö') == "ö"); 1864 } 1865