mirror of
https://github.com/dlang/phobos.git
synced 2025-05-03 16:40:48 +03:00
1167 lines
No EOL
57 KiB
D
1167 lines
No EOL
57 KiB
D
/**
|
||
* To validate an email address according to RFCs 5321, 5322 and others
|
||
*
|
||
* Copyright © 2008-2011, Dominic Sayers$(BR)
|
||
* Test schema documentation Copyright © 2011, Daniel Marschall$(BR)
|
||
* All rights reserved.
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person or organization
|
||
* obtaining a copy of the software and accompanying documentation covered by
|
||
* this license (the "Software") to use, reproduce, display, distribute,
|
||
* execute, and transmit the Software, and to prepare derivative works of the
|
||
* Software, and to permit third-parties to whom the Software is furnished to
|
||
* do so, all subject to the following:
|
||
*
|
||
* The copyright notices in the Software and this entire statement, including
|
||
* the above license grant, this restriction and the following disclaimer,
|
||
* must be included in all copies of the Software, in whole or in part, and
|
||
* all derivative works of the Software, unless such copies or derivative
|
||
* works are solely in the form of machine-executable object code generated by
|
||
* a source language processor.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
* FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||
* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||
* FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
* DEALINGS IN THE SOFTWARE.
|
||
*
|
||
* Authors: Dominic Sayers <dominic@sayers.cc>, Jacob Carlborg
|
||
* Copyright: Copyright (c) 2008-2011 Dominic Sayers. All rights reserved.
|
||
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
|
||
* Link: $(LINK http://www.dominicsayers.com/isemail)
|
||
* Version: 3.0.13 - Version 3.0
|
||
*
|
||
* Port to the D programming language:
|
||
* Jacob Carlborg
|
||
*/
|
||
//module std.net.isemail;
|
||
|
||
static import std.algorithm;
|
||
import std.array;
|
||
import std.regex;
|
||
import std.stdio;
|
||
import std.string;
|
||
import std.traits;
|
||
import std.conv;
|
||
import std.utf;
|
||
|
||
alias std.algorithm.canFind contains;
|
||
|
||
int firstChar (Char) (in Char[] str) if (isSomeChar!(Char))
|
||
{
|
||
return cast(int) str.first;
|
||
}
|
||
|
||
T max (T) (T[] arr)
|
||
{
|
||
auto max = arr.first;
|
||
|
||
foreach (i ; 0 .. arr.length - 1)
|
||
max = std.algorithm.max(max, arr[i + 1]);
|
||
|
||
return max;
|
||
}
|
||
|
||
T[] substr (T) (T[] str, sizediff_t start = 0, sizediff_t length = sizediff_t.min)
|
||
{
|
||
sizediff_t end = length;
|
||
|
||
if (start < 0)
|
||
{
|
||
start = str.length + start;
|
||
|
||
if (end < 0)
|
||
{
|
||
if (end == sizediff_t.min)
|
||
end = 0;
|
||
|
||
end = str.length + end;
|
||
}
|
||
|
||
|
||
else
|
||
end = start + end;
|
||
}
|
||
|
||
else
|
||
{
|
||
if (end == sizediff_t.min)
|
||
end = str.length;
|
||
|
||
if (end < 0)
|
||
end = str.length + end;
|
||
}
|
||
|
||
return str[start .. end];
|
||
}
|
||
|
||
int compareFirstN (alias pred = "a < b", S1, S2)(S1 s1, S2 s2, size_t length, bool caseInsensitive = false) if (is(Unqual!(std.algorithm.ElementType!S1) == dchar) && is(Unqual!(std.algorithm.ElementType!S2) == dchar))
|
||
{
|
||
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 caseInsensitive ? slice1.icmp(slice2) : slice1.cmp(slice2);
|
||
}
|
||
|
||
auto grep (Range, Regex) (Range range, Regex regex, bool invert = false)
|
||
{
|
||
auto dg = invert ? (std.algorithm.ElementType!(Range) e) { return e.match(regex).empty; } :
|
||
(std.algorithm.ElementType!(Range) e) { return !e.match(regex).empty; };
|
||
|
||
return std.algorithm.filter!(dg)(range);
|
||
}
|
||
|
||
std.algorithm.ElementType!(A) pop (A) (ref A a) if (isDynamicArray!A && !isNarrowString!A && isMutable!A && !is(A == void[]))
|
||
{
|
||
auto e = a.last;
|
||
a.popBack;
|
||
return e;
|
||
}
|
||
|
||
T[] get (T) (T[] str, size_t index, dchar c)
|
||
{
|
||
return str[index .. index + codeLength!(T)(c)];
|
||
}
|
||
|
||
// issue 4673
|
||
bool isNumeric (dchar c)
|
||
{
|
||
switch (c)
|
||
{
|
||
case 'i':
|
||
case '.':
|
||
case '-':
|
||
case '+':
|
||
case 'u':
|
||
case 'l':
|
||
case 'L':
|
||
case 'U':
|
||
case 'I':
|
||
return false;
|
||
|
||
default:
|
||
}
|
||
|
||
return std.string.isNumeric(c);
|
||
}
|
||
|
||
alias writeln println;
|
||
alias front first;
|
||
alias back last;
|
||
alias popFront shift;
|
||
|
||
enum EmailStatus
|
||
{
|
||
// Categories
|
||
ValidCategory = 1,
|
||
DnsWarning = 7,
|
||
Rfc5321 = 15,
|
||
CFoldingWhitespace = 31,
|
||
Deprecated = 63,
|
||
Rfc5322 = 127,
|
||
Error = 255,
|
||
|
||
// Diagnoses
|
||
// Address is valid
|
||
Valid = 0,
|
||
|
||
// Address is valid but a DNS check was not successful
|
||
DnsWarningNoMXRecord = 5,
|
||
DnsWarningNoRecord = 6,
|
||
|
||
// Address is valid for SMTP but has unusual elements
|
||
Rfc5321TopLevelDomain = 9,
|
||
Rfc5321TopLevelDomainNumeric = 10,
|
||
Rfc5321QuotedString = 11,
|
||
Rfc5321AddressLiteral = 12,
|
||
Rfc5321IpV6Deprecated = 13,
|
||
|
||
// Address is valid within the message but cannot be used unmodified for the envelope
|
||
Comment = 17,
|
||
FoldingWhitespace = 18,
|
||
|
||
// Address contains deprecated elements but may still be valid in restricted contexts
|
||
DeprecatedLocalPart = 33,
|
||
DeprecatedFoldingWhitespace = 34,
|
||
DeprecatedQuotedText = 35,
|
||
DeprecatedQuotedPair = 36,
|
||
DeprecatedComment = 37,
|
||
DeprecatedCommentText = 38,
|
||
DeprecatedCommentFoldingWhitespaceNearAt = 49,
|
||
|
||
// The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid.
|
||
Rfc5322Domain = 65,
|
||
Rfc5322TooLong = 66,
|
||
Rfc5322LocalTooLong = 67,
|
||
Rfc5322DomainTooLong = 68,
|
||
Rfc5322LabelTooLong = 69,
|
||
Rfc5322DomainLiteral = 70,
|
||
Rfc5322DomainLiteralObsoleteText = 71,
|
||
Rfc5322IpV6GroupCount = 72,
|
||
Rfc5322IpV6TooManyDoubleColons = 73,
|
||
Rfc5322IpV6BadChar = 74,
|
||
Rfc5322IpV6MaxGroups = 75,
|
||
Rfc5322IpV6ColonStart = 76,
|
||
Rfc5322IpV6ColonEnd = 77,
|
||
|
||
// Address is invalid for any purpose
|
||
ErrorExpectingDomainText = 129,
|
||
ErrorNoLocalPart = 130,
|
||
ErrorNoDomain = 131,
|
||
ErrorConsecutiveDots = 132,
|
||
ErrorTextAfterCommentFoldingWhitespace = 133,
|
||
ErrorTextAfterQuotedString = 134,
|
||
ErrorTextAfterDomainLiteral = 135,
|
||
ErrorExpectingQuotedPair = 136,
|
||
ErrorExpectingText = 137,
|
||
ErrorExpectingQuotedText = 138,
|
||
ErrorExpectingCommentText = 139,
|
||
ErrorBackslashEnd = 140,
|
||
ErrorDotStart = 141,
|
||
ErrorDotEnd = 142,
|
||
ErrorDomainHyphenStart = 143,
|
||
ErrorDomainHyphenEnd = 144,
|
||
ErrorUnclosedQuotedString = 145,
|
||
ErrorUnclosedComment = 146,
|
||
ErrorUnclosedDomainLiteral = 147,
|
||
ErrorFoldingWhitespaceCrflX2 = 148,
|
||
ErrorFoldingWhitespaceCrLfEnd = 149,
|
||
ErrorCrNoLf = 150,
|
||
|
||
Threshold = 16
|
||
}
|
||
|
||
enum ErrorLevel
|
||
{
|
||
Off,
|
||
On,
|
||
Warning,
|
||
Error
|
||
}
|
||
|
||
enum EmailPart
|
||
{
|
||
ComponentLocalPart,
|
||
ComponentDomain,
|
||
ComponentLiteral,
|
||
ContextComment,
|
||
ContextFoldingWhitespace,
|
||
ContextQuotedString,
|
||
ContextQuotedPair,
|
||
Status
|
||
}
|
||
|
||
private
|
||
{
|
||
// Miscellaneous string constants
|
||
struct Token
|
||
{
|
||
static:
|
||
|
||
enum
|
||
{
|
||
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 = `()<>[]:;@\\,."`
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Check that an email address conforms to RFCs 5321, 5322 and others
|
||
*
|
||
* As of Version 3.0, we are now distinguishing clearly 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. The
|
||
* RFC 5321 Mailbox specification is more restrictive (comments, white space
|
||
* and obsolete forms are not allowed)
|
||
*
|
||
* Params:
|
||
* method = the method alias to build the selector of
|
||
*
|
||
* email = The email address to check
|
||
* checkDNS = If true 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 ISEMAIL_VALID. Thus the
|
||
* calling program can simply look for ISEMAIL_VALID if it is
|
||
* only interested in whether an address is valid or not. The
|
||
* errorlevel will determine how "picky" is_email() is about
|
||
* the address.
|
||
*
|
||
* If omitted or passed as false then isEmail() will return
|
||
* true or false rather than an integer error or warning.
|
||
*
|
||
* NB Note the difference between $(D_PARAM errorLevel) = false and
|
||
* $(D_PARAM errorLevel) = 0
|
||
* parsedata = If passed, returns the parsed address components
|
||
*/
|
||
EmailStatus isEmail (string email, bool checkDNS = false, ErrorLevel errorLevel = ErrorLevel.Off, string[EmailPart] parseData = null)
|
||
{
|
||
int threshold;
|
||
bool diagnose;
|
||
|
||
if (errorLevel == ErrorLevel.On || errorLevel == ErrorLevel.Off)
|
||
{
|
||
threshold = EmailStatus.Valid;
|
||
diagnose = cast(bool) errorLevel;
|
||
}
|
||
|
||
else
|
||
{
|
||
diagnose = true;
|
||
|
||
switch (errorLevel)
|
||
{
|
||
case ErrorLevel.Warning: threshold = EmailStatus.Threshold; break;
|
||
case ErrorLevel.Error: threshold = EmailStatus.Valid; break;
|
||
default: threshold = errorLevel;
|
||
}
|
||
}
|
||
|
||
auto returnStatus = [EmailStatus.Valid];
|
||
auto rawLength = email.length;
|
||
auto context = EmailPart.ComponentLocalPart;
|
||
auto contextStack = [context];
|
||
auto contextPrior = context;
|
||
auto token = "";
|
||
auto tokenPrior = "";
|
||
parseData = [EmailPart.ComponentLocalPart : "", EmailPart.ComponentDomain : ""];
|
||
auto atomList = [EmailPart.ComponentLocalPart : [""], EmailPart.ComponentDomain : [""]];
|
||
auto elementCount = 0;
|
||
auto elementLength = 0;
|
||
auto hyphenFlag = false;
|
||
auto endOrDie = false;
|
||
auto crlfCount = -1;
|
||
|
||
foreach (i, e ; email)
|
||
{
|
||
token = email.get(i, e);
|
||
|
||
switch (context)
|
||
{
|
||
case EmailPart.ComponentLocalPart:
|
||
switch (token)
|
||
{
|
||
case Token.OpenParenthesis:
|
||
if (elementLength == 0)
|
||
returnStatus ~= elementCount == 0 ? EmailStatus.Comment : EmailStatus.DeprecatedComment;
|
||
|
||
else
|
||
{
|
||
returnStatus ~= EmailStatus.Comment;
|
||
endOrDie = true;
|
||
}
|
||
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextComment;
|
||
break;
|
||
|
||
case Token.Dot:
|
||
if (elementLength == 0)
|
||
returnStatus ~= elementCount == 0 ? EmailStatus.ErrorDotStart : EmailStatus.ErrorConsecutiveDots;
|
||
|
||
else
|
||
{
|
||
if (endOrDie)
|
||
returnStatus ~= EmailStatus.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 ? EmailStatus.Rfc5321QuotedString : EmailStatus.DeprecatedLocalPart;
|
||
|
||
parseData[EmailPart.ComponentLocalPart] ~= token;
|
||
atomList[EmailPart.ComponentLocalPart][elementCount] ~= token;
|
||
elementLength++;
|
||
endOrDie = true;
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextQuotedString;
|
||
}
|
||
|
||
else
|
||
returnStatus ~= EmailStatus.ErrorExpectingText;
|
||
break;
|
||
|
||
case Token.Cr:
|
||
case Token.Space:
|
||
case Token.Tab:
|
||
if ((token == Token.Cr) && ((++i == rawLength) || (email.get(i, e) != Token.Lf)))
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorCrNoLf;
|
||
break;
|
||
}
|
||
|
||
if (elementLength == 0)
|
||
returnStatus ~= elementCount == 0 ? EmailStatus.FoldingWhitespace : EmailStatus.DeprecatedFoldingWhitespace;
|
||
|
||
else
|
||
endOrDie = true;
|
||
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextFoldingWhitespace;
|
||
tokenPrior = token;
|
||
break;
|
||
|
||
case Token.At:
|
||
if (contextStack.length != 1)
|
||
throw new Exception("Unexpected item on context stack");
|
||
|
||
if (parseData[EmailPart.ComponentLocalPart] == "")
|
||
returnStatus ~= EmailStatus.ErrorNoLocalPart;
|
||
|
||
else if (elementLength == 0)
|
||
returnStatus ~= EmailStatus.ErrorDotEnd;
|
||
|
||
else if (parseData[EmailPart.ComponentLocalPart].length > 64)
|
||
returnStatus ~= EmailStatus.Rfc5322LocalTooLong;
|
||
|
||
else if (contextPrior == EmailPart.ContextComment || contextPrior == EmailPart.ContextFoldingWhitespace)
|
||
returnStatus ~= EmailStatus.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 ~= EmailStatus.ErrorTextAfterCommentFoldingWhitespace;
|
||
break;
|
||
|
||
case EmailPart.ContextQuotedString:
|
||
returnStatus ~= EmailStatus.ErrorTextAfterQuotedString;
|
||
break;
|
||
|
||
default:
|
||
throw new Exception("More text found where none is allowed, but unrecognised prior context: " ~ to!(string)(contextPrior));
|
||
}
|
||
}
|
||
|
||
else
|
||
{
|
||
contextPrior = context;
|
||
auto ord = token.firstChar;
|
||
|
||
if (ord < 33 || ord > 126 || ord == 10 || Token.Specials.contains(token))
|
||
returnStatus ~= EmailStatus.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 ? EmailStatus.DeprecatedCommentFoldingWhitespaceNearAt : EmailStatus.DeprecatedComment;
|
||
|
||
else
|
||
{
|
||
returnStatus ~= EmailStatus.Comment;
|
||
endOrDie = true;
|
||
}
|
||
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextComment;
|
||
break;
|
||
|
||
case Token.Dot:
|
||
if (elementLength == 0)
|
||
returnStatus ~= elementCount == 0 ? EmailStatus.ErrorDotStart : EmailStatus.ErrorConsecutiveDots;
|
||
|
||
else if (hyphenFlag)
|
||
returnStatus ~= EmailStatus.ErrorDomainHyphenEnd;
|
||
|
||
else
|
||
{
|
||
if (elementLength > 63)
|
||
returnStatus ~= EmailStatus.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 ~= EmailStatus.ErrorExpectingText;
|
||
break;
|
||
|
||
case Token.Cr:
|
||
case Token.Space:
|
||
case Token.Tab:
|
||
if (token == Token.Cr && (++i == rawLength || email.get(i, e) != Token.Lf))
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorCrNoLf;
|
||
break;
|
||
}
|
||
|
||
if (elementLength == 0)
|
||
returnStatus ~= elementCount == 0 ? EmailStatus.DeprecatedCommentFoldingWhitespaceNearAt : EmailStatus.DeprecatedFoldingWhitespace;
|
||
|
||
else
|
||
{
|
||
returnStatus ~= EmailStatus.FoldingWhitespace;
|
||
endOrDie = true;
|
||
}
|
||
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextFoldingWhitespace;
|
||
tokenPrior = token;
|
||
break;
|
||
|
||
default:
|
||
if (endOrDie)
|
||
{
|
||
switch (contextPrior)
|
||
{
|
||
case EmailPart.ContextComment:
|
||
case EmailPart.ContextFoldingWhitespace:
|
||
returnStatus ~= EmailStatus.ErrorTextAfterCommentFoldingWhitespace;
|
||
break;
|
||
|
||
case EmailPart.ComponentLiteral:
|
||
returnStatus ~= EmailStatus.ErrorTextAfterDomainLiteral;
|
||
break;
|
||
|
||
default:
|
||
throw new Exception("More text found where none is allowed, but unrecognised prior context: " ~ to!(string)(contextPrior));
|
||
}
|
||
|
||
}
|
||
|
||
auto ord = token.firstChar;
|
||
hyphenFlag = false;
|
||
|
||
if (ord < 33 || ord > 126 || Token.Specials.contains(token))
|
||
returnStatus ~= EmailStatus.ErrorExpectingText;
|
||
|
||
else if (token == Token.Hyphen)
|
||
{
|
||
if (elementLength == 0)
|
||
returnStatus ~= EmailStatus.ErrorDomainHyphenStart;
|
||
|
||
hyphenFlag = true;
|
||
}
|
||
|
||
else if (!((ord > 47 && ord < 58) || (ord > 64 && ord < 91) || (ord > 96 && ord < 123)))
|
||
returnStatus ~= EmailStatus.Rfc5322Domain;
|
||
|
||
parseData[EmailPart.ComponentDomain] ~= token;
|
||
atomList[EmailPart.ComponentDomain][elementCount] ~= token;
|
||
elementLength++;
|
||
}
|
||
break;
|
||
|
||
case EmailPart.ComponentLiteral:
|
||
switch (token)
|
||
{
|
||
case Token.CloseBracket:
|
||
if (returnStatus.max < EmailStatus.Deprecated)
|
||
{
|
||
auto maxGroups = 8;
|
||
auto index = -1;
|
||
auto addressLiteral = parseData[EmailPart.ComponentLiteral];
|
||
auto matchesIp = array(addressLiteral.match(regex(`\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]?)$`)).captures);
|
||
|
||
if (!matchesIp.empty)
|
||
{
|
||
index = addressLiteral.lastIndexOf(matchesIp.first);
|
||
|
||
if (index != 0)
|
||
addressLiteral = addressLiteral.substr(0, index) ~ "0:0";
|
||
}
|
||
|
||
if (index == 0)
|
||
returnStatus ~= EmailStatus.Rfc5321AddressLiteral;
|
||
|
||
else if (addressLiteral.compareFirstN(Token.IpV6Tag, 5, true))
|
||
returnStatus ~= EmailStatus.Rfc5322DomainLiteral;
|
||
|
||
else
|
||
{
|
||
auto ipV6 = addressLiteral.substr(5);
|
||
matchesIp = ipV6.split(Token.Colon);
|
||
auto groupCount = matchesIp.length;
|
||
index = ipV6.indexOf(Token.DoubleColon);
|
||
|
||
if (index == -1)
|
||
{
|
||
if (groupCount != maxGroups)
|
||
returnStatus ~= EmailStatus.Rfc5322IpV6GroupCount;
|
||
}
|
||
|
||
else
|
||
{
|
||
if (index != ipV6.lastIndexOf(Token.DoubleColon))
|
||
returnStatus ~= EmailStatus.Rfc5322IpV6TooManyDoubleColons;
|
||
|
||
else
|
||
{
|
||
if (index == 0 || index == (ipV6.length - 2))
|
||
maxGroups++;
|
||
|
||
if (groupCount > maxGroups)
|
||
returnStatus ~= EmailStatus.Rfc5322IpV6MaxGroups;
|
||
|
||
else if (groupCount == maxGroups)
|
||
returnStatus ~= EmailStatus.Rfc5321IpV6Deprecated;
|
||
}
|
||
}
|
||
|
||
if (ipV6.substr(0, 1) == Token.Colon && ipV6.substr(1, 1) != Token.Colon)
|
||
returnStatus ~= EmailStatus.Rfc5322IpV6ColonStart;
|
||
|
||
else if (ipV6.substr(-1) == Token.Colon && ipV6.substr(-2, -1) != Token.Colon)
|
||
returnStatus ~= EmailStatus.Rfc5322IpV6ColonEnd;
|
||
|
||
else if (!matchesIp.grep(regex(`^[0-9A-Fa-f]{0,4}$`), true).empty)
|
||
returnStatus ~= EmailStatus.Rfc5322IpV6BadChar;
|
||
|
||
else
|
||
returnStatus ~= EmailStatus.Rfc5321AddressLiteral;
|
||
}
|
||
}
|
||
|
||
else
|
||
returnStatus ~= EmailStatus.Rfc5322DomainLiteral;
|
||
|
||
parseData[EmailPart.ComponentDomain] ~= token;
|
||
atomList[EmailPart.ComponentDomain][elementCount] ~= token;
|
||
elementLength++;
|
||
contextPrior = context;
|
||
context = contextStack.pop;
|
||
break;
|
||
|
||
case Token.Backslash:
|
||
returnStatus ~= EmailStatus.Rfc5322DomainLiteralObsoleteText;
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextQuotedPair;
|
||
break;
|
||
|
||
case Token.Cr:
|
||
case Token.Space:
|
||
case Token.Tab:
|
||
if (token == Token.Cr && (++i == rawLength || email.get(i, e) != Token.Lf))
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorCrNoLf;
|
||
break;
|
||
}
|
||
|
||
returnStatus ~= EmailStatus.FoldingWhitespace;
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextFoldingWhitespace;
|
||
tokenPrior = token;
|
||
break;
|
||
|
||
default:
|
||
auto ord = token.firstChar;
|
||
|
||
if (ord > 127 || ord == 0 || token == Token.OpenBracket)
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorExpectingDomainText;
|
||
break;
|
||
}
|
||
|
||
else if (ord < 33 || ord == 127)
|
||
returnStatus ~= EmailStatus.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 == rawLength || email.get(i, e) != Token.Lf))
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorCrNoLf;
|
||
break;
|
||
}
|
||
|
||
parseData[EmailPart.ComponentLocalPart] ~= Token.Space;
|
||
atomList[EmailPart.ComponentLocalPart][elementCount] ~= Token.Space;
|
||
elementLength++;
|
||
|
||
returnStatus ~= EmailStatus.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:
|
||
auto ord = token.firstChar;
|
||
|
||
if (ord > 127 || ord == 0 || ord == 10)
|
||
returnStatus ~= EmailStatus.ErrorExpectingQuotedText;
|
||
|
||
else if (ord < 32 || ord == 127)
|
||
returnStatus ~= EmailStatus.DeprecatedQuotedText;
|
||
|
||
parseData[EmailPart.ComponentLocalPart] ~= token;
|
||
atomList[EmailPart.ComponentLocalPart][elementCount] ~= token;
|
||
elementLength++;
|
||
}
|
||
break;
|
||
|
||
case EmailPart.ContextQuotedPair:
|
||
auto ord = token.firstChar;
|
||
|
||
if (ord > 127)
|
||
returnStatus ~= EmailStatus.ErrorExpectingQuotedPair;
|
||
|
||
else if (ord < 31 && ord != 9 || ord == 127)
|
||
returnStatus ~= EmailStatus.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++ == rawLength || email.get(i, e) != Token.Lf))
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorCrNoLf;
|
||
break;
|
||
}
|
||
|
||
returnStatus ~= EmailStatus.FoldingWhitespace;
|
||
|
||
contextStack ~= context;
|
||
context = EmailPart.ContextFoldingWhitespace;
|
||
tokenPrior = token;
|
||
break;
|
||
|
||
default:
|
||
auto ord = token.firstChar;
|
||
|
||
if (ord > 127 || ord == 0 || ord == 10)
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorExpectingCommentText;
|
||
break;
|
||
}
|
||
|
||
else if (ord < 32 || ord == 127)
|
||
returnStatus ~= EmailStatus.DeprecatedCommentText;
|
||
}
|
||
break;
|
||
|
||
case EmailPart.ContextFoldingWhitespace:
|
||
if (tokenPrior == Token.Cr)
|
||
{
|
||
if (token == Token.Cr)
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorFoldingWhitespaceCrflX2;
|
||
break;
|
||
}
|
||
|
||
if (crlfCount != -1) // -1 == not defined
|
||
{
|
||
if (++crlfCount > 1)
|
||
returnStatus ~= EmailStatus.DeprecatedFoldingWhitespace;
|
||
}
|
||
|
||
else
|
||
crlfCount = 1;
|
||
}
|
||
|
||
switch (token)
|
||
{
|
||
case Token.Cr:
|
||
if (++i == rawLength || email.get(i, e) != Token.Lf)
|
||
returnStatus ~= EmailStatus.ErrorCrNoLf;
|
||
break;
|
||
|
||
case Token.Space:
|
||
case Token.Tab:
|
||
break;
|
||
|
||
default:
|
||
if (tokenPrior == Token.Cr)
|
||
{
|
||
returnStatus ~= EmailStatus.ErrorFoldingWhitespaceCrLfEnd;
|
||
break;
|
||
}
|
||
|
||
crlfCount = -1; // -1 == not defined
|
||
contextPrior = context;
|
||
context = contextStack.pop;
|
||
i--;
|
||
break;
|
||
}
|
||
|
||
tokenPrior = token;
|
||
break;
|
||
|
||
default:
|
||
throw new Exception("Unkown context: " ~ to!(string)(context));
|
||
}
|
||
|
||
if (returnStatus.max > EmailStatus.Rfc5322)
|
||
break;
|
||
}
|
||
|
||
if (returnStatus.max < EmailStatus.Rfc5322)
|
||
{
|
||
if (context == EmailPart.ContextQuotedString)
|
||
returnStatus ~= EmailStatus.ErrorUnclosedQuotedString;
|
||
|
||
else if (context == EmailPart.ContextQuotedPair)
|
||
returnStatus ~= EmailStatus.ErrorBackslashEnd;
|
||
|
||
else if (context == EmailPart.ContextComment)
|
||
returnStatus ~= EmailStatus.ErrorUnclosedComment;
|
||
|
||
else if (context == EmailPart.ComponentLiteral)
|
||
returnStatus ~= EmailStatus.ErrorUnclosedDomainLiteral;
|
||
|
||
else if (token == Token.Cr)
|
||
returnStatus ~= EmailStatus.ErrorFoldingWhitespaceCrLfEnd;
|
||
|
||
else if (parseData[EmailPart.ComponentDomain] == "")
|
||
returnStatus ~= EmailStatus.ErrorNoDomain;
|
||
|
||
else if (elementLength == 0)
|
||
returnStatus ~= EmailStatus.ErrorDotEnd;
|
||
|
||
else if (hyphenFlag)
|
||
returnStatus ~= EmailStatus.ErrorDomainHyphenEnd;
|
||
|
||
else if (parseData[EmailPart.ComponentDomain].length > 255)
|
||
returnStatus ~= EmailStatus.Rfc5322DomainTooLong;
|
||
|
||
else if ((parseData[EmailPart.ComponentLocalPart] ~ Token.At ~ parseData[EmailPart.ComponentDomain]).length > 254)
|
||
returnStatus ~= EmailStatus.Rfc5322TooLong;
|
||
|
||
else if (elementLength > 63)
|
||
returnStatus ~= EmailStatus.Rfc5322LabelTooLong;
|
||
}
|
||
|
||
auto dnsChecked = false;
|
||
|
||
if (checkDNS && returnStatus.max < EmailStatus.DnsWarning)
|
||
{
|
||
assert(false, "DNS check is currently not implemented");
|
||
}
|
||
|
||
if (!dnsChecked && returnStatus.max < EmailStatus.DnsWarning)
|
||
{
|
||
if (elementCount == 0)
|
||
returnStatus ~= EmailStatus.Rfc5321TopLevelDomain;
|
||
|
||
if (isNumeric(atomList[EmailPart.ComponentDomain][elementCount].first))
|
||
returnStatus ~= EmailStatus.Rfc5321TopLevelDomainNumeric;
|
||
}
|
||
|
||
returnStatus = array(std.algorithm.uniq(returnStatus));
|
||
auto finalStatus = returnStatus.max;
|
||
|
||
if (returnStatus.length != 1)
|
||
returnStatus.shift;
|
||
|
||
parseData[EmailPart.Status] = to!(string)(returnStatus);
|
||
|
||
if (finalStatus < threshold)
|
||
finalStatus = EmailStatus.Valid;
|
||
|
||
if (diagnose)
|
||
return finalStatus;
|
||
|
||
else
|
||
return finalStatus < EmailStatus.Threshold ? EmailStatus.Valid : EmailStatus.Error;
|
||
}
|
||
|
||
unittest
|
||
{
|
||
assert(``.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorNoDomain);
|
||
assert(`test`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorNoDomain);
|
||
assert(`@`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorNoLocalPart);
|
||
assert(`test@`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorNoDomain);
|
||
//assert(`test@io`.isEmail(false, ErrorLevel.On) == EmailStatus.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(false, ErrorLevel.On) == EmailStatus.ErrorNoLocalPart, `io. currently has an MX-record (Feb 2011)`);
|
||
assert(`@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorNoLocalPart);
|
||
assert(`test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`test@nominet.org.uk`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`test@about.museum`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`a@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
//assert(`test@e.com`.isEmail(false, ErrorLevel.On) == EmailStatus.DnsWarningNoRecord); // DNS check is currently not implemented
|
||
//assert(`test@iana.a`.isEmail(false, ErrorLevel.On) == EmailStatus.DnsWarningNoRecord); // DNS check is currently not implemented
|
||
assert(`test.test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`.test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorDotStart);
|
||
assert(`test.@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorDotEnd);
|
||
assert(`test..iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorConsecutiveDots);
|
||
assert(`test_exa-mple.com`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorNoDomain);
|
||
assert("!#$%&`*+/=?^`{|}~@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`test\@test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`123@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`test@123.com`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`test@iana.123`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321TopLevelDomainNumeric);
|
||
assert(`test@255.255.255.255`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321TopLevelDomainNumeric);
|
||
assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322LocalTooLong);
|
||
//assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(false, ErrorLevel.On) == EmailStatus.DnsWarningNoRecord); // DNS check is currently not implemented
|
||
assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322LabelTooLong);
|
||
assert(`test@mason-dixon.com`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
assert(`test@-iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorDomainHyphenStart);
|
||
assert(`test@iana-.com`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorDomainHyphenEnd);
|
||
assert(`test@g--a.com`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid);
|
||
//assert(`test@iana.co-uk`.isEmail(false, ErrorLevel.On) == EmailStatus.DnsWarningNoRecord); // DNS check is currently not implemented
|
||
assert(`test@.iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorDotStart);
|
||
assert(`test@iana.org.`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorDotEnd);
|
||
assert(`test@iana..com`.isEmail(false, ErrorLevel.On) == EmailStatus.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(false, ErrorLevel.On) == EmailStatus.DnsWarningNoRecord); // DNS check is currently not implemented
|
||
//assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi`.isEmail(false, ErrorLevel.On) == EmailStatus.DnsWarningNoRecord); // DNS check is currently not implemented
|
||
assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322TooLong);
|
||
assert(`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322TooLong);
|
||
assert(`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainTooLong);
|
||
assert(`"test"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321QuotedString);
|
||
assert(`""@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321QuotedString);
|
||
assert(`"""@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`"\a"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321QuotedString);
|
||
assert(`"\""@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321QuotedString);
|
||
assert(`"\"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedQuotedString);
|
||
assert(`"\\"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321QuotedString);
|
||
assert(`test"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`"test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedQuotedString);
|
||
assert(`"test"test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorTextAfterQuotedString);
|
||
assert(`test"text"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`"test""test"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`"test"."test"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedLocalPart);
|
||
assert(`"test\ test"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321QuotedString);
|
||
assert(`"test".test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedLocalPart);
|
||
assert("\"test\u0000\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingQuotedText);
|
||
assert("\"test\\\u0000\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedQuotedPair);
|
||
assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322LocalTooLong, `Quotes are still part of the length restriction`);
|
||
assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\h"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322LocalTooLong, `Quoted pair is still part of the length restriction`);
|
||
//assert(`test@[255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321AddressLiteral); // std.regex bug: *+? not allowed in atom
|
||
assert(`test@a[255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
// assert(`test@[255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[255.255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[255.255.255.256]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6GroupCount); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321AddressLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6GroupCount); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6BadChar); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321IpV6Deprecated); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555::8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321AddressLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6MaxGroups); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6::3333:4444:5555:6666:7777:8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6ColonStart); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:::3333:4444:5555:6666:7777:8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321AddressLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111::4444:5555::8888]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6TooManyDoubleColons); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:::]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321AddressLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6GroupCount); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321AddressLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6GroupCount); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444::255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321AddressLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6MaxGroups); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6:1111:2222:3333:4444:::255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6TooManyDoubleColons); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[IPv6::255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6ColonStart); // std.regex bug: *+? not allowed in atom
|
||
assert(` test @iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedCommentFoldingWhitespaceNearAt);
|
||
assert(`test@ iana .com`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedCommentFoldingWhitespaceNearAt);
|
||
assert(`test . test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedFoldingWhitespace);
|
||
assert("\u000D\u000A test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.FoldingWhitespace, `Folding whitespace`);
|
||
assert("\u000D\u000A \u000D\u000A test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.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(false, ErrorLevel.On) == EmailStatus.Comment);
|
||
assert(`((comment)test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedComment);
|
||
assert(`(comment(comment))test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Comment);
|
||
assert(`test@(comment)iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedCommentFoldingWhitespaceNearAt);
|
||
assert(`test(comment)test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorTextAfterCommentFoldingWhitespace);
|
||
// assert(`test@(comment)[255.255.255.255]`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedCommentFoldingWhitespaceNearAt); // std.regex bug: *+? not allowed in atom
|
||
assert(`(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Comment);
|
||
assert(`test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedCommentFoldingWhitespaceNearAt);
|
||
assert(`(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu`.isEmail(false, ErrorLevel.On) == EmailStatus.Comment);
|
||
assert("test@iana.org\u000A".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`test@xn--hxajbheg2az3al.xn--jxalpdlp`.isEmail(false, ErrorLevel.On) == EmailStatus.Valid, `A valid IDN from ICANN's <a href="http://idn.icann.org/#The_example.test_names">IDN TLD evaluation gateway</a>`);
|
||
assert(`xn--test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.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(false, ErrorLevel.On) == EmailStatus.ErrorDomainHyphenEnd);
|
||
assert(`"test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedQuotedString);
|
||
assert(`(test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedComment);
|
||
assert(`test@(iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedComment);
|
||
assert(`test@[1.2.3.4`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedDomainLiteral);
|
||
assert(`"test\"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedQuotedString);
|
||
assert(`(comment\)test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedComment);
|
||
assert(`test@iana.org(comment\)`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedComment);
|
||
assert(`test@iana.org(comment\`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorBackslashEnd);
|
||
// assert(`test@[RFC-5322-domain-literal]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[RFC-5322]-domain-literal]`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorTextAfterDomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[RFC-5322-[domain-literal]`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingDomainText); // std.regex bug: *+? not allowed in atom
|
||
// assert("test@[RFC-5322-\\\u0007-domain-literal]".isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteralObsoleteText, `obs-dtext <strong>and</strong> obs-qp`); // std.regex bug: *+? not allowed in atom
|
||
// assert("test@[RFC-5322-\\\u0009-domain-literal]".isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteralObsoleteText); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[RFC-5322-\]-domain-literal]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteralObsoleteText); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[RFC-5322-domain-literal\]`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorUnclosedDomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[RFC-5322-domain-literal\`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorBackslashEnd); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[RFC 5322 domain literal]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteral, `Spaces are FWS in a domain literal`); // std.regex bug: *+? not allowed in atom
|
||
// assert(`test@[RFC-5322-domain-literal] (comment)`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322DomainLiteral); // std.regex bug: *+? not allowed in atom
|
||
assert(`@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`test@.org`.isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert(`""@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedQuotedText);
|
||
assert(`"\"@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedQuotedPair);
|
||
assert(`()test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedCommentText);
|
||
assert("test@iana.org\u000D".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorCrNoLf, `No LF after the CR`);
|
||
assert("\u000Dtest@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorCrNoLf, `No LF after the CR`);
|
||
assert("\"\u000Dtest\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorCrNoLf, `No LF after the CR`);
|
||
assert("(\u000D)test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorCrNoLf, `No LF after the CR`);
|
||
assert("test@iana.org(\u000D)".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorCrNoLf, `No LF after the CR`);
|
||
assert("\u000Atest@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert("\"\u000A\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingQuotedText);
|
||
assert("\"\\\u000A\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedQuotedPair);
|
||
assert("(\u000A)test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingCommentText);
|
||
assert("\u0007@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert("test@\u0007.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingText);
|
||
assert("\"\u0007\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedQuotedText);
|
||
assert("\"\\\u0007\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedQuotedPair);
|
||
assert("(\u0007)test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedCommentText);
|
||
assert("\u000D\u000Atest@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`);
|
||
assert("\u000D\u000A \u000D\u000Atest@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`);
|
||
assert(" \u000D\u000Atest@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`);
|
||
assert(" \u000D\u000A test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.FoldingWhitespace, `FWS`);
|
||
assert(" \u000D\u000A \u000D\u000Atest@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`);
|
||
assert(" \u000D\u000A\u000D\u000Atest@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`);
|
||
assert(" \u000D\u000A\u000D\u000A test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`);
|
||
assert("test@iana.org\u000D\u000A ".isEmail(false, ErrorLevel.On) == EmailStatus.FoldingWhitespace, `FWS`);
|
||
assert("test@iana.org\u000D\u000A \u000D\u000A ".isEmail(false, ErrorLevel.On) == EmailStatus.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(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`);
|
||
assert("test@iana.org\u000D\u000A \u000D\u000A".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`);
|
||
assert("test@iana.org \u000D\u000A".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`);
|
||
assert("test@iana.org \u000D\u000A ".isEmail(false, ErrorLevel.On) == EmailStatus.FoldingWhitespace, `FWS`);
|
||
assert("test@iana.org \u000D\u000A \u000D\u000A".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`);
|
||
assert("test@iana.org \u000D\u000A\u000D\u000A".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`);
|
||
assert("test@iana.org \u000D\u000A\u000D\u000A ".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`);
|
||
assert(" test@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.FoldingWhitespace);
|
||
assert(`test@iana.org `.isEmail(false, ErrorLevel.On) == EmailStatus.FoldingWhitespace);
|
||
// assert(`test@[IPv6:1::2:]`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322IpV6ColonEnd); // std.regex bug: *+? not allowed in atom
|
||
assert("\"test\\\u00A9\"@iana.org".isEmail(false, ErrorLevel.On) == EmailStatus.ErrorExpectingQuotedPair);
|
||
assert(`test@iana/icann.org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5322Domain);
|
||
assert(`test.(comment)test@iana.org`.isEmail(false, ErrorLevel.On) == EmailStatus.DeprecatedComment);
|
||
assert(`test@org`.isEmail(false, ErrorLevel.On) == EmailStatus.Rfc5321TopLevelDomain);
|
||
// assert(`test@test.com`.isEmail(false, ErrorLevel.On) == EmailStatus.DnsWarningNoMXRecord, `test.com has an A-record but not an MX-record`); // DNS check is currently not implemented
|
||
// assert(`test@nic.no`.isEmail(false, ErrorLevel.On) == EmailStatus.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
|
||
}
|
||
|
||
void main ()
|
||
{
|
||
string[EmailPart] parseData;
|
||
println(`testö@org`.isEmail(false, ErrorLevel.On, parseData));
|
||
// println(parseData);
|
||
} |