/**
* Validates an email address according to RFCs 5321, 5322 and others.
*
* Authors: Dominic Sayers $(LT)dominic@sayers.cc$(GT), Jacob Carlborg
* Copyright: Dominic Sayers, Jacob Carlborg 2008-.
* Test schema documentation: Copyright © 2011, Daniel Marschall
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
* Dominic Sayers graciously granted permission to use the Boost license via email on Feb 22, 2011.
* Version: 3.0.13 - Version 3.0 of the original PHP implementation: $(LINK http://www.dominicsayers.com/isemail)
*
* Standards:
* $(UL
* $(LI RFC 5321)
* $(LI RFC 5322)
* )
*
* References:
* $(UL
* $(LI $(LINK http://www.dominicsayers.com/isemail))
* $(LI $(LINK http://tools.ietf.org/html/rfc5321))
* $(LI $(LINK http://tools.ietf.org/html/rfc5322))
* )
*
* Source: $(PHOBOSSRC std/net/isemail.d)
*/
module std.net.isemail;
import std.range.primitives : back, front, ElementType, popFront, popBack;
import std.traits;
import std.typecons : Flag, Yes, No;
/**
* Check that an email address conforms to RFCs 5321, 5322 and others.
*
* Distinguishes between a Mailbox as defined by RFC 5321 and an addr-spec as
* defined by RFC 5322. Depending on the context, either can be regarded as a
* valid email address.
*
* Note: The DNS check is currently not implemented.
*
* Params:
* email = The email address to check
* checkDNS = If `Yes.checkDns` then a DNS check for MX records will be made
* errorLevel = Determines the boundary between valid and invalid addresses.
* Status codes above this number will be returned as-is,
* status codes below will be returned as EmailStatusCode.valid.
* Thus the calling program can simply look for EmailStatusCode.valid
* if it is only interested in whether an address is valid or not. The
* $(D_PARAM errorLevel) will determine how "picky" isEmail() is about
* the address.
*
* If omitted or passed as EmailStatusCode.none then isEmail() will
* not perform any finer grained error checking and an address is
* either considered valid or not. Email status code will either be
* EmailStatusCode.valid or EmailStatusCode.error.
*
* Returns:
* An $(LREF EmailStatus), indicating the status of the email address.
*/
EmailStatus isEmail(Char)(const(Char)[] email, CheckDns checkDNS = No.checkDns,
EmailStatusCode errorLevel = EmailStatusCode.none)
if (isSomeChar!(Char))
{
import std.algorithm.iteration : uniq, filter, map;
import std.algorithm.searching : canFind, maxElement;
import std.array : array, split;
import std.conv : to;
import std.exception : enforce;
import std.string : indexOf, lastIndexOf;
import std.uni : isNumber;
alias tstring = const(Char)[];
alias Token = TokenImpl!(Char);
enum defaultThreshold = 16;
int threshold;
bool diagnose;
if (errorLevel == EmailStatusCode.any)
{
threshold = EmailStatusCode.valid;
diagnose = true;
}
else if (errorLevel == EmailStatusCode.none)
threshold = defaultThreshold;
else
{
diagnose = true;
switch (errorLevel)
{
case EmailStatusCode.warning: threshold = defaultThreshold; break;
case EmailStatusCode.error: threshold = EmailStatusCode.valid; break;
default: threshold = errorLevel;
}
}
auto returnStatus = [EmailStatusCode.valid];
auto context = EmailPart.componentLocalPart;
auto contextStack = [context];
auto contextPrior = context;
tstring token = "";
tstring tokenPrior = "";
tstring[EmailPart] parseData = [EmailPart.componentLocalPart : "", EmailPart.componentDomain : ""];
tstring[][EmailPart] atomList = [EmailPart.componentLocalPart : [""], EmailPart.componentDomain : [""]];
auto elementCount = 0;
auto elementLength = 0;
auto hyphenFlag = false;
auto endOrDie = false;
auto crlfCount = int.min; // int.min == not defined
foreach (ref i, e ; email)
{
token = email.get(i, e);
switch (context)
{
case EmailPart.componentLocalPart:
switch (token)
{
case Token.openParenthesis:
if (elementLength == 0)
returnStatus ~= elementCount == 0 ? EmailStatusCode.comment :
EmailStatusCode.deprecatedComment;
else
{
returnStatus ~= EmailStatusCode.comment;
endOrDie = true;
}
contextStack ~= context;
context = EmailPart.contextComment;
break;
case Token.dot:
if (elementLength == 0)
returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart :
EmailStatusCode.errorConsecutiveDots;
else
{
if (endOrDie)
returnStatus ~= EmailStatusCode.deprecatedLocalPart;
}
endOrDie = false;
elementLength = 0;
elementCount++;
parseData[EmailPart.componentLocalPart] ~= token;
if (elementCount >= atomList[EmailPart.componentLocalPart].length)
atomList[EmailPart.componentLocalPart] ~= "";
else
atomList[EmailPart.componentLocalPart][elementCount] = "";
break;
case Token.doubleQuote:
if (elementLength == 0)
{
returnStatus ~= elementCount == 0 ? EmailStatusCode.rfc5321QuotedString :
EmailStatusCode.deprecatedLocalPart;
parseData[EmailPart.componentLocalPart] ~= token;
atomList[EmailPart.componentLocalPart][elementCount] ~= token;
elementLength++;
endOrDie = true;
contextStack ~= context;
context = EmailPart.contextQuotedString;
}
else
returnStatus ~= EmailStatusCode.errorExpectingText;
break;
case Token.cr:
case Token.space:
case Token.tab:
if ((token == Token.cr) && ((++i == email.length) || (email.get(i, e) != Token.lf)))
{
returnStatus ~= EmailStatusCode.errorCrNoLf;
break;
}
if (elementLength == 0)
returnStatus ~= elementCount == 0 ? EmailStatusCode.foldingWhitespace :
EmailStatusCode.deprecatedFoldingWhitespace;
else
endOrDie = true;
contextStack ~= context;
context = EmailPart.contextFoldingWhitespace;
tokenPrior = token;
break;
case Token.at:
enforce(contextStack.length == 1, "Unexpected item on context stack");
if (parseData[EmailPart.componentLocalPart] == "")
returnStatus ~= EmailStatusCode.errorNoLocalPart;
else if (elementLength == 0)
returnStatus ~= EmailStatusCode.errorDotEnd;
else if (parseData[EmailPart.componentLocalPart].length > 64)
returnStatus ~= EmailStatusCode.rfc5322LocalTooLong;
else if (contextPrior == EmailPart.contextComment ||
contextPrior == EmailPart.contextFoldingWhitespace)
returnStatus ~= EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt;
context = EmailPart.componentDomain;
contextStack = [context];
elementCount = 0;
elementLength = 0;
endOrDie = false;
break;
default:
if (endOrDie)
{
switch (contextPrior)
{
case EmailPart.contextComment:
case EmailPart.contextFoldingWhitespace:
returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace;
break;
case EmailPart.contextQuotedString:
returnStatus ~= EmailStatusCode.errorTextAfterQuotedString;
break;
default:
throw new Exception("More text found where none is allowed, but "
~"unrecognised prior context: " ~ to!(string)(contextPrior));
}
}
else
{
contextPrior = context;
immutable c = token.front;
if (c < '!' || c > '~' || c == '\n' || Token.specials.canFind(token))
returnStatus ~= EmailStatusCode.errorExpectingText;
parseData[EmailPart.componentLocalPart] ~= token;
atomList[EmailPart.componentLocalPart][elementCount] ~= token;
elementLength++;
}
}
break;
case EmailPart.componentDomain:
switch (token)
{
case Token.openParenthesis:
if (elementLength == 0)
{
returnStatus ~= elementCount == 0 ?
EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt
: EmailStatusCode.deprecatedComment;
}
else
{
returnStatus ~= EmailStatusCode.comment;
endOrDie = true;
}
contextStack ~= context;
context = EmailPart.contextComment;
break;
case Token.dot:
if (elementLength == 0)
returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart :
EmailStatusCode.errorConsecutiveDots;
else if (hyphenFlag)
returnStatus ~= EmailStatusCode.errorDomainHyphenEnd;
else
{
if (elementLength > 63)
returnStatus ~= EmailStatusCode.rfc5322LabelTooLong;
}
endOrDie = false;
elementLength = 0;
elementCount++;
//atomList[EmailPart.componentDomain][elementCount] = "";
atomList[EmailPart.componentDomain] ~= "";
parseData[EmailPart.componentDomain] ~= token;
break;
case Token.openBracket:
if (parseData[EmailPart.componentDomain] == "")
{
endOrDie = true;
elementLength++;
contextStack ~= context;
context = EmailPart.componentLiteral;
parseData[EmailPart.componentDomain] ~= token;
atomList[EmailPart.componentDomain][elementCount] ~= token;
parseData[EmailPart.componentLiteral] = "";
}
else
returnStatus ~= EmailStatusCode.errorExpectingText;
break;
case Token.cr:
case Token.space:
case Token.tab:
if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
{
returnStatus ~= EmailStatusCode.errorCrNoLf;
break;
}
if (elementLength == 0)
{
returnStatus ~= elementCount == 0 ?
EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt
: EmailStatusCode.deprecatedFoldingWhitespace;
}
else
{
returnStatus ~= EmailStatusCode.foldingWhitespace;
endOrDie = true;
}
contextStack ~= context;
context = EmailPart.contextFoldingWhitespace;
tokenPrior = token;
break;
default:
if (endOrDie)
{
switch (contextPrior)
{
case EmailPart.contextComment:
case EmailPart.contextFoldingWhitespace:
returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace;
break;
case EmailPart.componentLiteral:
returnStatus ~= EmailStatusCode.errorTextAfterDomainLiteral;
break;
default:
throw new Exception("More text found where none is allowed, but "
~"unrecognised prior context: " ~ to!(string)(contextPrior));
}
}
immutable c = token.front;
hyphenFlag = false;
if (c < '!' || c > '~' || Token.specials.canFind(token))
returnStatus ~= EmailStatusCode.errorExpectingText;
else if (token == Token.hyphen)
{
if (elementLength == 0)
returnStatus ~= EmailStatusCode.errorDomainHyphenStart;
hyphenFlag = true;
}
else if (!((c > '/' && c < ':') || (c > '@' && c < '[') || (c > '`' && c < '{')))
returnStatus ~= EmailStatusCode.rfc5322Domain;
parseData[EmailPart.componentDomain] ~= token;
atomList[EmailPart.componentDomain][elementCount] ~= token;
elementLength++;
}
break;
case EmailPart.componentLiteral:
switch (token)
{
case Token.closeBracket:
if (returnStatus.maxElement() < EmailStatusCode.deprecated_)
{
auto maxGroups = 8;
size_t index = -1;
auto addressLiteral = parseData[EmailPart.componentLiteral];
const(Char)[] ipSuffix = matchIPSuffix(addressLiteral);
if (ipSuffix.length)
{
index = addressLiteral.length - ipSuffix.length;
if (index != 0)
addressLiteral = addressLiteral[0 .. index] ~ "0:0";
}
if (index == 0)
returnStatus ~= EmailStatusCode.rfc5321AddressLiteral;
else if (addressLiteral.compareFirstN(Token.ipV6Tag, 5))
returnStatus ~= EmailStatusCode.rfc5322DomainLiteral;
else
{
auto ipV6 = addressLiteral[5 .. $];
auto matchesIp = ipV6.split(Token.colon);
immutable groupCount = matchesIp.length;
index = ipV6.indexOf(Token.doubleColon);
if (index == -1)
{
if (groupCount != maxGroups)
returnStatus ~= EmailStatusCode.rfc5322IpV6GroupCount;
}
else
{
if (index != ipV6.lastIndexOf(Token.doubleColon))
returnStatus ~= EmailStatusCode.rfc5322IpV6TooManyDoubleColons;
else
{
if (index == 0 || index == (ipV6.length - 2))
maxGroups++;
if (groupCount > maxGroups)
returnStatus ~= EmailStatusCode.rfc5322IpV6MaxGroups;
else if (groupCount == maxGroups)
returnStatus ~= EmailStatusCode.rfc5321IpV6Deprecated;
}
}
if (ipV6[0 .. 1] == Token.colon && ipV6[1 .. 2] != Token.colon)
returnStatus ~= EmailStatusCode.rfc5322IpV6ColonStart;
else if (ipV6[$ - 1 .. $] == Token.colon && ipV6[$ - 2 .. $ - 1] != Token.colon)
returnStatus ~= EmailStatusCode.rfc5322IpV6ColonEnd;
else if (!matchesIp
.filter!(a => !isUpToFourHexChars(a))
.empty)
returnStatus ~= EmailStatusCode.rfc5322IpV6BadChar;
else
returnStatus ~= EmailStatusCode.rfc5321AddressLiteral;
}
}
else
returnStatus ~= EmailStatusCode.rfc5322DomainLiteral;
parseData[EmailPart.componentDomain] ~= token;
atomList[EmailPart.componentDomain][elementCount] ~= token;
elementLength++;
contextPrior = context;
context = contextStack.pop();
break;
case Token.backslash:
returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText;
contextStack ~= context;
context = EmailPart.contextQuotedPair;
break;
case Token.cr:
case Token.space:
case Token.tab:
if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
{
returnStatus ~= EmailStatusCode.errorCrNoLf;
break;
}
returnStatus ~= EmailStatusCode.foldingWhitespace;
contextStack ~= context;
context = EmailPart.contextFoldingWhitespace;
tokenPrior = token;
break;
default:
immutable c = token.front;
if (c > AsciiToken.delete_ || c == '\0' || token == Token.openBracket)
{
returnStatus ~= EmailStatusCode.errorExpectingDomainText;
break;
}
else if (c < '!' || c == AsciiToken.delete_ )
returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText;
parseData[EmailPart.componentLiteral] ~= token;
parseData[EmailPart.componentDomain] ~= token;
atomList[EmailPart.componentDomain][elementCount] ~= token;
elementLength++;
}
break;
case EmailPart.contextQuotedString:
switch (token)
{
case Token.backslash:
contextStack ~= context;
context = EmailPart.contextQuotedPair;
break;
case Token.cr:
case Token.tab:
if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
{
returnStatus ~= EmailStatusCode.errorCrNoLf;
break;
}
parseData[EmailPart.componentLocalPart] ~= Token.space;
atomList[EmailPart.componentLocalPart][elementCount] ~= Token.space;
elementLength++;
returnStatus ~= EmailStatusCode.foldingWhitespace;
contextStack ~= context;
context = EmailPart.contextFoldingWhitespace;
tokenPrior = token;
break;
case Token.doubleQuote:
parseData[EmailPart.componentLocalPart] ~= token;
atomList[EmailPart.componentLocalPart][elementCount] ~= token;
elementLength++;
contextPrior = context;
context = contextStack.pop();
break;
default:
immutable c = token.front;
if (c > AsciiToken.delete_ || c == '\0' || c == '\n')
returnStatus ~= EmailStatusCode.errorExpectingQuotedText;
else if (c < ' ' || c == AsciiToken.delete_)
returnStatus ~= EmailStatusCode.deprecatedQuotedText;
parseData[EmailPart.componentLocalPart] ~= token;
atomList[EmailPart.componentLocalPart][elementCount] ~= token;
elementLength++;
}
break;
case EmailPart.contextQuotedPair:
immutable c = token.front;
if (c > AsciiToken.delete_)
returnStatus ~= EmailStatusCode.errorExpectingQuotedPair;
else if (c < AsciiToken.unitSeparator && c != AsciiToken.horizontalTab || c == AsciiToken.delete_)
returnStatus ~= EmailStatusCode.deprecatedQuotedPair;
contextPrior = context;
context = contextStack.pop();
token = Token.backslash ~ token;
switch (context)
{
case EmailPart.contextComment: break;
case EmailPart.contextQuotedString:
parseData[EmailPart.componentLocalPart] ~= token;
atomList[EmailPart.componentLocalPart][elementCount] ~= token;
elementLength += 2;
break;
case EmailPart.componentLiteral:
parseData[EmailPart.componentDomain] ~= token;
atomList[EmailPart.componentDomain][elementCount] ~= token;
elementLength += 2;
break;
default:
throw new Exception("Quoted pair logic invoked in an invalid context: " ~ to!(string)(context));
}
break;
case EmailPart.contextComment:
switch (token)
{
case Token.openParenthesis:
contextStack ~= context;
context = EmailPart.contextComment;
break;
case Token.closeParenthesis:
contextPrior = context;
context = contextStack.pop();
break;
case Token.backslash:
contextStack ~= context;
context = EmailPart.contextQuotedPair;
break;
case Token.cr:
case Token.space:
case Token.tab:
if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
{
returnStatus ~= EmailStatusCode.errorCrNoLf;
break;
}
returnStatus ~= EmailStatusCode.foldingWhitespace;
contextStack ~= context;
context = EmailPart.contextFoldingWhitespace;
tokenPrior = token;
break;
default:
immutable c = token.front;
if (c > AsciiToken.delete_ || c == '\0' || c == '\n')
{
returnStatus ~= EmailStatusCode.errorExpectingCommentText;
break;
}
else if (c < ' ' || c == AsciiToken.delete_)
returnStatus ~= EmailStatusCode.deprecatedCommentText;
}
break;
case EmailPart.contextFoldingWhitespace:
if (tokenPrior == Token.cr)
{
if (token == Token.cr)
{
returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrflX2;
break;
}
if (crlfCount != int.min) // int.min == not defined
{
if (++crlfCount > 1)
returnStatus ~= EmailStatusCode.deprecatedFoldingWhitespace;
}
else
crlfCount = 1;
}
switch (token)
{
case Token.cr:
if (++i == email.length || email.get(i, e) != Token.lf)
returnStatus ~= EmailStatusCode.errorCrNoLf;
break;
case Token.space:
case Token.tab:
break;
default:
if (tokenPrior == Token.cr)
{
returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd;
break;
}
crlfCount = int.min; // int.min == not defined
contextPrior = context;
context = contextStack.pop();
i--;
break;
}
tokenPrior = token;
break;
default:
throw new Exception("Unkown context: " ~ to!(string)(context));
}
if (returnStatus.maxElement() > EmailStatusCode.rfc5322)
break;
}
if (returnStatus.maxElement() < EmailStatusCode.rfc5322)
{
if (context == EmailPart.contextQuotedString)
returnStatus ~= EmailStatusCode.errorUnclosedQuotedString;
else if (context == EmailPart.contextQuotedPair)
returnStatus ~= EmailStatusCode.errorBackslashEnd;
else if (context == EmailPart.contextComment)
returnStatus ~= EmailStatusCode.errorUnclosedComment;
else if (context == EmailPart.componentLiteral)
returnStatus ~= EmailStatusCode.errorUnclosedDomainLiteral;
else if (token == Token.cr)
returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd;
else if (parseData[EmailPart.componentDomain] == "")
returnStatus ~= EmailStatusCode.errorNoDomain;
else if (elementLength == 0)
returnStatus ~= EmailStatusCode.errorDotEnd;
else if (hyphenFlag)
returnStatus ~= EmailStatusCode.errorDomainHyphenEnd;
else if (parseData[EmailPart.componentDomain].length > 255)
returnStatus ~= EmailStatusCode.rfc5322DomainTooLong;
else if ((parseData[EmailPart.componentLocalPart] ~ Token.at ~ parseData[EmailPart.componentDomain]).length >
254)
returnStatus ~= EmailStatusCode.rfc5322TooLong;
else if (elementLength > 63)
returnStatus ~= EmailStatusCode.rfc5322LabelTooLong;
}
auto dnsChecked = false;
if (checkDNS == Yes.checkDns && returnStatus.maxElement() < EmailStatusCode.dnsWarning)
{
assert(false, "DNS check is currently not implemented");
}
if (!dnsChecked && returnStatus.maxElement() < EmailStatusCode.dnsWarning)
{
if (elementCount == 0)
returnStatus ~= EmailStatusCode.rfc5321TopLevelDomain;
if (isNumber(atomList[EmailPart.componentDomain][elementCount].front))
returnStatus ~= EmailStatusCode.rfc5321TopLevelDomainNumeric;
}
returnStatus = array(uniq(returnStatus));
auto finalStatus = returnStatus.maxElement();
if (returnStatus.length != 1)
returnStatus.popFront();
parseData[EmailPart.status] = to!(tstring)(returnStatus);
if (finalStatus < threshold)
finalStatus = EmailStatusCode.valid;
if (!diagnose)
finalStatus = finalStatus < threshold ? EmailStatusCode.valid : EmailStatusCode.error;
auto valid = finalStatus == EmailStatusCode.valid;
tstring localPart = "";
tstring domainPart = "";
if (auto value = EmailPart.componentLocalPart in parseData)
localPart = *value;
if (auto value = EmailPart.componentDomain in parseData)
domainPart = *value;
return EmailStatus(valid, to!(string)(localPart), to!(string)(domainPart), finalStatus);
}
@safe unittest
{
assert(`test.test@iana.org`.isEmail(No.checkDns).statusCode == EmailStatusCode.valid);
assert(`test.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.valid);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(No.checkDns,
EmailStatusCode.none).statusCode == EmailStatusCode.valid);
assert(`test`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.error);
assert(`(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.error);
assert(``.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
assert(`test`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
assert(`@`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart);
assert(`test@`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
// assert(`test@io`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid,
// `io. currently has an MX-record (Feb 2011). Some DNS setups seem to find it, some don't.`
// ` If you don't see the MX for io. then try setting your DNS server to 8.8.8.8 (the Google DNS server)`);
assert(`@io`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart,
`io. currently has an MX-record (Feb 2011)`);
assert(`@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart);
assert(`test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`test@nominet.org.uk`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`test@about.museum`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`a@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
//assert(`test@e.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
// DNS check is currently not implemented
//assert(`test@iana.a`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
// DNS check is currently not implemented
assert(`test.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart);
assert(`test.@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd);
assert(`test .. iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorConsecutiveDots);
assert(`test_exa-mple.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
assert("!#$%&`*+/=?^`{|}~@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`test\@test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert(`123@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`test@123.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`test@iana.123`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321TopLevelDomainNumeric);
assert(`test@255.255.255.255`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321TopLevelDomainNumeric);
assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong);
// assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(No.checkDns,
// EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
// DNS check is currently not implemented
assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LabelTooLong);
assert(`test@mason-dixon.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
assert(`test@-iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorDomainHyphenStart);
assert(`test@iana-.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorDomainHyphenEnd);
assert(`test@g--a.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
//assert(`test@iana.co-uk`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
//EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented
assert(`test@.iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart);
assert(`test@iana.org.`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd);
assert(`test@iana .. com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorConsecutiveDots);
//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`
// `.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`
// `.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 ==
// EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented
// assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`
// `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`
// `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi`.isEmail(No.checkDns,
// EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
// DNS check is currently not implemented
assert((`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`~
`abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~
`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij`).isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong);
assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~
`abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~
`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij`).isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong);
assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~
`abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~
`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk`).isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322DomainTooLong);
assert(`"test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321QuotedString);
assert(`""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
assert(`"""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
assert(`"\a"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
assert(`"\""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
assert(`"\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedQuotedString);
assert(`"\\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
assert(`test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
assert(`"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedQuotedString);
assert(`"test"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorTextAfterQuotedString);
assert(`test"text"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert(`"test""test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert(`"test"."test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedLocalPart);
assert(`"test\ test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321QuotedString);
assert(`"test".test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedLocalPart);
assert("\"test\u0000\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingQuotedText);
assert("\"test\\\u0000\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedQuotedPair);
assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj"@iana.org`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong,
`Quotes are still part of the length restriction`);
assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\h"@iana.org`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong,
`Quoted pair is still part of the length restriction`);
assert(`test@[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321AddressLiteral);
assert(`test@a[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert(`test@[255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteral);
assert(`test@[255.255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteral);
assert(`test@[255.255.255.256]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteral);
assert(`test@[1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteral);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322IpV6GroupCount);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode
== EmailStatusCode.rfc5321AddressLiteral);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6BadChar);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321IpV6Deprecated);
assert(`test@[IPv6:1111:2222:3333:4444:5555::8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321AddressLiteral);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups);
assert(`test@[IPv6::3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322IpV6ColonStart);
assert(`test@[IPv6:::3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321AddressLiteral);
assert(`test@[IPv6:1111::4444:5555::8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322IpV6TooManyDoubleColons);
assert(`test@[IPv6:::]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5321AddressLiteral);
assert(`test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
assert(`test@[IPv6:1111:2222:3333:4444::255.255.255.255]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral);
assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups);
assert(`test@[IPv6:1111:2222:3333:4444:::255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode
== EmailStatusCode.rfc5322IpV6TooManyDoubleColons);
assert(`test@[IPv6::255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322IpV6ColonStart);
assert(` test @iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
assert(`test@ iana .com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
assert(`test . test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedFoldingWhitespace);
assert("\u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.foldingWhitespace, `Folding whitespace`);
assert("\u000D\u000A \u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP`~
` -- only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`);
assert(`(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.comment);
assert(`((comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedComment);
assert(`(comment(comment))test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.comment);
assert(`test@(comment)iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
assert(`test(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorTextAfterCommentFoldingWhitespace);
assert(`test@(comment)[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
assert(`(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.comment);
assert(`test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
assert((`(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyz`~
`abcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.`~
`abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu`).isEmail(No.checkDns,
EmailStatusCode.any).statusCode == EmailStatusCode.comment);
assert("test@iana.org\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert(`test@xn--hxajbheg2az3al.xn--jxalpdlp`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.valid, `A valid IDN from ICANN's `~
`IDN TLD evaluation gateway`);
assert(`xn--test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid,
`RFC 3490: "unless the email standards are revised to invite the use of IDNA for local parts, a domain label`~
` that holds the local part of an email address SHOULD NOT begin with the ACE prefix, and even if it does,`~
` it is to be interpreted literally as a local part that happens to begin with the ACE prefix"`);
assert(`test@iana.org-`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorDomainHyphenEnd);
assert(`"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedQuotedString);
assert(`(test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedComment);
assert(`test@(iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedComment);
assert(`test@[1.2.3.4`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedDomainLiteral);
assert(`"test\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedQuotedString);
assert(`(comment\)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedComment);
assert(`test@iana.org(comment\)`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedComment);
assert(`test@iana.org(comment\`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorBackslashEnd);
assert(`test@[RFC-5322-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteral);
assert(`test@[RFC-5322]-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorTextAfterDomainLiteral);
assert(`test@[RFC-5322-[domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingDomainText);
assert("test@[RFC-5322-\\\u0007-domain-literal]".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteralObsoleteText, `obs-dtext and obs-qp`);
assert("test@[RFC-5322-\\\u0009-domain-literal]".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteralObsoleteText);
assert(`test@[RFC-5322-\]-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteralObsoleteText);
assert(`test@[RFC-5322-domain-literal\]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorUnclosedDomainLiteral);
assert(`test@[RFC-5322-domain-literal\`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorBackslashEnd);
assert(`test@[RFC 5322 domain literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteral, `Spaces are FWS in a domain literal`);
assert(`test@[RFC-5322-domain-literal] (comment)`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322DomainLiteral);
assert("\u007F@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert("test@\u007F.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert("\"\u007F\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedQuotedText);
assert("\"\\\u007F\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedQuotedPair);
assert("(\u007F)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedCommentText);
assert("test@iana.org\u000D".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
`No LF after the CR`);
assert("\u000Dtest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
`No LF after the CR`);
assert("\"\u000Dtest\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorCrNoLf, `No LF after the CR`);
assert("(\u000D)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
`No LF after the CR`);
assert("(\u000D".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
`No LF after the CR`);
assert("test@iana.org(\u000D)".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
`No LF after the CR`);
assert("\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert("\"\u000A\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingQuotedText);
assert("\"\\\u000A\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedQuotedPair);
assert("(\u000A)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingCommentText);
assert("\u0007@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert("test@\u0007.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingText);
assert("\"\u0007\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedQuotedText);
assert("\"\\\u0007\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedQuotedPair);
assert("(\u0007)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedCommentText);
assert("\u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`);
assert("\u000D\u000A \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`);
assert(" \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`);
assert(" \u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.foldingWhitespace, `FWS`);
assert(" \u000D\u000A \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`);
assert(" \u000D\u000A\u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`);
assert(" \u000D\u000A\u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`);
assert("test@iana.org\u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.foldingWhitespace, `FWS`);
assert("test@iana.org\u000D\u000A \u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP -- `~
`only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`);
assert("test@iana.org\u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`);
assert("test@iana.org\u000D\u000A \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`);
assert("test@iana.org \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`);
assert("test@iana.org \u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.foldingWhitespace, `FWS`);
assert("test@iana.org \u000D\u000A \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`);
assert("test@iana.org \u000D\u000A\u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`);
assert("test@iana.org \u000D\u000A\u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`);
assert(" test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace);
assert(`test@iana.org `.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace);
assert(`test@[IPv6:1::2:]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.rfc5322IpV6ColonEnd);
assert("\"test\\\u00A9\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.errorExpectingQuotedPair);
assert(`test@iana/icann.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322Domain);
assert(`test.(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
EmailStatusCode.deprecatedComment);
assert(`test@org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321TopLevelDomain);
// assert(`test@test.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode ==
//EmailStatusCode.dnsWarningNoMXRecord, `test.com has an A-record but not an MX-record`);
// DNS check is currently not implemented
//
// assert(`test@nic.no`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord,
// `nic.no currently has no MX-records or A-records (Feb 2011). If you are seeing an A-record for nic.io then`
// ` try setting your DNS server to 8.8.8.8 (the Google DNS server) - your DNS server may be faking an A-record`
// ` (OpenDNS does this, for instance).`); // DNS check is currently not implemented
}
// https://issues.dlang.org/show_bug.cgi?id=17217
@safe unittest
{
wstring a = `test.test@iana.org`w;
dstring b = `test.test@iana.org`d;
const(wchar)[] c = `test.test@iana.org`w;
const(dchar)[] d = `test.test@iana.org`d;
assert(a.isEmail(No.checkDns).statusCode == EmailStatusCode.valid);
assert(b.isEmail(No.checkDns).statusCode == EmailStatusCode.valid);
assert(c.isEmail(No.checkDns).statusCode == EmailStatusCode.valid);
assert(d.isEmail(No.checkDns).statusCode == EmailStatusCode.valid);
}
/**
* Flag for indicating if the isEmail function should perform a DNS check or not.
*
* If set to `CheckDns.no`, isEmail does not perform DNS checking.
*
* Otherwise if set to `CheckDns.yes`, isEmail performs DNS checking.
*/
alias CheckDns = Flag!"checkDns";
/// Represents the status of an email address
struct EmailStatus
{
private
{
bool valid_;
string localPart_;
string domainPart_;
EmailStatusCode statusCode_;
}
/// Self aliases to a `bool` representing if the email is valid or not
alias valid this;
/*
* Params:
* valid = indicates if the email address is valid or not
* localPart = the local part of the email address
* domainPart = the domain part of the email address
* statusCode = the status code
*/
private this (bool valid, string localPart, string domainPart, EmailStatusCode statusCode) @safe @nogc pure nothrow
{
this.valid_ = valid;
this.localPart_ = localPart;
this.domainPart_ = domainPart;
this.statusCode_ = statusCode;
}
/// Returns: If the email address is valid or not.
@property bool valid() const @safe @nogc pure nothrow scope
{
return valid_;
}
/// Returns: The local part of the email address, that is, the part before the @ sign.
@property string localPart() const @safe @nogc pure nothrow return scope
{
return localPart_;
}
/// Returns: The domain part of the email address, that is, the part after the @ sign.
@property string domainPart() const @safe @nogc pure nothrow return scope
{
return domainPart_;
}
/// Returns: The email status code
@property EmailStatusCode statusCode() const @safe @nogc pure nothrow scope
{
return statusCode_;
}
/// Returns: A describing string of the status code
@property string status() const @safe @nogc pure nothrow scope
{
return statusCodeDescription(statusCode_);
}
/// Returns: A textual representation of the email status
string toString() const @safe pure scope
{
import std.format : format;
return format("EmailStatus\n{\n\tvalid: %s\n\tlocalPart: %s\n\tdomainPart: %s\n\tstatusCode: %s\n}", valid,
localPart, domainPart, statusCode);
}
}
/**
* Params:
* statusCode = The $(LREF EmailStatusCode) to read
* Returns:
* A detailed string describing the given status code
*/
string statusCodeDescription(EmailStatusCode statusCode) @safe @nogc pure nothrow
{
final switch (statusCode)
{
// Categories
case EmailStatusCode.validCategory: return "Address is valid";
case EmailStatusCode.dnsWarning: return "Address is valid but a DNS check was not successful";
case EmailStatusCode.rfc5321: return "Address is valid for SMTP but has unusual elements";
case EmailStatusCode.cFoldingWhitespace: return "Address is valid within the message but cannot be used"~
" unmodified for the envelope";
case EmailStatusCode.deprecated_: return "Address contains deprecated elements but may still be valid in"~
" restricted contexts";
case EmailStatusCode.rfc5322: return "The address is only valid according to the broad definition of RFC 5322."~
" It is otherwise invalid";
case EmailStatusCode.any: return "";
case EmailStatusCode.none: return "";
case EmailStatusCode.warning: return "";
case EmailStatusCode.error: return "Address is invalid for any purpose";
// Diagnoses
case EmailStatusCode.valid: return "Address is valid";
// Address is valid but a DNS check was not successful
case EmailStatusCode.dnsWarningNoMXRecord: return "Could not find an MX record for this domain but an A-record"~
" does exist";
case EmailStatusCode.dnsWarningNoRecord: return "Could not find an MX record or an A-record for this domain";
// Address is valid for SMTP but has unusual elements
case EmailStatusCode.rfc5321TopLevelDomain: return "Address is valid but at a Top Level Domain";
case EmailStatusCode.rfc5321TopLevelDomainNumeric: return "Address is valid but the Top Level Domain begins"~
" with a number";
case EmailStatusCode.rfc5321QuotedString: return "Address is valid but contains a quoted string";
case EmailStatusCode.rfc5321AddressLiteral: return "Address is valid but at a literal address not a domain";
case EmailStatusCode.rfc5321IpV6Deprecated: return "Address is valid but contains a :: that only elides one"~
" zero group";
// Address is valid within the message but cannot be used unmodified for the envelope
case EmailStatusCode.comment: return "Address contains comments";
case EmailStatusCode.foldingWhitespace: return "Address contains Folding White Space";
// Address contains deprecated elements but may still be valid in restricted contexts
case EmailStatusCode.deprecatedLocalPart: return "The local part is in a deprecated form";
case EmailStatusCode.deprecatedFoldingWhitespace: return "Address contains an obsolete form of"~
" Folding White Space";
case EmailStatusCode.deprecatedQuotedText: return "A quoted string contains a deprecated character";
case EmailStatusCode.deprecatedQuotedPair: return "A quoted pair contains a deprecated character";
case EmailStatusCode.deprecatedComment: return "Address contains a comment in a position that is deprecated";
case EmailStatusCode.deprecatedCommentText: return "A comment contains a deprecated character";
case EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt: return "Address contains a comment or"~
" Folding White Space around the @ sign";
// The address is only valid according to the broad definition of RFC 5322
case EmailStatusCode.rfc5322Domain: return "Address is RFC 5322 compliant but contains domain characters that"~
" are not allowed by DNS";
case EmailStatusCode.rfc5322TooLong: return "Address is too long";
case EmailStatusCode.rfc5322LocalTooLong: return "The local part of the address is too long";
case EmailStatusCode.rfc5322DomainTooLong: return "The domain part is too long";
case EmailStatusCode.rfc5322LabelTooLong: return "The domain part contains an element that is too long";
case EmailStatusCode.rfc5322DomainLiteral: return "The domain literal is not a valid RFC 5321 address literal";
case EmailStatusCode.rfc5322DomainLiteralObsoleteText: return "The domain literal is not a valid RFC 5321"~
" address literal and it contains obsolete characters";
case EmailStatusCode.rfc5322IpV6GroupCount:
return "The IPv6 literal address contains the wrong number of groups";
case EmailStatusCode.rfc5322IpV6TooManyDoubleColons:
return "The IPv6 literal address contains too many :: sequences";
case EmailStatusCode.rfc5322IpV6BadChar: return "The IPv6 address contains an illegal group of characters";
case EmailStatusCode.rfc5322IpV6MaxGroups: return "The IPv6 address has too many groups";
case EmailStatusCode.rfc5322IpV6ColonStart: return "IPv6 address starts with a single colon";
case EmailStatusCode.rfc5322IpV6ColonEnd: return "IPv6 address ends with a single colon";
// Address is invalid for any purpose
case EmailStatusCode.errorExpectingDomainText:
return "A domain literal contains a character that is not allowed";
case EmailStatusCode.errorNoLocalPart: return "Address has no local part";
case EmailStatusCode.errorNoDomain: return "Address has no domain part";
case EmailStatusCode.errorConsecutiveDots: return "The address may not contain consecutive dots";
case EmailStatusCode.errorTextAfterCommentFoldingWhitespace:
return "Address contains text after a comment or Folding White Space";
case EmailStatusCode.errorTextAfterQuotedString: return "Address contains text after a quoted string";
case EmailStatusCode.errorTextAfterDomainLiteral: return "Extra characters were found after the end of"~
" the domain literal";
case EmailStatusCode.errorExpectingQuotedPair:
return "The address contains a character that is not allowed in a quoted pair";
case EmailStatusCode.errorExpectingText: return "Address contains a character that is not allowed";
case EmailStatusCode.errorExpectingQuotedText:
return "A quoted string contains a character that is not allowed";
case EmailStatusCode.errorExpectingCommentText: return "A comment contains a character that is not allowed";
case EmailStatusCode.errorBackslashEnd: return "The address cannot end with a backslash";
case EmailStatusCode.errorDotStart: return "Neither part of the address may begin with a dot";
case EmailStatusCode.errorDotEnd: return "Neither part of the address may end with a dot";
case EmailStatusCode.errorDomainHyphenStart: return "A domain or subdomain cannot begin with a hyphen";
case EmailStatusCode.errorDomainHyphenEnd: return "A domain or subdomain cannot end with a hyphen";
case EmailStatusCode.errorUnclosedQuotedString: return "Unclosed quoted string";
case EmailStatusCode.errorUnclosedComment: return "Unclosed comment";
case EmailStatusCode.errorUnclosedDomainLiteral: return "Domain literal is missing its closing bracket";
case EmailStatusCode.errorFoldingWhitespaceCrflX2:
return "Folding White Space contains consecutive CRLF sequences";
case EmailStatusCode.errorFoldingWhitespaceCrLfEnd: return "Folding White Space ends with a CRLF sequence";
case EmailStatusCode.errorCrNoLf:
return "Address contains a carriage return that is not followed by a line feed";
}
}
/**
* An email status code, indicating if an email address is valid or not.
* If it is invalid it also indicates why.
*/
enum EmailStatusCode
{
// Categories
/// Address is valid
validCategory = 1,
/// Address is valid but a DNS check was not successful
dnsWarning = 7,
/// Address is valid for SMTP but has unusual elements
rfc5321 = 15,
/// Address is valid within the message but cannot be used unmodified for the envelope
cFoldingWhitespace = 31,
/// Address contains deprecated elements but may still be valid in restricted contexts
deprecated_ = 63,
/// The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid
rfc5322 = 127,
/**
* All finer grained error checking is turned on. Address containing errors or
* warnings is considered invalid. A specific email status code will be
* returned indicating the error/warning of the address.
*/
any = 252,
/**
* Address is either considered valid or not, no finer grained error checking
* is performed. Returned email status code will be either Error or Valid.
*/
none = 253,
/**
* Address containing warnings is considered valid, that is,
* any status code below 16 is considered valid.
*/
warning = 254,
/// Address is invalid for any purpose
error = 255,
// Diagnoses
/// Address is valid
valid = 0,
// Address is valid but a DNS check was not successful
/// Could not find an MX record for this domain but an A-record does exist
dnsWarningNoMXRecord = 5,
/// Could not find an MX record or an A-record for this domain
dnsWarningNoRecord = 6,
// Address is valid for SMTP but has unusual elements
/// Address is valid but at a Top Level Domain
rfc5321TopLevelDomain = 9,
/// Address is valid but the Top Level Domain begins with a number
rfc5321TopLevelDomainNumeric = 10,
/// Address is valid but contains a quoted string
rfc5321QuotedString = 11,
/// Address is valid but at a literal address not a domain
rfc5321AddressLiteral = 12,
/// Address is valid but contains a :: that only elides one zero group
rfc5321IpV6Deprecated = 13,
// Address is valid within the message but cannot be used unmodified for the envelope
/// Address contains comments
comment = 17,
/// Address contains Folding White Space
foldingWhitespace = 18,
// Address contains deprecated elements but may still be valid in restricted contexts
/// The local part is in a deprecated form
deprecatedLocalPart = 33,
/// Address contains an obsolete form of Folding White Space
deprecatedFoldingWhitespace = 34,
/// A quoted string contains a deprecated character
deprecatedQuotedText = 35,
/// A quoted pair contains a deprecated character
deprecatedQuotedPair = 36,
/// Address contains a comment in a position that is deprecated
deprecatedComment = 37,
/// A comment contains a deprecated character
deprecatedCommentText = 38,
/// Address contains a comment or Folding White Space around the @ sign
deprecatedCommentFoldingWhitespaceNearAt = 49,
// The address is only valid according to the broad definition of RFC 5322
/// Address is RFC 5322 compliant but contains domain characters that are not allowed by DNS
rfc5322Domain = 65,
/// Address is too long
rfc5322TooLong = 66,
/// The local part of the address is too long
rfc5322LocalTooLong = 67,
/// The domain part is too long
rfc5322DomainTooLong = 68,
/// The domain part contains an element that is too long
rfc5322LabelTooLong = 69,
/// The domain literal is not a valid RFC 5321 address literal
rfc5322DomainLiteral = 70,
/// The domain literal is not a valid RFC 5321 address literal and it contains obsolete characters
rfc5322DomainLiteralObsoleteText = 71,
/// The IPv6 literal address contains the wrong number of groups
rfc5322IpV6GroupCount = 72,
/// The IPv6 literal address contains too many :: sequences
rfc5322IpV6TooManyDoubleColons = 73,
/// The IPv6 address contains an illegal group of characters
rfc5322IpV6BadChar = 74,
/// The IPv6 address has too many groups
rfc5322IpV6MaxGroups = 75,
/// IPv6 address starts with a single colon
rfc5322IpV6ColonStart = 76,
/// IPv6 address ends with a single colon
rfc5322IpV6ColonEnd = 77,
// Address is invalid for any purpose
/// A domain literal contains a character that is not allowed
errorExpectingDomainText = 129,
/// Address has no local part
errorNoLocalPart = 130,
/// Address has no domain part
errorNoDomain = 131,
/// The address may not contain consecutive dots
errorConsecutiveDots = 132,
/// Address contains text after a comment or Folding White Space
errorTextAfterCommentFoldingWhitespace = 133,
/// Address contains text after a quoted string
errorTextAfterQuotedString = 134,
/// Extra characters were found after the end of the domain literal
errorTextAfterDomainLiteral = 135,
/// The address contains a character that is not allowed in a quoted pair
errorExpectingQuotedPair = 136,
/// Address contains a character that is not allowed
errorExpectingText = 137,
/// A quoted string contains a character that is not allowed
errorExpectingQuotedText = 138,
/// A comment contains a character that is not allowed
errorExpectingCommentText = 139,
/// The address cannot end with a backslash
errorBackslashEnd = 140,
/// Neither part of the address may begin with a dot
errorDotStart = 141,
/// Neither part of the address may end with a dot
errorDotEnd = 142,
/// A domain or subdomain cannot begin with a hyphen
errorDomainHyphenStart = 143,
/// A domain or subdomain cannot end with a hyphen
errorDomainHyphenEnd = 144,
/// Unclosed quoted string
errorUnclosedQuotedString = 145,
/// Unclosed comment
errorUnclosedComment = 146,
/// Domain literal is missing its closing bracket
errorUnclosedDomainLiteral = 147,
/// Folding White Space contains consecutive CRLF sequences
errorFoldingWhitespaceCrflX2 = 148,
/// Folding White Space ends with a CRLF sequence
errorFoldingWhitespaceCrLfEnd = 149,
/// Address contains a carriage return that is not followed by a line feed
errorCrNoLf = 150,
}
private:
// Email parts for the isEmail function
enum EmailPart
{
// The local part of the email address, that is, the part before the @ sign
componentLocalPart,
// The domain part of the email address, that is, the part after the @ sign.
componentDomain,
componentLiteral,
contextComment,
contextFoldingWhitespace,
contextQuotedString,
contextQuotedPair,
status
}
// Miscellaneous string constants
struct TokenImpl(Char)
{
enum : const(Char)[]
{
at = "@",
backslash = `\`,
dot = ".",
doubleQuote = `"`,
openParenthesis = "(",
closeParenthesis = ")",
openBracket = "[",
closeBracket = "]",
hyphen = "-",
colon = ":",
doubleColon = "::",
space = " ",
tab = "\t",
cr = "\r",
lf = "\n",
ipV6Tag = "IPV6:",
// US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3)
specials = `()<>[]:;@\\,."`
}
}
enum AsciiToken
{
horizontalTab = 9,
unitSeparator = 31,
delete_ = 127
}
/*
* Compare the two given strings lexicographically. An upper limit of the number of
* characters, that will be used in the comparison, can be specified. Supports both
* case-sensitive and case-insensitive comparison.
*
* Params:
* s1 = the first string to be compared
* s2 = the second string to be compared
* length = the length of strings to be used in the comparison.
* caseInsensitive = if true, a case-insensitive comparison will be made,
* otherwise a case-sensitive comparison will be made
*
* Returns: (for $(D pred = "a < b")):
*
* $(BOOKTABLE,
* $(TR $(TD $(D < 0)) $(TD $(D s1 < s2) ))
* $(TR $(TD $(D = 0)) $(TD $(D s1 == s2)))
* $(TR $(TD $(D > 0)) $(TD $(D s1 > s2)))
* )
*/
int compareFirstN(alias pred = "a < b", S1, S2) (S1 s1, S2 s2, size_t length)
if (is(immutable ElementType!(S1) == immutable dchar) && is(immutable ElementType!(S2) == immutable dchar))
{
import std.uni : icmp;
auto s1End = length <= s1.length ? length : s1.length;
auto s2End = length <= s2.length ? length : s2.length;
auto slice1 = s1[0 .. s1End];
auto slice2 = s2[0 .. s2End];
return slice1.icmp(slice2);
}
@safe unittest
{
assert("abc".compareFirstN("abcdef", 3) == 0);
assert("abc".compareFirstN("Abc", 3) == 0);
assert("abc".compareFirstN("abcdef", 6) < 0);
assert("abcdef".compareFirstN("abc", 6) > 0);
}
/*
* Pops the last element of the given range and returns the element.
*
* Params:
* range = the range to pop the element from
*
* Returns: the popped element
*/
ElementType!(A) pop (A) (ref A a)
if (isDynamicArray!(A) && !isNarrowString!(A) && isMutable!(A) && !is(A == void[]))
{
auto e = a.back;
a.popBack();
return e;
}
@safe unittest
{
auto array = [0, 1, 2, 3];
auto result = array.pop();
assert(array == [0, 1, 2]);
assert(result == 3);
}
/*
* Returns the character at the given index as a string. The returned string will be a
* slice of the original string.
*
* Params:
* str = the string to get the character from
* index = the index of the character to get
* c = the character to return, or any other of the same length
*
* Returns: the character at the given index as a string
*/
const(T)[] get (T) (const(T)[] str, size_t index, dchar c)
{
import std.utf : codeLength;
return str[index .. index + codeLength!(T)(c)];
}
@safe unittest
{
assert("abc".get(1, 'b') == "b");
assert("löv".get(1, 'ö') == "ö");
}
@safe unittest
{
assert("abc".get(1, 'b') == "b");
assert("löv".get(1, 'ö') == "ö");
}
/+
Replacement for:
---
static fourChars = ctRegex!(`^[0-9A-Fa-f]{0,4}$`.to!(const(Char)[]));
...
a => a.matchFirst(fourChars).empty
---
+/
bool isUpToFourHexChars(Char)(scope const(Char)[] s)
{
import std.ascii : isHexDigit;
if (s.length > 4) return false;
foreach (c; s)
if (!isHexDigit(c)) return false;
return true;
}
@nogc nothrow pure @safe unittest
{
assert(!isUpToFourHexChars("12345"));
assert(!isUpToFourHexChars("defg"));
assert(isUpToFourHexChars("1A0a"));
}
/+
Replacement for:
---
static ipRegex = ctRegex!(`\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}`~
`(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`.to!(const(Char)[]));
...
auto matchesIp = addressLiteral.matchAll(ipRegex).map!(a => a.hit).array;
----
Note that only the first item of "matchAll" was ever used in practice
so we can return `const(Char)[]` instead of `const(Char)[][]` using a
zero-length string to indicate no match.
+/
const(Char)[] matchIPSuffix(Char)(return scope const(Char)[] s) @nogc nothrow pure @safe
{
size_t end = s.length;
if (end < 7) return null;
// Check the first three `[.]\d{1,3}`
foreach (_; 0 .. 3)
{
size_t start = void;
if (end >= 2 && s[end-2] == '.')
start = end - 2;
else if (end >= 3 && s[end-3] == '.')
start = end - 3;
else if (end >= 4 && s[end-4] == '.')
start = end - 4;
else
return null;
uint x = 0;
foreach (i; start + 1 .. end)
{
uint c = cast(uint) s[i] - '0';
if (c > 9) return null;
x = x * 10 + c;
}
if (x > 255) return null;
end = start;
}
// Check the final `\d{1,3}`.
if (end < 1) return null;
size_t start = end - 1;
uint x = cast(uint) s[start] - '0';
if (x > 9) return null;
if (start > 0 && cast(uint) s[start-1] - '0' <= 9)
{
--start;
x += 10 * (cast(uint) s[start] - '0');
if (start > 0 && cast(uint) s[start-1] - '0' <= 9)
{
--start;
x += 100 * (cast(uint) s[start] - '0');
}
}
if (x > 255) return null;
// Must either be at start of string or preceded by a non-word character.
// (TO DETERMINE: is the definition of "word character" ASCII only?)
if (start == 0) return s;
const b = s[start - 1];
import std.ascii : isAlphaNum;
if (isAlphaNum(b) || b == '_') return null;
return s[start .. $];
}
@nogc nothrow pure @safe unittest
{
assert(matchIPSuffix("255.255.255.255") == "255.255.255.255");
assert(matchIPSuffix("babaev 176.16.0.1") == "176.16.0.1");
}