mirror of
https://github.com/egulias/EmailValidator.git
synced 2026-09-01 04:59:18 +00:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
report/
|
||||
vendor/
|
||||
.idea
|
||||
@@ -0,0 +1,8 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.3
|
||||
|
||||
before_script:
|
||||
- wget http://getcomposer.org/composer.phar
|
||||
- php composer.phar install
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
"&" / "'" /
|
||||
"*" / "+" /
|
||||
"-" / "/" /
|
||||
"=" / "?" /
|
||||
"^" / "_" /
|
||||
"`" / "{" /
|
||||
"|" / "}" /
|
||||
"~"
|
||||
|
||||
specials = "(" / ")" / ; Special characters that do
|
||||
"<" / ">" / ; 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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user