PageRenderTime 124ms CodeModel.GetById 19ms app.highlight 95ms RepoModel.GetById 1ms app.codeStats 1ms

/std/net/isemail.d

http://github.com/jcd/phobos
D | 2073 lines | 1336 code | 402 blank | 335 comment | 341 complexity | a1982edccdc56ec3ff1ce8370f71a4cd MD5 | raw file

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

Large files files are truncated, but you can click here to view the full file