Initial Commit

This commit is contained in:
Eduardo Gulias Davis
2013-05-19 18:13:48 +02:00
commit f33ab05be5
18 changed files with 2057 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
report/
vendor/
.idea
+8
View File
@@ -0,0 +1,8 @@
language: php
php:
- 5.3
before_script:
- wget http://getcomposer.org/composer.phar
- php composer.phar install
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2013-2013 Eduardo Gulias Davis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+44
View File
@@ -0,0 +1,44 @@
EmailValidator
=============================
#Installation#
Install via composer.
#Usage#
Simple example:
```php
<?php
use IsEmail\EmailValidator;
$validator = new EmailValidator;
if ($validator->isValid($email)) {
echo $email . ' is a valid email address';
}
```
More advanced example (returns detailed diagnostic error codes):
```php
<?php
require_once 'EmailValidator.php';
$validator = new EmailValidator;
$email = 'dominic@sayers.cc';
$result = $validator->isValid($email);
if ($result) {
echo $email . ' is a valid email address';
} else if ($validator->hasWarnings()) {
echo 'Warning! ' . $email . ' has unusual/deprecated features (result code ' . var_export($validator->getWarnings(), true) . ')';
} else {
echo $email . ' is not a valid email address (result code ' . $validator->getError() . ')';
}
```
#Licence#
This code is released under the MIT Licence attached with this code.
+13
View File
@@ -0,0 +1,13 @@
{
"name": "egulias/EmailValidator",
"description": "A library for validating emails",
"license": "MIT",
"require": {
"jms/parser-lib": "dev-master"
},
"autoload": {
"psr-0": {
"egulias\\": "src/"
}
}
}
Generated
+99
View File
@@ -0,0 +1,99 @@
{
"hash": "6aabe7b13fcf2af4eeb25983a1c6de35",
"packages": [
{
"name": "jms/parser-lib",
"version": "dev-master",
"source": {
"type": "git",
"url": "git://github.com/schmittjoh/parser-lib",
"reference": "4d469a70c6dd03f921cbdeadafbcb261bb23e8b0"
},
"dist": {
"type": "zip",
"url": "https://github.com/schmittjoh/parser-lib/archive/4d469a70c6dd03f921cbdeadafbcb261bb23e8b0.zip",
"reference": "4d469a70c6dd03f921cbdeadafbcb261bb23e8b0",
"shasum": ""
},
"require": {
"phpoption/phpoption": ">=0.9,<2.0-dev"
},
"time": "2012-11-30 08:11:04",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"installation-source": "source",
"autoload": {
"psr-0": {
"JMS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache2"
],
"description": "A library for easily creating recursive-descent parsers."
},
{
"name": "phpoption/phpoption",
"version": "1.0.0",
"source": {
"type": "git",
"url": "git://github.com/schmittjoh/php-option",
"reference": "1.0.0"
},
"dist": {
"type": "zip",
"url": "https://github.com/schmittjoh/php-option/archive/1.0.0.zip",
"reference": "1.0.0",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2012-11-18 14:14:12",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"PhpOption\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache2"
],
"authors": [
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "http://jmsyst.com",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Option Type for PHP",
"keywords": [
"php",
"language",
"type",
"option"
]
}
],
"packages-dev": null,
"aliases": [
],
"minimum-stability": "stable",
"stability-flags": {
"jms/parser-lib": 20
}
}
+89
View File
@@ -0,0 +1,89 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>The BNF from RFC 5321 defining parts of a valid SMTP address</title>
</head>
<body>
<pre style="font-size:11px;">
Mailbox = Local-part "@" ( Domain / address-literal )
Local-part = Dot-string / Quoted-string
; MAY be case-sensitive
Dot-string = Atom *("." Atom)
Atom = 1*atext
Quoted-string = DQUOTE *QcontentSMTP DQUOTE
QcontentSMTP = qtextSMTP / quoted-pairSMTP
quoted-pairSMTP = %d92 %d32-126
; i.e., backslash followed by any ASCII
; graphic (including itself) or SPace
qtextSMTP = %d32-33 / %d35-91 / %d93-126
; i.e., within a quoted string, any
; ASCII graphic or space is permitted
; without blackslash-quoting except
; double-quote and the backslash itself.
Domain = sub-domain *("." sub-domain)
sub-domain = Let-dig [Ldh-str]
Let-dig = ALPHA / DIGIT
Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
address-literal = "[" ( IPv4-address-literal /
IPv6-address-literal /
General-address-literal ) "]"
; See Section 4.1.3
IPv4-address-literal = Snum 3("." Snum)
IPv6-address-literal = "IPv6:" IPv6-addr
General-address-literal = Standardized-tag ":" 1*dcontent
Standardized-tag = Ldh-str
; Standardized-tag MUST be specified in a
; Standards-Track RFC and registered with IANA
dcontent = %d33-90 / ; Printable US-ASCII
%d94-126 ; excl. "[", "\", "]"
Snum = 1*3DIGIT
; representing a decimal integer
; value in the range 0 through 255
IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
IPv6-hex = 1*4HEXDIG
IPv6-full = IPv6-hex 7(":" IPv6-hex)
IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
[IPv6-hex *5(":" IPv6-hex)]
; The "::" represents at least 2 16-bit groups of
; zeros. No more than 6 groups in addition to the
; "::" may be present.
IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
[IPv6-hex *3(":" IPv6-hex) ":"]
IPv4-address-literal
; The "::" represents at least 2 16-bit groups of
; zeros. No more than 4 groups in addition to the
; "::" and IPv4-address-literal may be present.
</pre>
</body>
</html>
+141
View File
@@ -0,0 +1,141 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>The BNF from RFC 5322 defining parts of a valid internet message address</title>
</head>
<body>
<pre style="font-size:11px;">
addr-spec = local-part "@" domain
local-part = dot-atom / quoted-string / obs-local-part
dot-atom = [CFWS] dot-atom-text [CFWS]
CFWS = (1*([FWS] comment) [FWS]) / FWS
FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
; Folding white space
WSP = SP / HTAB ; white space
obs-FWS = 1*([CRLF] WSP) ; As amended in erratum #1908
ctext = %d33-39 / ; Printable US-ASCII
%d42-91 / ; characters not including
%d93-126 / ; "(", ")", or "\"
obs-ctext
obs-ctext = obs-NO-WS-CTL
ccontent = ctext / quoted-pair / comment
comment = "(" *([FWS] ccontent) [FWS] ")"
dot-atom-text = 1*atext *("." 1*atext)
atext = ALPHA / DIGIT / ; Printable US-ASCII
"!" / "#" / ; characters not including
"$" / "%" / ; specials. Used for atoms.
"&amp;" / "'" /
"*" / "+" /
"-" / "/" /
"=" / "?" /
"^" / "_" /
"`" / "{" /
"|" / "}" /
"~"
specials = "(" / ")" / ; Special characters that do
"&lt;" / "&gt;" / ; not appear in atext
"[" / "]" /
":" / ";" /
"@" / "\" /
"," / "." /
DQUOTE
quoted-string = [CFWS]
DQUOTE *([FWS] qcontent) [FWS] DQUOTE
[CFWS]
qcontent = qtext / quoted-pair
qtext = %d33 / ; Printable US-ASCII
%d35-91 / ; characters not including
%d93-126 / ; "\" or the quote character
obs-qtext
obs-qtext = obs-NO-WS-CTL
obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
%d11 / ; characters that do not
%d12 / ; include the carriage
%d14-31 / ; return, line feed, and
%d127 ; white space characters
quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
VCHAR = %x21-7E ; visible (printing) characters
obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
obs-local-part = word *("." word)
word = atom / quoted-string
atom = [CFWS] 1*atext [CFWS]
domain = dot-atom / domain-literal / obs-domain
domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
dtext = %d33-90 / ; Printable US-ASCII
%d94-126 / ; characters not including
obs-dtext ; "[", "]", or "\"
obs-dtext = obs-NO-WS-CTL / quoted-pair
obs-domain = atom *("." atom)
NB For SMTP mail, the domain-literal is restricted by RFC5321 as follows:
Mailbox = Local-part "@" ( Domain / address-literal )
address-literal = "[" ( IPv4-address-literal /
IPv6-address-literal /
General-address-literal ) "]"
IPv4-address-literal = Snum 3("." Snum)
IPv6-address-literal = "IPv6:" IPv6-addr
Snum = 1*3DIGIT
; representing a decimal integer
; value in the range 0 through 255
IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
IPv6-hex = 1*4HEXDIG
IPv6-full = IPv6-hex 7(":" IPv6-hex)
IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
[IPv6-hex *5(":" IPv6-hex)]
; The "::" represents at least 2 16-bit groups of
; zeros. No more than 6 groups in addition to the
; "::" may be present.
IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
[IPv6-hex *3(":" IPv6-hex) ":"]
IPv4-address-literal
; The "::" represents at least 2 16-bit groups of
; zeros. No more than 4 groups in addition to the
; "::" and IPv4-address-literal may be present.
</pre>
</body>
</html>
+186
View File
@@ -0,0 +1,186 @@
<?php
namespace egulias\EmailValidator;
use JMS\Parser\AbstractLexer;
class EmailLexer extends AbstractLexer
{
//ASCII values
const C_DEL = 127;
const C_NUL = 0;
const S_AT = 64;//'@';
const S_BACKSLASH = 92;//'\\';
const S_DOT = 46;//'.';
const S_DQUOTE = 34;//'"';
const S_OPENPARENTHESIS = 49;//'(';
const S_CLOSEPARENTHESIS = 261;//')';
const S_OPENBRACKET = 262;//'[';
const S_CLOSEBRACKET = 263;//']';
const S_HYPHEN = 264;//'-';
const S_COLON = 265;//':';
const S_DOUBLECOLON = 266;//'::';
const S_SP = 267;//' ';
const S_HTAB = 268;//"\t";
const S_CR = 269;//"\r";
const S_LF = 270;//"\n";
const S_IPV6TAG = 271;//'IPv6:';
const S_LOWERTHAN = 272;//'<'
const S_GREATERTHAN = 273;//'>'
const S_COMMA = 274;//','
const S_SEMICOLON = 275;//';'
const S_OPENQBRACKET = 276;//'[';
const S_CLOSEQBRACKET = 277;//']';
const S_EMPTY = null;//'';
const GENERIC = 300;
const CRLF = 301;
const INVALID = 302;
/**
* US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
*
* @var array
*/
protected $charValue = array(
'(' => self::S_OPENPARENTHESIS,
')' => self::S_CLOSEPARENTHESIS,
'<' => self::S_LOWERTHAN,
'>' => self::S_GREATERTHAN,
'[' => self::S_OPENBRACKET,
']' => self::S_CLOSEBRACKET,
':' => self::S_COLON,
';' => self::S_SEMICOLON,
'@' => self::S_AT,
'\\' => self::S_BACKSLASH,
',' => self::S_COMMA,
'.' => self::S_DOT,
'"' => self::S_DQUOTE,
'-' => self::S_HYPHEN,
'::' => self::S_DOUBLECOLON,
' ' => self::S_SP,
"\t" => self::S_HTAB,
"\r" => self::S_CR,
"\n" => self::S_LF,
"\r\n" => self::CRLF,
'IPv6' => self::S_IPV6TAG,
'<' => self::S_LOWERTHAN,
'>' => self::S_GREATERTHAN,
'{' => self::S_OPENQBRACKET,
'}' => self::S_CLOSEQBRACKET,
'' => self::S_EMPTY
);
protected $previous;
/**
* getName
*
* @param string $type
*
* @return string $name
*/
public function getName($type)
{
$ref = new \ReflectionClass($this);
foreach ($ref->getConstants() as $name => $value) {
if ($value === $type) {
return $name;
}
}
throw new \InvalidArgumentException(sprintf('There is no token with value %s.', json_encode($type)));
}
public function find($type)
{
$search = clone $this;
$search->skipUntil($type);
}
/**
* getPrevious
*
* @return array token
*/
public function getPrevious()
{
return $this->previous;
}
/**
* moveNext
*
* @return mixed
*/
public function moveNext()
{
$this->previous = $this->token;
return parent::moveNext();
}
public function isNext($type)
{
return null !== $this->next && $type === $this->next[0];
}
public function isNextAny(array $types)
{
foreach ($types as $i => $type) {
$types[$i] = array_search($type, $this->charValue);
}
return parent::isNextAny($types);
}
/**
* {@inherit}
*/
protected function getRegex()
{
return '/
# Alphabetic-chars and IPv6 or 4
([a-zA-Z]+[4,6]?)
# Numbers
| ([0-9]+)
#CRLF
| (\r\n)
#double colon
| (::)
# Whitespace
| (\s+)
#Special
| ([\x1-\x1F]+)
# Anything that is left as single character tokens
| (.)
/xu';
}
/**
* {@inherit}
*/
protected function determineTypeAndValue($value)
{
if (isset($this->charValue[$value])) {
return array($value, $this->charValue[$value]);
}
if (preg_match('/[\x1-\x1F]+/', $value)) {
return array($value, self::INVALID);
}
if (preg_match('/[\x7f-\xff]+/', $value)) {
throw new \InvalidArgumentException(sprintf('There is no token with value %s.', json_encode($value)));
}
return array($value, self::GENERIC);
}
}
+472
View File
@@ -0,0 +1,472 @@
<?php
namespace egulias\EmailValidator;
use JMS\Parser\AbstractParser;
/**
* EmailParser
*
* @author Eduardo Gulias Davis <me@egulias.com>
*/
class EmailParser extends AbstractParser
{
protected $warnings = array();
protected $localPart = '';
protected $domainPart = '';
protected $index = 0;
/**
* {@inherit}
*/
public function parseInternal()
{
$this->lexer->moveNext();
if ($this->lexer->token[0] === EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_NOLOCALPART');
}
$this->parseLocalPart();
$this->parseDomainPart();
if (strlen($this->localPart . '@' . $this->domainPart) > 254) {
// http://tools.ietf.org/html/rfc5321#section-4.1.2
// Forward-path = Path
//
// Path = "<" [ A-d-l ":" ] Mailbox ">"
//
// http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
// The maximum total length of a reverse-path or forward-path is 256
// octets (including the punctuation and element separators).
//
// Thus, even without (obsolete) routing information, the Mailbox can
// only be 254 characters long. This is confirmed by this verified
// erratum to RFC 3696:
//
// http://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
// However, there is a restriction in RFC 2821 on the length of an
// address in MAIL and RCPT commands of 254 characters. Since addresses
// that do not fit in those fields are not normally useful, the upper
// limit on address lengths should normally be considered to be 254.
//
// http://tools.ietf.org/html/rfc1035#section-2.3.4
$this->warnings[] = EmailValidator::RFC5322_TOOLONG;
}
return array('local' => $this->localPart, 'domain' => $this->domainPart);
}
/**
* getWarnings
*
* @return array
*/
public function getWarnings()
{
return $this->warnings;
}
public function getParsedDomainPart()
{
return $this->domainPart;
}
/**
* parseDomainPart
*
*/
private function parseDomainPart()
{
$domain = '';
$this->lexer->moveNext();
if ($this->lexer->token[0] === EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEATS');
}
if ($this->lexer->token[0] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
if ($this->lexer->token[0] === EmailLexer::S_EMPTY) {
throw new \InvalidArgumentException('ERR_NODOMAIN');
}
// Comments at the start of the domain are deprecated in the text
// Comments at the start of a subdomain are obs-domain
// (http://tools.ietf.org/html/rfc5322#section-3.4.1)
if ($this->lexer->token[0] === EmailLexer::S_OPENPARENTHESIS) {
$this->warnings[] = EmailValidator::DEPREC_COMMENT;
$this->parseComments();
}
do {
$prev = $this->lexer->getPrevious();
if ($this->lexer->token[0] === EmailLexer::S_OPENQBRACKET && $prev[0] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->token[0] === EmailLexer::S_OPENPARENTHESIS) {
//$this->warnings[] = EmailValidator::CFWS_COMMENT;
$this->parseComments();
$this->lexer->moveNext();
}
if ($this->lexer->token[0] === EmailLexer::S_DOT && $this->lexer->isNext(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEDOTS');
}
if ($this->lexer->token[0] === EmailLexer::S_HYPHEN && $this->lexer->isNext(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($this->lexer->token[0] === EmailLexer::S_OPENQBRACKET) {
try {
$this->lexer->find(EmailLexer::S_CLOSEQBRACKET);
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_EXPECTING_DOMLIT_CLOSE');
}
$this->parseDomainLiteral();
}
if ($this->lexer->token[0] === EmailLexer::S_OPENBRACKET) {
try {
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_EXPECTING_DOMLIT_CLOSE');
}
$this->parseDomainLiteral();
}
if ($this->lexer->token[0] === EmailLexer::S_BACKSLASH && $this->lexer->isNext(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if (
$this->lexer->token[0] === EmailLexer::S_DOT &&
$prev[0] === EmailLexer::GENERIC &&
strlen($prev[2]) > 63
) {
$this->warnings[] = EmailValidator::RFC5322_LABEL_TOOLONG;
}
if (
$this->lexer->token[0] === EmailLexer::S_SP ||
$this->lexer->token[0] === EmailLexer::S_HTAB ||
$this->lexer->token[0] === EmailLexer::S_CR ||
$this->lexer->token[0] === EmailLexer::S_LF ||
$this->lexer->token[0] === EmailLexer::CRLF
) {
$this->parseFWS();
}
$domain .= $this->lexer->token[2];
$this->lexer->moveNext();
} while ($this->lexer->token);
$last = $this->lexer->getPrevious();
$length = strlen($last[2]);
if ($last[0] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
if ($last[0] === EmailLexer::S_HYPHEN) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($length > 254) {
$this->warnings[] = EmailValidator::RFC5322_DOMAIN_TOOLONG;
}
if ($last[0] === EmailLexer::S_CR) {
throw new \InvalidArgumentException('ERR_FWS_CRLF_END');
}
$this->domainPart = $domain;
}
private function parseDomainLiteral()
{
$IPv6TAG = false;
$addressLiteral = '';
if ($this->lexer->isNext(EmailLexer::S_COLON)) {
// Address starts with a single colon
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
if ($this->lexer->isNext(EmailLexer::S_IPV6TAG)) {
try {
$lexer = clone $this->lexer;
$lexer->moveNext();
if ($lexer->isNext(EmailLexer::S_DOUBLECOLON)) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
} catch (\Exception $e) {
break;
}
}
do {
if ($this->lexer->token[0] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
} elseif (
$this->lexer->token[0] === EmailLexer::INVALID ||
$this->lexer->token[0] === EmailLexer::C_DEL ||
$this->lexer->token[0] === EmailLexer::S_LF
) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
}
if ($this->lexer->isNextAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
}
if ($this->lexer->isNextAny(array(EmailLexer::S_HTAB, EmailLexer::S_SP))) {
$this->warnings[] = EmailValidator::CFWS_FWS;
$this->parseFWS();
}
if ($this->lexer->isNext(EmailLexer::S_CR)) {
throw new \InvalidArgumentException("ERR_CR_NO_LF");
}
if ($this->lexer->token[0] === EmailLexer::S_BACKSLASH) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
$addressLiteral .= $this->lexer->token[2];
$this->lexer->moveNext();
$this->validateQuotedPair();
}
if ($this->lexer->token[0] === EmailLexer::S_CLOSEQBRACKET) {
break;
}
if (
$this->lexer->token[0] === EmailLexer::S_SP ||
$this->lexer->token[0] === EmailLexer::S_HTAB ||
$this->lexer->token[0] === EmailLexer::CRLF
) {
$this->parseFWS();
}
if ($this->lexer->token[0] === EmailLexer::S_IPV6TAG) {
$IPv6TAG = true;
}
$addressLiteral .= $this->lexer->token[2];
} while ($this->lexer->moveNext());
// Revision 2.7: Daniel Marschall's new IPv6 testing strategy
$prev = $this->lexer->getPrevious();
if ($prev[0] === EmailLexer::S_COLON) {
// Address ends with a single colon
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONEND;
}
$addressLiteral = str_replace('[', '', $addressLiteral);
$maxGroups = 8;
$matchesIP = array();
$index = 0;
// Extract IPv4 part from the end of the address-literal (if there is one)
if (preg_match(
'/\\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]?)$/',
$addressLiteral,
$matchesIP
) > 0
) {
$index = strrpos($addressLiteral, $matchesIP[0]);
if ($index === 0) {
$this->warnings[] = EmailValidator::RFC5321_ADDRESSLITERAL;
return;
}
// Convert IPv4 part to IPv6 format for further testing
$addressLiteral = substr($addressLiteral, 0, $index) . '0:0';
}
if (!$IPv6TAG) {
$this->warnings[] = EmailValidator::RFC5322_DOMAINLITERAL;
return;
}
$this->warnings[] = EmailValidator::RFC5321_ADDRESSLITERAL;
$IPv6 = substr($addressLiteral, 5);
//Daniel Marschall's new IPv6 testing strategy
$matchesIP = explode(':', $IPv6);
$groupCount = count($matchesIP);
$colons = strpos($IPv6, '::');
$matches = array();
if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
// Check for unmatched characters
$this->warnings[] = EmailValidator::RFC5322_IPV6_BADCHAR;
}
if ($colons === false) {
// We need exactly the right number of groups
if ($groupCount !== $maxGroups) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_GRPCOUNT;
}
} else {
if ($colons !== strrpos($IPv6, '::')) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_2X2XCOLON;
} else {
if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
// RFC 4291 allows :: at the start or end of an address
//with 7 other groups in addition
++$maxGroups;
}
if ($groupCount > $maxGroups) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_MAXGRPS;
} elseif ($groupCount === $maxGroups) {
// Eliding a single "::"
$this->warnings[] = EmailValidator::RFC5321_IPV6DEPRECATED;
}
}
}
return $addressLiteral;
}
/**
* validateQuotedPair
*
* @TODO This needs to be reviewed
*/
private function validateQuotedPair()
{
if (!($this->lexer->token[0] === EmailLexer::INVALID || $this->lexer->token[0] === EmailLexer::C_DEL)) {
throw new \InvalidArgumentException('ERR_EXPECTING_QPAIR');
}
// SP & HTAB are allowed
$this->warnings[] = EmailValidator::DEPREC_QP;
}
private function parseLocalPart()
{
$closingQuote = false;
while ($this->lexer->token[0] !== EmailLexer::S_AT) {
$previous = $this->lexer->getPrevious();
if ($this->lexer->token[0] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
if ($this->lexer->token[0] === EmailLexer::S_DQUOTE) {
if (!$closingQuote) {
if ($this->lexer->isNext(EmailLexer::GENERIC) && $previous[0] === EmailLexer::GENERIC) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
$this->warnings[] = EmailValidator::RFC5321_QUOTEDSTRING;
try {
$this->lexer->find(EmailLexer::S_DQUOTE);
$closingQuote = true;
} catch (\Exception $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDQUOTEDSTR');
}
}
//if ($previous[0] != EmailLexer::S_BACKSLASH) {
// throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
//}
//$this->warnings[] = EmailValidator::DEPREC_LOCALPART;
}
//Comments
if ($this->lexer->token[0] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
}
if ($this->lexer->token[0] === EmailLexer::S_DOT && $this->lexer->isNext(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEDOTS');
}
if ($this->lexer->token[0] === EmailLexer::S_DOT && $this->lexer->isNext(EmailLexer::S_AT)) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
if ($this->lexer->token[0] === EmailLexer::S_BACKSLASH) {
//if ($this->lexer->isNextAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
// $this->warnings[] = EmailValidator::DEPREC_QP;
//}
if ($this->lexer->isNext(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
if ($this->lexer->isNextAny(
array(
EmailLexer::INVALID, EmailLexer::S_LOWERTHAN, EmailLexer::S_GREATERTHAN
)
)
) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if (
$this->lexer->token[0] === EmailLexer::S_SP ||
$this->lexer->token[0] === EmailLexer::S_HTAB ||
$this->lexer->token[0] === EmailLexer::S_CR ||
$this->lexer->token[0] === EmailLexer::S_LF ||
$this->lexer->token[0] === EmailLexer::CRLF
) {
$this->parseFWS();
}
$this->lexer->moveNext();
}
$prev = $this->lexer->getPrevious();
if ($prev[1] > EmailValidator::RFC5322_LOCAL_TOOLONG) {
$this->warnings[] = EmailValidator::RFC5322_LOCAL_TOOLONG;
}
}
/**
* parseComments
*
* @return string the the comment
*/
private function parseComments()
{
$this->warnings[] = EmailValidator::CFWS_COMMENT;
while (!$this->lexer->isNext(EmailLexer::S_CLOSEPARENTHESIS)) {
try {
$this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDCOMMENT');
}
//$this->lexer->moveNext();
//scaping in a comment
if ($this->lexer->token[0] === EmailLexer::S_BACKSLASH) {
if ($this->lexer->isNextAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
$this->warnings[] = EmailValidator::DEPREC_QP;
}
}
$this->lexer->moveNext();
}
$this->lexer->moveNext();
if ($this->lexer->isNext(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->isNext(EmailLexer::S_AT)) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
}
}
/**
* parseFWS
*
* @throw InvalidArgumentException
*/
private function parseFWS()
{
$previous = $this->lexer->getPrevious();
if ($this->lexer->token[0] === EmailLexer::CRLF && $this->lexer->isNext(EmailLexer::CRLF)) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_X2");
}
if ($this->lexer->token[0] === EmailLexer::S_CR) {
throw new \InvalidArgumentException("ERR_CR_NO_LF");
}
if (
!$this->lexer->isNextAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB)) &&
$this->lexer->token[0] === EmailLexer::CRLF ) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_END");
}
if ($this->lexer->isNext(EmailLexer::GENERIC) && $this->lexer->token[0] !== EmailLexer::S_SP) {
throw new \InvalidArgumentException("ERR_ATEXT_AFTER_CFWS");
}
if ($this->lexer->token[0] === EmailLexer::S_LF || $this->lexer->token[0] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_CTEXT');
}
if ($this->lexer->isNext(EmailLexer::S_AT) || $previous[0] === EmailLexer::S_AT) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
} else {
$this->warnings[] = EmailValidator::CFWS_FWS;
}
}
}
@@ -0,0 +1,243 @@
<?php
namespace egulias\EmailValidator;
/**
* EmailValidator
*
* @author Eduardo Gulias Davis <me@egulias.com>
*/
class EmailValidator
{
// Address is invalid for any purpose
const ERR_CONSECUTIVEATS = 128;
const ERR_EXPECTING_DTEXT = 129;
const ERR_NOLOCALPART = 130;
const ERR_NODOMAIN = 131;
const ERR_CONSECUTIVEDOTS = 132;
const ERR_ATEXT_AFTER_CFWS = 133;
const ERR_ATEXT_AFTER_QS = 134;
const ERR_ATEXT_AFTER_DOMLIT = 135;
const ERR_EXPECTING_QPAIR = 136;
const ERR_EXPECTING_ATEXT = 137;
const ERR_EXPECTING_QTEXT = 138;
const ERR_EXPECTING_CTEXT = 139;
const ERR_BACKSLASHEND = 140;
const ERR_DOT_START = 141;
const ERR_DOT_END = 142;
const ERR_DOMAINHYPHENSTART = 143;
const ERR_DOMAINHYPHENEND = 144;
const ERR_UNCLOSEDQUOTEDSTR = 145;
const ERR_UNCLOSEDCOMMENT = 146;
const ERR_UNCLOSEDDOMLIT = 147;
const ERR_FWS_CRLF_X2 = 148;
const ERR_FWS_CRLF_END = 149;
const ERR_CR_NO_LF = 150;
const ERR_DEPREC_REACHED = 151;
const RFC5321_TLD = 9;
const RFC5321_TLDNUMERIC = 10;
const RFC5321_QUOTEDSTRING = 11;
const RFC5321_ADDRESSLITERAL = 12;
const RFC5321_IPV6DEPRECATED = 13;
const CFWS_COMMENT = 17;
const CFWS_FWS = 18;
const DEPREC_LOCALPART = 33;
const DEPREC_FWS = 34;
const DEPREC_QTEXT = 35;
const DEPREC_QP = 36;
const DEPREC_COMMENT = 37;
const DEPREC_CTEXT = 38;
const DEPREC_CFWS_NEAR_AT = 49;
const RFC5322_LOCAL_TOOLONG = 64;
const RFC5322_LABEL_TOOLONG = 63;
const RFC5322_DOMAIN = 65;
const RFC5322_TOOLONG = 66;
const RFC5322_DOMAIN_TOOLONG = 255;
const RFC5322_DOMAINLITERAL = 70;
const RFC5322_DOMLIT_OBSDTEXT = 71;
const RFC5322_IPV6_GRPCOUNT = 72;
const RFC5322_IPV6_2X2XCOLON = 73;
const RFC5322_IPV6_BADCHAR = 74;
const RFC5322_IPV6_MAXGRPS = 75;
const RFC5322_IPV6_COLONSTRT = 76;
const RFC5322_IPV6_COLONEND = 77;
const DNSWARN_NO_MX_RECORD = 5;
const DNSWARN_NO_RECORD = 6;
protected $parser;
protected $warnings = array();
protected $error;
protected $threshold = 255;
protected $emailParts = array();
public function __construct()
{
$this->parser = new EmailParser(new EmailLexer());
}
public function isValid($email, $checkDNS = false, $strict = false)
{
try {
$this->parser->parse((string)$email);
$this->emailParts = explode('@', $email);
$this->warnings = $this->parser->getWarnings();
} catch (\Exception $e) {
$rClass = new \ReflectionClass($this);
$this->error = $rClass->getConstant($e->getMessage());
return false;
}
if ($checkDNS) {
$dns = $this->checkDNS();
}
if ($this->hasWarnings() && ((int) max($this->warnings) > $this->threshold)) {
$this->error = self::ERR_DEPREC_REACHED;
return false;
}
return ($strict) ? (!$this->hasWarnings() && $dns) : true;
}
/**
* hasWarnings
*
* @return boolean
*/
public function hasWarnings()
{
return !empty($this->warnings);
}
/**
* getWarnings
*
* @return array
*/
public function getWarnings()
{
return $this->warnings;
}
/**
* getError
*
* @return string
*/
public function getError()
{
return $this->error;
}
/**
* setThreshols
*
* @param int $threshold
*
* @return EmailValidator
*/
public function setThreshold($threshold)
{
$this->threshold = (int) $threshold;
return $this;
}
/**
* getThreshold
*
* @return int
*/
public function getThreshold()
{
return $this->threshold;
}
protected function checkDNS()
{
$checked = false;
if (!function_exists('dns_get_record') && (
in_array(self::DNSWARN_NO_RECORD, $this->warnings) &&
in_array(self::DNSWARN_NO_MX_RECORD, $this->warnings)
)) {
return $checked;
}
// http://tools.ietf.org/html/rfc5321#section-2.3.5
// Names that can
// be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
// in Section 5) are permitted, as are CNAME RRs whose targets can be
// resolved, in turn, to MX or address RRs.
//
// http://tools.ietf.org/html/rfc5321#section-5.1
// The lookup first attempts to locate an MX record associated with the
// name. If a CNAME record is found, the resulting name is processed as
// if it were the initial name. ... If an empty list of MXs is returned,
// the address is treated as if it was associated with an implicit MX
// RR, with a preference of 0, pointing to that host.
//
// is_email() author's note: We will regard the existence of a CNAME to be
// sufficient evidence of the domain's existence. For performance reasons
// we will not repeat the DNS lookup for the CNAME's target, but we will
// raise a warning because we didn't immediately find an MX record.
$result = checkdnsrr(trim($this->parser->getParsedDomainPart()), 'MX');
$checked = true;
if (!$result) {
// Domain can't be found in DNS
$this->warnings[] = self::DNSWARN_NO_RECORD;
$checked = false;
}
// Check for TLD addresses
// -----------------------
// TLD addresses are specifically allowed in RFC 5321 but they are
// unusual to say the least. We will allocate a separate
// status to these addresses on the basis that they are more likely
// to be typos than genuine addresses (unless we've already
// established that the domain does have an MX record)
//
// http://tools.ietf.org/html/rfc5321#section-2.3.5
// In the case
// of a top-level domain used by itself in an email address, a single
// string is used without any dots. This makes the requirement,
// described in more detail below, that only fully-qualified domain
// names appear in SMTP transactions on the public Internet,
// particularly important where top-level domains are involved.
//
// TLD format
// ----------
// The format of TLDs has changed a number of times. The standards
// used by IANA have been largely ignored by ICANN, leading to
// confusion over the standards being followed. These are not defined
// anywhere, except as a general component of a DNS host name (a label).
// However, this could potentially lead to 123.123.123.123 being a
// valid DNS name (rather than an IP address) and thereby creating
// an ambiguity. The most authoritative statement on TLD formats that
// the author can find is in a (rejected!) erratum to RFC 1123
// submitted by John Klensin, the author of RFC 5321:
//
// http://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
// However, a valid host name can never have the dotted-decimal
// form #.#.#.#, since this change does not permit the highest-level
// component label to start with a digit even if it is not all-numeric.
if (!$checked && (!in_array(self::DNSWARN_NO_RECORD, $this->warnings) &&
!in_array(self::DNSWARN_NO_MX_RECORD, $this->warnings))
) {
if (in_array(RFC5322_DOMAINLITERAL)) {
$this->warnings[] = self::RFC5321_TLD;
}
//@TODO review how to test TLD
//if (isset($this->atomList[self::COMPONENT_DOMAIN][$this->elementCount][0]) &&
// is_numeric($this->atomList[self::COMPONENT_DOMAIN][$this->elementCount][0])
//) {
// $this->warnings[] = self::RFC5321_TLDNUMERIC;
//}
}
return $checked;
}
}
+320
View File
@@ -0,0 +1,320 @@
<?php
namespace tests;
require_once 'EmailValidator.php';
class EmailValidatorTest extends \PHPUnit_Framework_TestCase
{
protected $validator;
protected function setUp()
{
$this->validator = new \EmailValidator\EmailValidator();
}
protected function tearDown()
{
$this->validator = null;
}
/**
* @dataProvider getValidEmails
*/
public function testValidEmails($email)
{
$this->assertTrue($this->validator->isValid($email));
}
public function getValidEmails()
{
return array(
array('fabien@symfony.com'),
array('example@example.co.uk'),
array('fabien_potencier@example.fr'),
array('example@localhost'),
array('example((example))@fakedfake.co.uk'),
);
}
/**
* @dataProvider getExistingEmails
*/
public function testValidExistingEmails($email)
{
$this->assertTrue($this->validator->isValid($email, true));
}
public function getExistingEmails()
{
return array(
array('fabien@symfony.com'),
array('stloyd@o2.pl'),
array('ominic@sayers.cc'),
);
}
/**
* @dataProvider getNonexistingEmails
*/
public function testValidNonexistingEmails($email)
{
$this->assertTrue($this->validator->isValid($email, true));
}
/**
* @dataProvider getNonexistingEmails
*/
public function testValidNonexistingEmailsWithStrict($email)
{
$this->assertFalse($this->validator->isValid($email, true, true));
}
public function getNonexistingEmails()
{
return array(
array('example@fakedfake.co.uk'),
array('fabien_potencier@non-existing.fr'),
array('example@localhost'),
);
}
/**
* @dataProvider getInvalidEmails
*/
public function testTInvalidEmails($email)
{
$this->assertFalse($this->validator->isValid($email));
}
public function getInvalidEmails()
{
return array(
array('example.@example.co.uk'),
array('(fabien_potencier@example.fr)'),
array('example(example)example@example.co.uk'),
array('.example@localhost'),
array('ex\ample@localhost'),
array('example@local\host'),
array('example@localhost.'),
);
}
/**
* @dataProvider getInvalidEmailsWithErrors
*/
public function testInvalidEmailsWithErrorsCheck($errors, $email)
{
$this->assertFalse($this->validator->isValid($email));
$this->assertEquals($errors, $this->validator->getErrors());
}
public function getInvalidEmailsWithErrors()
{
return array(
array(array(\EmailValidator\EmailValidator::ERR_NOLOCALPART), '@example.co.uk'),
array(array(\EmailValidator\EmailValidator::ERR_NODOMAIN), 'example@'),
array(array(\EmailValidator\EmailValidator::ERR_DOMAINHYPHENEND), 'example@example-.co.uk'),
array(array(\EmailValidator\EmailValidator::ERR_DOMAINHYPHENEND), 'example@example-'),
array(array(\EmailValidator\EmailValidator::ERR_CONSECUTIVEATS), 'example@@example.co.uk'),
array(array(\EmailValidator\EmailValidator::ERR_CONSECUTIVEDOTS), 'example..example@example.co.uk'),
array(array(\EmailValidator\EmailValidator::ERR_CONSECUTIVEDOTS), 'example@example..co.uk'),
array(array(\EmailValidator\EmailValidator::ERR_EXPECTING_ATEXT), '<fabien_potencier>@example.fr'),
array(array(\EmailValidator\EmailValidator::ERR_DOT_START), '.example@localhost'),
array(array(\EmailValidator\EmailValidator::ERR_DOT_START), 'example@.localhost'),
array(array(\EmailValidator\EmailValidator::ERR_DOT_END), 'example@localhost.'),
array(array(\EmailValidator\EmailValidator::ERR_DOT_END), 'example.@example.co.uk'),
array(array(\EmailValidator\EmailValidator::ERR_UNCLOSEDCOMMENT), '(example@localhost'),
array(array(\EmailValidator\EmailValidator::ERR_UNCLOSEDQUOTEDSTR), '"example@localhost'),
array(array(\EmailValidator\EmailValidator::ERR_EXPECTING_ATEXT), 'exa"mple@localhost'),
array(array(\EmailValidator\EmailValidator::ERR_EXPECTING_ATEXT), "exampl\ne@example.co.uk"),
array(array(\EmailValidator\EmailValidator::ERR_EXPECTING_DTEXT), "example@[[]"),
array(array(\EmailValidator\EmailValidator::ERR_ATEXT_AFTER_CFWS), "exampl\te@example.co.uk"),
array(array(\EmailValidator\EmailValidator::ERR_CR_NO_LF), "example@exa\rmple.co.uk"),
array(array(\EmailValidator\EmailValidator::ERR_CR_NO_LF), "example@[\r]"),
array(array(\EmailValidator\EmailValidator::ERR_CR_NO_LF), "exam\rple@example.co.uk"),
array(array(\EmailValidator\EmailValidator::ERR_CR_NO_LF), "\"\r\"@localhost"),
);
}
/**
* @dataProvider getInvalidEmailsWithWarnings
*/
public function testInvalidEmailsWithWarningsCheck($warnings, $email)
{
$this->assertTrue($this->validator->isValid($email, true));
$this->assertEquals($warnings, $this->validator->getWarnings());
}
public function getInvalidEmailsWithWarnings()
{
return array(
array(
array(
\EmailValidator\EmailValidator::DEPREC_CFWS_NEAR_AT,
),
'example @example.co.uk'
),
array(
array(
\EmailValidator\EmailValidator::DEPREC_CFWS_NEAR_AT,
),
'example@ example.co.uk'
),
array(
array(
\EmailValidator\EmailValidator::CFWS_COMMENT,
),
'example@example(example).co.uk'
),
array(
array(
\EmailValidator\EmailValidator::CFWS_COMMENT,
\EmailValidator\EmailValidator::DEPREC_CFWS_NEAR_AT,
),
'example(example)@example.co.uk'
),
array(
array(
\EmailValidator\EmailValidator::RFC5321_QUOTEDSTRING,
\EmailValidator\EmailValidator::CFWS_FWS,
),
"\"\t\"@example.co.uk"
),
array(
array(
\EmailValidator\EmailValidator::RFC5321_ADDRESSLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[127.0.0.1]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5321_ADDRESSLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5321_IPV6DEPRECATED,
\EmailValidator\EmailValidator::RFC5321_ADDRESSLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370::]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_IPV6_MAXGRPS,
\EmailValidator\EmailValidator::RFC5321_ADDRESSLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334::]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_IPV6_2X2XCOLON,
\EmailValidator\EmailValidator::RFC5321_ADDRESSLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:1::1::1]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_DOMLIT_OBSDTEXT,
\EmailValidator\EmailValidator::RFC5322_DOMAINLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
"example@[\n]"
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_DOMAINLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[::1]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_DOMAINLITERAL,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[::123.45.67.178]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_IPV6_GRPCOUNT,
\EmailValidator\EmailValidator::RFC5322_IPV6_COLONSTRT,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6::2001:0db8:85a3:0000:0000:8a2e:0370:7334]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_IPV6_BADCHAR,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:z001:0db8:85a3:0000:0000:8a2e:0370:7334]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_IPV6_COLONEND,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:]'
),
array(
array(
\EmailValidator\EmailValidator::RFC5321_QUOTEDSTRING,
),
'"example"@example.co.uk'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_LOCAL_TOOLONG,
),
'too_long_localpart_too_long_localpart_too_long_localpart_too_long_localpart@example.co.uk'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_LABEL_TOOLONG,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart.co.uk'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_DOMAIN_TOOLONG,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart'
),
array(
array(
\EmailValidator\EmailValidator::RFC5322_TOOLONG,
\EmailValidator\EmailValidator::DNSWARN_NO_MX_RECORD,
\EmailValidator\EmailValidator::DNSWARN_NO_RECORD,
),
'example@toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpar'
),
);
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
if ( ! is_file($autoloadFile = __DIR__.'/../vendor/autoload.php')) {
echo 'Could not find "vendor/autoload.php". Did you forget to run "composer install --dev"?'.PHP_EOL;
exit(1);
}
require_once $autoloadFile;
@@ -0,0 +1,35 @@
<?php
use egulias\EmailValidator\EmailValidator;
require __DIR__ . '/../../bootstrap.php';
$iterations = 10000;
$testingMail = 'fabien@symfony.com';
echo 'Testing ' . $iterations . ' iterations with ' . $testingMail . PHP_EOL;
$a = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$isValid = filter_var($testingMail, FILTER_VALIDATE_EMAIL);
}
$b = microtime(true);
echo ($b - $a) . ' seconds with filter_var' . PHP_EOL;
$a = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$validator = new EmailValidator();
$isValid = $validator->isValid($testingMail);
}
$b = microtime(true);
echo ($b - $a) . ' seconds with EmailValidator + instantiation' . PHP_EOL;
$a = microtime(true);
$validator = new EmailValidator();
for ($i = 0; $i < $iterations; $i++) {
$isValid = $validator->isValid($testingMail);
}
$b = microtime(true);
echo ($b - $a) . ' seconds with EmailValidator once instanced' . PHP_EOL;
@@ -0,0 +1,70 @@
<?php
namespace egulias\Tests\EmailValidator;
use egulias\EmailValidator\EmailLexer;
class EmailLexerTests extends \PHPUnit_Framework_TestCase
{
public function testLexerExtendsLib()
{
$lexer = new EmailLexer();
$this->assertInstanceOf('JMS\Parser\AbstractLexer', $lexer);
}
/**
* @dataProvider getTokens
*
*/
public function testLexerTokens($str, $token)
{
$lexer = new EmailLexer();
$lexer->setInput($str);
$lexer->moveNext();
$this->assertEquals($token, $lexer->token[0]);
}
public function testLexerForTab()
{
$lexer = new EmailLexer();
$lexer->setInput("foo\tbar");
$lexer->skipUntil(EmailLexer::S_HTAB);
$lexer->moveNext();
$this->assertEquals(EmailLexer::S_HTAB, $lexer->token[0]);
}
public function getTokens()
{
return array(
array("foo", EmailLexer::GENERIC),
array("\r", EmailLexer::S_CR),
array("\t", EmailLexer::S_HTAB),
array("\r\n", EmailLexer::CRLF),
array("\n", EmailLexer::S_LF),
array(" ", EmailLexer::S_SP),
array("@", EmailLexer::S_AT),
array("IPv6", EmailLexer::S_IPV6TAG),
array("::", EmailLexer::S_DOUBLECOLON),
array(":", EmailLexer::S_COLON),
array(".", EmailLexer::S_DOT),
array("\"", EmailLexer::S_DQUOTE),
array("-", EmailLexer::S_HYPHEN),
array("\\", EmailLexer::S_BACKSLASH),
array("(", EmailLexer::S_OPENPARENTHESIS),
array(")", EmailLexer::S_CLOSEPARENTHESIS),
array('<', EmailLexer::S_LOWERTHAN),
array('>', EmailLexer::S_GREATERTHAN),
array('[', EmailLexer::S_OPENBRACKET),
array(']', EmailLexer::S_CLOSEBRACKET),
array(';', EmailLexer::S_SEMICOLON),
array(',', EmailLexer::S_COMMA),
array('<', EmailLexer::S_LOWERTHAN),
array('>', EmailLexer::S_GREATERTHAN),
array('{', EmailLexer::S_OPENQBRACKET),
array('}', EmailLexer::S_CLOSEQBRACKET),
array('', EmailLexer::S_EMPTY),
array(chr(31), EmailLexer::INVALID)
);
}
}
@@ -0,0 +1,15 @@
<?php
namespace egulias\Tests\EmailValidator;
use egulias\EmailValidator\EmailParser;
class EmailParserTests extends \PHPUnit_Framework_TestCase
{
public function testParserExtendsLib()
{
$mock = $this->getMock('egulias\EmailValidator\EmailLexer');
$parser = new EmailParser($mock);
$this->assertInstanceOf('JMS\Parser\AbstractParser', $parser);
}
}
@@ -0,0 +1,273 @@
<?php
namespace egulias\Tests\EmailValidator;
use egulias\EmailValidator\EmailValidator;
class EmailValidatorTest extends \PHPUnit_Framework_TestCase
{
protected $validator;
protected function setUp()
{
$this->validator = new EmailValidator();
}
protected function tearDown()
{
$this->validator = null;
}
/**
* @dataProvider getValidEmails
*/
public function testValidEmails($email)
{
$this->assertTrue($this->validator->isValid($email));
}
public function getValidEmails()
{
return array(
array('fabien@symfony.com'),
array('example@example.co.uk'),
array('fabien_potencier@example.fr'),
array('example@localhost'),
array('fab\'ien@symfony.com'),
array('example((example))@fakedfake.co.uk'),
);
}
/**
* @dataProvider getInvalidEmails
*/
public function testInvalidEmails($email)
{
$this->assertFalse($this->validator->isValid($email));
}
public function getInvalidEmails()
{
return array(
array('example.@example.co.uk'),
//array('(fabien_potencier@example.fr)'),
array('example(example)example@example.co.uk'),
array('.example@localhost'),
array('ex\ample@localhost'),
array('example@local\host'),
array('example@localhost.'),
);
}
/**
* @dataProvider getInvalidEmailsWithErrors
*/
public function testInvalidEmailsWithErrorsCheck($errors, $email)
{
$this->assertFalse($this->validator->isValid($email));
$this->assertEquals($errors, $this->validator->getError());
}
public function getInvalidEmailsWithErrors()
{
return array(
array(EmailValidator::ERR_NOLOCALPART, '@example.co.uk'),
array(EmailValidator::ERR_NODOMAIN, 'example@'),
array(EmailValidator::ERR_DOMAINHYPHENEND, 'example@example-.co.uk'),
array(EmailValidator::ERR_DOMAINHYPHENEND, 'example@example-'),
array(EmailValidator::ERR_CONSECUTIVEATS, 'example@@example.co.uk'),
array(EmailValidator::ERR_CONSECUTIVEDOTS, 'example..example@example.co.uk'),
array(EmailValidator::ERR_CONSECUTIVEDOTS, 'example@example..co.uk'),
array(EmailValidator::ERR_EXPECTING_ATEXT, '<fabien_potencier>@example.fr'),
array(EmailValidator::ERR_DOT_START, '.example@localhost'),
array(EmailValidator::ERR_DOT_START, 'example@.localhost'),
array(EmailValidator::ERR_DOT_END, 'example@localhost.'),
array(EmailValidator::ERR_DOT_END, 'example.@example.co.uk'),
array(EmailValidator::ERR_UNCLOSEDCOMMENT, '(example@localhost'),
array(EmailValidator::ERR_UNCLOSEDQUOTEDSTR, '"example@localhost'),
array(EmailValidator::ERR_EXPECTING_ATEXT, 'exa"mple@localhost'),
//This was the original. But atext is not allowed after \n
//array(EmailValidator::ERR_EXPECTING_ATEXT, "exampl\ne@example.co.uk"),
array(EmailValidator::ERR_ATEXT_AFTER_CFWS, "exampl\ne@example.co.uk"),
array(EmailValidator::ERR_EXPECTING_DTEXT, "example@[[]"),
array(EmailValidator::ERR_ATEXT_AFTER_CFWS, "exampl\te@example.co.uk"),
array(EmailValidator::ERR_CR_NO_LF, "example@exa\rmple.co.uk"),
array(EmailValidator::ERR_CR_NO_LF, "example@[\r]"),
array(EmailValidator::ERR_CR_NO_LF, "exam\rple@example.co.uk"),
array(EmailValidator::ERR_CR_NO_LF, "\"\r\"@localhost"),
);
}
/**
* @dataProvider getInvalidEmailsWithWarnings
*/
public function testInvalidEmailsWithWarningsCheck($warnings, $email)
{
$this->assertTrue($this->validator->isValid($email, true));
$this->assertEquals($warnings, $this->validator->getWarnings());
}
public function getInvalidEmailsWithWarnings()
{
return array(
array(
array(
EmailValidator::DEPREC_CFWS_NEAR_AT,
),
'example @example.co.uk'
),
array(
array(
EmailValidator::DEPREC_CFWS_NEAR_AT,
),
'example@ example.co.uk'
),
array(
array(
EmailValidator::CFWS_COMMENT,
),
'example@example(examplecomment).co.uk'
),
array(
array(
EmailValidator::CFWS_COMMENT,
EmailValidator::DEPREC_CFWS_NEAR_AT,
),
'example(examplecomment)@example.co.uk'
),
array(
array(
EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::CFWS_FWS,
),
"\"\t\"@example.co.uk"
),
array(
array(
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[127.0.0.1]'
),
array(
array(
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]'
),
array(
array(
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::RFC5321_IPV6DEPRECATED,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370::]'
),
array(
array(
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::RFC5322_IPV6_MAXGRPS,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334::]'
),
array(
array(
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::RFC5322_IPV6_2X2XCOLON,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:1::1::1]'
),
array(
array(
EmailValidator::RFC5322_DOMLIT_OBSDTEXT,
EmailValidator::RFC5322_DOMAINLITERAL,
EmailValidator::DNSWARN_NO_RECORD,
),
"example@[\n]"
),
array(
array(
EmailValidator::RFC5322_DOMAINLITERAL,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[::1]'
),
array(
array(
EmailValidator::RFC5322_DOMAINLITERAL,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[::123.45.67.178]'
),
array(
array(
EmailValidator::RFC5322_IPV6_COLONSTRT,
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::RFC5322_IPV6_GRPCOUNT,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6::2001:0db8:85a3:0000:0000:8a2e:0370:7334]'
),
array(
array(
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::RFC5322_IPV6_BADCHAR,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:z001:0db8:85a3:0000:0000:8a2e:0370:7334]'
),
array(
array(
EmailValidator::RFC5322_IPV6_COLONEND,
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:]'
),
array(
array(
EmailValidator::RFC5321_QUOTEDSTRING,
),
'"example"@example.co.uk'
),
array(
array(
EmailValidator::RFC5322_LOCAL_TOOLONG,
),
'too_long_localpart_too_long_localpart_too_long_localpart_too_long_localpart@example.co.uk'
),
array(
array(
EmailValidator::RFC5322_LABEL_TOOLONG,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart.co.uk'
),
array(
array(
EmailValidator::RFC5322_DOMAIN_TOOLONG,
EmailValidator::RFC5322_TOOLONG,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocal'.
'parttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart'.
'toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart'
),
array(
array(
EmailValidator::RFC5322_DOMAIN_TOOLONG,
EmailValidator::RFC5322_TOOLONG,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocal'.
'parttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart'.
'toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpar'
),
);
}
}
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="bootstrap.php"
>
<testsuites>
<testsuite name="EmailValidator Test Suite">
<directory>./IsEmail/</directory>
</testsuite>
</testsuites>
</phpunit>