Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -0,0 +1,3 @@
service_name: travis-ci
src_dir: .
coverage_clover: build/logs/clover*.xml

View file

@ -0,0 +1,2 @@
report/
vendor/

View file

@ -0,0 +1,43 @@
sudo: false
language: php
cache:
directories:
- $HOME/.composer/cache/files
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- hhvm
env:
global:
- deps=high
matrix:
fast_finish: true
include:
- php: 5.3
dist: precise
- php: 5.3
dist: precise
env: deps=low
- php: 5.4
env: deps=no
- php: 5.5
env: deps=no
install:
- if [ "$deps" = "no" ]; then composer install; fi
- if [ "$deps" = "low" ]; then composer update --prefer-lowest; fi
- if [ "$deps" = "high" ]; then composer update; fi
script:
- mkdir -p build/logs
- composer test -- --coverage-clover build/logs/clover.xml

19
vendor/egulias/email-validator/LICENSE vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 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.

View file

@ -0,0 +1,59 @@
#EmailValidator
[![Build Status](https://travis-ci.org/egulias/EmailValidator.png?branch=master)](https://travis-ci.org/egulias/EmailValidator) [![Coverage Status](https://coveralls.io/repos/egulias/EmailValidator/badge.png?branch=master)](https://coveralls.io/r/egulias/EmailValidator?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/egulias/EmailValidator/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/egulias/EmailValidator/?branch=master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6/small.png)](https://insight.sensiolabs.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6)
=============================
With the help of
![Powered by PhpStorm](https://www.jetbrains.com/phpstorm/documentation/docs/logo_phpstorm.png)
##Installation##
Run the command below to install via Composer
```shell
composer require egulias/email-validator "~1.2"
```
##Usage##
Simple example:
```php
<?php
use Egulias\EmailValidator\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
use Egulias\EmailValidator\EmailValidator;
$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() . ')';
}
```
##Contributors##
As this is a port from another library and work, here are other people related to the previous:
* Ricard Clau [@ricardclau](http://github.com/ricardclau): Performance against PHP built-in filter_var
* Josepf Bielawski [@stloyd](http://github.com/stloyd): For its first re-work of Dominic's lib
* Dominic Sayers [@dominicsayers](http://github.com/dominicsayers): The original isemail function
##License##
Released under the MIT License attached with this code.

View file

@ -0,0 +1,31 @@
{
"name": "egulias/email-validator",
"description": "A library for validating emails",
"homepage": "https://github.com/egulias/EmailValidator",
"type": "Library",
"keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"],
"license": "MIT",
"authors": [
{"name": "Eduardo Gulias Davis"}
],
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"require": {
"php": ">= 5.3.3",
"doctrine/lexer": "^1.0.1"
},
"require-dev" : {
"phpunit/phpunit": "^4.8.24"
},
"autoload": {
"psr-0": {
"Egulias\\": "src/"
}
},
"scripts": {
"test": "phpunit"
}
}

1078
vendor/egulias/email-validator/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,69 @@
Email length
------------
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
http://tools.ietf.org/html/rfc1035#section-2.3.4
DNS
---
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.
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.
Comments
--------
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)

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>

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>

View file

@ -0,0 +1,26 @@
<?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="tests/bootstrap.php"
>
<testsuites>
<testsuite name="EmailValidator Test Suite">
<directory>./tests/egulias/Tests/</directory>
<exclude>./vendor/</exclude>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>./vendor</directory>
</blacklist>
</filter>
</phpunit>

View file

@ -0,0 +1,221 @@
<?php
namespace Egulias\EmailValidator;
use Doctrine\Common\Lexer\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;
const S_CR = 269;
const S_LF = 270;
const S_IPV6TAG = 271;
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_SLASH = 278;
const S_EMPTY = null;
const GENERIC = 300;
const CRLF = 301;
const INVALID = 302;
const ASCII_INVALID_FROM = 127;
const ASCII_INVALID_TO = 199;
/**
* 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_SLASH,
',' => 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_OPENQBRACKET,
'}' => self::S_CLOSEQBRACKET,
'' => self::S_EMPTY,
'\0' => self::C_NUL,
);
protected $hasInvalidTokens = false;
protected $previous;
public function reset()
{
$this->hasInvalidTokens = false;
parent::reset();
}
public function hasInvalidTokens()
{
return $this->hasInvalidTokens;
}
/**
* @param $type
* @throws \UnexpectedValueException
* @return boolean
*/
public function find($type)
{
$search = clone $this;
$search->skipUntil($type);
if (!$search->lookahead) {
throw new \UnexpectedValueException($type . ' not found');
}
return true;
}
/**
* getPrevious
*
* @return array token
*/
public function getPrevious()
{
return $this->previous;
}
/**
* moveNext
*
* @return boolean
*/
public function moveNext()
{
$this->previous = $this->token;
return parent::moveNext();
}
/**
* Lexical catchable patterns.
*
* @return string[]
*/
protected function getCatchablePatterns()
{
return array(
'[a-zA-Z_]+[46]?', //ASCII and domain literal
'[^\x00-\x7F]', //UTF-8
'[0-9]+',
'\r\n',
'::',
'\s+?',
'.',
);
}
/**
* Lexical non-catchable patterns.
*
* @return string[]
*/
protected function getNonCatchablePatterns()
{
return array('[\xA0-\xff]+');
}
/**
* Retrieve token type. Also processes the token value if necessary.
*
* @param string $value
* @throws \InvalidArgumentException
* @return integer
*/
protected function getType(&$value)
{
if ($this->isNullType($value)) {
return self::C_NUL;
}
if ($this->isValid($value)) {
return $this->charValue[$value];
}
if ($this->isUTF8Invalid($value)) {
$this->hasInvalidTokens = true;
return self::INVALID;
}
return self::GENERIC;
}
protected function isValid($value)
{
if (isset($this->charValue[$value])) {
return true;
}
return false;
}
/**
* @param $value
* @return bool
*/
protected function isNullType($value)
{
if ($value === "\0") {
return true;
}
return false;
}
/**
* @param $value
* @return bool
*/
protected function isUTF8Invalid($value)
{
if (preg_match('/\p{Cc}+/u', $value)) {
return true;
}
return false;
}
protected function getModifiers()
{
return 'iu';
}
}

View file

@ -0,0 +1,100 @@
<?php
namespace Egulias\EmailValidator;
use Egulias\EmailValidator\Parser\DomainPart;
use Egulias\EmailValidator\Parser\LocalPart;
/**
* EmailParser
*
* @author Eduardo Gulias Davis <me@egulias.com>
*/
class EmailParser
{
const EMAIL_MAX_LENGTH = 254;
protected $warnings = array();
protected $domainPart = '';
protected $localPart = '';
protected $lexer;
protected $localPartParser;
protected $domainPartParser;
public function __construct(EmailLexer $lexer)
{
$this->lexer = $lexer;
$this->localPartParser = new LocalPart($this->lexer);
$this->domainPartParser = new DomainPart($this->lexer);
}
/**
* @param $str
* @return array
*/
public function parse($str)
{
$this->lexer->setInput($str);
if (!$this->hasAtToken()) {
throw new \InvalidArgumentException('ERR_NOLOCALPART');
}
$this->localPartParser->parse($str);
$this->domainPartParser->parse($str);
$this->setParts($str);
if ($this->lexer->hasInvalidTokens()) {
throw new \InvalidArgumentException('ERR_INVALID_ATEXT');
}
return array('local' => $this->localPart, 'domain' => $this->domainPart);
}
public function getWarnings()
{
$localPartWarnings = $this->localPartParser->getWarnings();
$domainPartWarnings = $this->domainPartParser->getWarnings();
$this->warnings = array_merge($localPartWarnings, $domainPartWarnings);
$this->addLongEmailWarning($this->localPart, $this->domainPart);
return $this->warnings;
}
public function getParsedDomainPart()
{
return $this->domainPart;
}
protected function setParts($email)
{
$parts = explode('@', $email);
$this->domainPart = $this->domainPartParser->getDomainPart();
$this->localPart = $parts[0];
}
protected function hasAtToken()
{
$this->lexer->moveNext();
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
return false;
}
return true;
}
/**
* @param string $localPart
* @param string $parsedDomainPart
*/
protected function addLongEmailWarning($localPart, $parsedDomainPart)
{
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
$this->warnings[] = EmailValidator::RFC5322_TOOLONG;
}
}
}

View file

@ -0,0 +1,209 @@
<?php
namespace Egulias\EmailValidator;
/**
* EmailValidator
*
* @author Eduardo Gulias Davis <me@egulias.com>
*/
class EmailValidator implements EmailValidatorInterface
{
/**
* Critical validation errors used to indicate that
* an email address is invalid:
*/
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_EXPECTING_QPAIR = 136;
const ERR_EXPECTING_ATEXT = 137;
const ERR_EXPECTING_CTEXT = 139;
const ERR_DOT_START = 141;
const ERR_DOT_END = 142;
const ERR_DOMAINHYPHENEND = 144;
const ERR_UNCLOSEDQUOTEDSTR = 145;
const ERR_UNCLOSEDCOMMENT = 146;
const ERR_FWS_CRLF_X2 = 148;
const ERR_FWS_CRLF_END = 149;
const ERR_CR_NO_LF = 150;
const ERR_DEPREC_REACHED = 151;
const ERR_UNOPENEDCOMMENT = 152;
const ERR_ATEXT_AFTER_QS = 134; // not in use
const ERR_ATEXT_AFTER_DOMLIT = 135; // not in use
const ERR_EXPECTING_QTEXT = 138; // not in use
const ERR_BACKSLASHEND = 140; // not in use
const ERR_DOMAINHYPHENSTART = 143; // not in use
const ERR_UNCLOSEDDOMLIT = 147; // not in use
/**
* Informational validation warnings regarding unusual or
* deprecated features found in an email address:
*/
// Address is valid for SMTP (RFC-5321), but has unusual elements.
const RFC5321_TLD = 9;
const RFC5321_QUOTEDSTRING = 11;
const RFC5321_ADDRESSLITERAL = 12;
const RFC5321_IPV6DEPRECATED = 13;
const RFC5321_TLDNUMERIC = 10; // not in use
// Address is only valid according to the broad
// definition of RFC-5322. It is otherwise invalid.
const RFC5322_LOCAL_TOOLONG = 64;
const RFC5322_LABEL_TOOLONG = 63;
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 RFC5322_DOMAIN = 65; // not in use
// Address contains deprecated elements, but may
// still be valid in restricted contexts.
const DEPREC_QP = 36;
const DEPREC_COMMENT = 37;
const DEPREC_CFWS_NEAR_AT = 49;
const DEPREC_LOCALPART = 33; // not in use
const DEPREC_FWS = 34; // not in use
const DEPREC_QTEXT = 35; // not in use
const DEPREC_CTEXT = 38; // not in use
// Address is valid within the message,
// but cannot be used unmodified in the envelope.
const CFWS_COMMENT = 17;
const CFWS_FWS = 18;
// Hostname DNS checks were unsuccessful.
const DNSWARN_NO_MX_RECORD = 5;
const DNSWARN_NO_RECORD = 6;
/**
* @var EmailParser
*/
protected $parser;
/**
* Contains any informational warnings regarding unusual/deprecated
* features that were encountered during validation.
*
* @var array
*/
protected $warnings = array();
/**
* If a critical validation problem is encountered, this will be
* set to the value of one of this class's ERR_* constants.
*
* @var int
*/
protected $error;
/**
* @var int
*/
protected $threshold = 255;
public function __construct()
{
$this->parser = new EmailParser(new EmailLexer());
}
/**
* {@inheritdoc}
*/
public function isValid($email, $checkDNS = false, $strict = false)
{
try {
$this->parser->parse((string)$email);
$this->warnings = $this->parser->getWarnings();
} catch (\Exception $e) {
$rClass = new \ReflectionClass($this);
$this->error = $rClass->getConstant($e->getMessage());
return false;
}
$dnsProblemExists = ($checkDNS ? !$this->checkDNS() : false);
if ($this->hasWarnings() && ((int) max($this->warnings) > $this->threshold)) {
$this->error = self::ERR_DEPREC_REACHED;
return false;
}
return !($dnsProblemExists || $strict && $this->hasWarnings());
}
/**
* {@inheritdoc}
*/
public function hasWarnings()
{
return !empty($this->warnings);
}
/**
* {@inheritdoc}
*/
public function getWarnings()
{
return $this->warnings;
}
/**
* {@inheritdoc}
*/
public function getError()
{
return $this->error;
}
/**
* {@inheritdoc}
*/
public function setThreshold($threshold)
{
$this->threshold = (int) $threshold;
return $this;
}
/**
* {@inheritdoc}
*/
public function getThreshold()
{
return $this->threshold;
}
/**
* @return bool Whether or not an MX record exists for the
* email address's host name.
*/
protected function checkDNS()
{
$host = $this->parser->getParsedDomainPart();
$host = rtrim($host, '.') . '.';
$mxRecordExists = checkdnsrr($host, 'MX');
if (!$mxRecordExists) {
$this->warnings[] = self::DNSWARN_NO_RECORD;
$this->addTLDWarnings();
}
return $mxRecordExists;
}
protected function addTLDWarnings()
{
if (!in_array(self::DNSWARN_NO_RECORD, $this->warnings) &&
!in_array(self::DNSWARN_NO_MX_RECORD, $this->warnings) &&
in_array(self::RFC5322_DOMAINLITERAL, $this->warnings)
) {
$this->warnings[] = self::RFC5321_TLD;
}
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Egulias\EmailValidator;
/**
* EmailValidatorInterface
*
* @author Chris McCafferty <cilefen@gmail.com>
*/
interface EmailValidatorInterface
{
/**
* Validates an email address against the following standards:
*
* RFC-5321: Simple Mail Transfer Protocol
* RFC-5322: Internet Message Format
* RFC-6530: Overview and Framework for Internationalized Email
* RFC-6531: SMTP Extension for Internationalized Email
* RFC-6532: Internationalized Email Headers
* RFC-1123 section 2.1: Requirements for Internet Hosts -- Application and Support
* RFC-4291 section 2.2: IP Version 6 Addressing Architecture
*
* @param string $email The email address to validate.
* @param bool $checkDNS Whether or not the email address's hostname should
* be confirmed with a DNS lookup. This only comes
* into play if strict mode is also enabled.
* @param bool $strict If this is true, and any informational warnings
* were raised during validation, the email address
* will be considered invalid. Additionally, if
* $checkDNS is true and the DNS lookup failed,
* the email address will be considered invalid.
* @return bool
*/
public function isValid($email, $checkDNS = false, $strict = false);
/**
* @return bool
*/
public function hasWarnings();
/**
* @return array
*/
public function getWarnings();
/**
* @return string
*/
public function getError();
/**
* @param int $threshold The acceptable number of deprecation warnings.
*
* @return EmailValidator
*/
public function setThreshold($threshold);
/**
* @return int
*/
public function getThreshold();
}

View file

@ -0,0 +1,339 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
class DomainPart extends Parser
{
const DOMAIN_MAX_LENGTH = 254;
protected $domainPart = '';
public function parse($domainPart)
{
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
if ($this->lexer->token['type'] === EmailLexer::S_EMPTY) {
throw new \InvalidArgumentException('ERR_NODOMAIN');
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->warnings[] = EmailValidator::DEPREC_COMMENT;
$this->parseDomainComments();
}
$domain = $this->doParseDomainPart();
$prev = $this->lexer->getPrevious();
$length = strlen($domain);
if ($prev['type'] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
if ($prev['type'] === EmailLexer::S_HYPHEN) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($length > self::DOMAIN_MAX_LENGTH) {
$this->warnings[] = EmailValidator::RFC5322_DOMAIN_TOOLONG;
}
if ($prev['type'] === EmailLexer::S_CR) {
throw new \InvalidArgumentException('ERR_FWS_CRLF_END');
}
$this->domainPart = $domain;
}
public function getDomainPart()
{
return $this->domainPart;
}
public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
{
$prev = $this->lexer->getPrevious();
if ($prev['type'] === EmailLexer::S_COLON) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONEND;
}
$IPv6 = substr($addressLiteral, 5);
//Daniel Marschall's new IPv6 testing strategy
$matchesIP = explode(':', $IPv6);
$groupCount = count($matchesIP);
$colons = strpos($IPv6, '::');
if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
$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;
}
return;
}
if ($colons !== strrpos($IPv6, '::')) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_2X2XCOLON;
return;
}
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) {
$this->warnings[] = EmailValidator::RFC5321_IPV6DEPRECATED;
}
}
protected function doParseDomainPart()
{
$domain = '';
$openedParenthesis = 0;
$openBrackets = false;
do {
$prev = $this->lexer->getPrevious();
$this->checkNotAllowedChars($this->lexer->token);
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
$openedParenthesis += $this->getOpenedParenthesis();
$this->lexer->moveNext();
$tmpPrev = $this->lexer->getPrevious();
if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
$openedParenthesis--;
}
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
if ($openedParenthesis === 0) {
throw new \InvalidArgumentException('ERR_UNOPENEDCOMMENT');
} else {
$openedParenthesis--;
}
}
$this->checkConsecutiveDots();
$this->checkDomainPartExceptions($prev);
if ($openBrackets = $this->hasBrackets($openBrackets)) {
$this->parseDomainLiteral();
}
$this->checkLabelLength($prev);
if ($this->isFWS()) {
$this->parseFWS();
}
$domain .= $this->lexer->token['value'];
$this->lexer->moveNext();
} while ($this->lexer->token);
return $domain;
}
private function checkNotAllowedChars($token)
{
$notAllowed = array(EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true);
if (isset($notAllowed[$token['type']])) {
throw new \InvalidArgumentException('ERR_DOMAIN_CHAR_NOT_ALLOWED');
}
}
protected function parseDomainLiteral()
{
if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
$lexer = clone $this->lexer;
$lexer->moveNext();
if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
}
return $this->doParseDomainLiteral();
}
protected function doParseDomainLiteral()
{
$IPv6TAG = false;
$addressLiteral = '';
do {
if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
}
if ($this->lexer->token['type'] === EmailLexer::INVALID ||
$this->lexer->token['type'] === EmailLexer::C_DEL ||
$this->lexer->token['type'] === EmailLexer::S_LF
) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
}
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
}
if ($this->lexer->isNextTokenAny(
array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
)) {
$this->warnings[] = EmailValidator::CFWS_FWS;
$this->parseFWS();
}
if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
throw new \InvalidArgumentException('ERR_CR_NO_LF');
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
$addressLiteral .= $this->lexer->token['value'];
$this->lexer->moveNext();
$this->validateQuotedPair();
}
if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
$IPv6TAG = true;
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
break;
}
$addressLiteral .= $this->lexer->token['value'];
} while ($this->lexer->moveNext());
$addressLiteral = str_replace('[', '', $addressLiteral);
$addressLiteral = $this->checkIPV4Tag($addressLiteral);
if (false === $addressLiteral) {
return $addressLiteral;
}
if (!$IPv6TAG) {
$this->warnings[] = EmailValidator::RFC5322_DOMAINLITERAL;
return $addressLiteral;
}
$this->warnings[] = EmailValidator::RFC5321_ADDRESSLITERAL;
$this->checkIPV6Tag($addressLiteral);
return $addressLiteral;
}
protected function checkIPV4Tag($addressLiteral)
{
$matchesIP = array();
// 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 false;
}
// Convert IPv4 part to IPv6 format for further testing
$addressLiteral = substr($addressLiteral, 0, $index) . '0:0';
}
return $addressLiteral;
}
protected function checkDomainPartExceptions($prev)
{
$invalidDomainTokens = array(
EmailLexer::S_DQUOTE => true,
EmailLexer::S_SEMICOLON => true,
EmailLexer::S_GREATERTHAN => true,
EmailLexer::S_LOWERTHAN => true,
);
if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
throw new \InvalidArgumentException('ERR_COMMA_IN_DOMAIN');
}
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEATS');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
&& $this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
protected function hasBrackets($openBrackets)
{
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEBRACKET && !$openBrackets) {
throw new \InvalidArgumentException('ERR_EXPECTING_OPENBRACKET');
}
if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
return false;
}
try {
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_EXPECTING_DOMLIT_CLOSE');
}
return true;
}
protected function checkLabelLength($prev)
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$prev['type'] === EmailLexer::GENERIC &&
strlen($prev['value']) > 63
) {
$this->warnings[] = EmailValidator::RFC5322_LABEL_TOOLONG;
}
}
protected function parseDomainComments()
{
$this->isUnclosedComment();
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
$this->warnEscaping();
$this->lexer->moveNext();
}
$this->lexer->moveNext();
if ($this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
}

View file

@ -0,0 +1,127 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
class LocalPart extends Parser
{
public function parse($localPart)
{
$parseDQuote = true;
$closingQuote = false;
$openedParenthesis = 0;
while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) {
if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
$closingQuote = $this->checkDQUOTE($closingQuote);
if ($closingQuote && $parseDQuote) {
$parseDQuote = $this->parseDoubleQuote();
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
$openedParenthesis += $this->getOpenedParenthesis();
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
if ($openedParenthesis === 0) {
throw new \InvalidArgumentException('ERR_UNOPENEDCOMMENT');
} else {
$openedParenthesis--;
}
}
$this->checkConsecutiveDots();
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$this->lexer->isNextToken(EmailLexer::S_AT)
) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
$this->warnEscaping();
$this->isInvalidToken($this->lexer->token, $closingQuote);
if ($this->isFWS()) {
$this->parseFWS();
}
$this->lexer->moveNext();
}
$prev = $this->lexer->getPrevious();
if (strlen($prev['value']) > EmailValidator::RFC5322_LOCAL_TOOLONG) {
$this->warnings[] = EmailValidator::RFC5322_LOCAL_TOOLONG;
}
}
protected function parseDoubleQuote()
{
$parseAgain = true;
$special = array(
EmailLexer::S_CR => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_LF => true
);
$invalid = array(
EmailLexer::C_NUL => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_CR => true,
EmailLexer::S_LF => true
);
$setSpecialsWarning = true;
$this->lexer->moveNext();
while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && $this->lexer->token) {
$parseAgain = false;
if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) {
$this->warnings[] = EmailValidator::CFWS_FWS;
$setSpecialsWarning = false;
}
$this->lexer->moveNext();
if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
throw new \InvalidArgumentException('ERR_EXPECTED_ATEXT');
}
}
$prev = $this->lexer->getPrevious();
if ($prev['type'] === EmailLexer::S_BACKSLASH) {
if (!$this->checkDQUOTE(false)) {
throw new \InvalidArgumentException('ERR_UNCLOSED_DQUOTE');
}
}
if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) {
throw new \InvalidArgumentException('ERR_EXPECED_AT');
}
return $parseAgain;
}
protected function isInvalidToken($token, $closingQuote)
{
$forbidden = array(
EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON,
EmailLexer::INVALID
);
if (in_array($token['type'], $forbidden) && !$closingQuote) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
}

View file

@ -0,0 +1,197 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
abstract class Parser
{
protected $warnings = array();
protected $lexer;
protected $openedParenthesis = 0;
public function __construct(EmailLexer $lexer)
{
$this->lexer = $lexer;
}
public function getWarnings()
{
return $this->warnings;
}
abstract public function parse($str);
/** @return int */
public function getOpenedParenthesis()
{
return $this->openedParenthesis;
}
/**
* validateQuotedPair
*/
protected function validateQuotedPair()
{
if (!($this->lexer->token['type'] === EmailLexer::INVALID
|| $this->lexer->token['type'] === EmailLexer::C_DEL)) {
throw new \InvalidArgumentException('ERR_EXPECTING_QPAIR');
}
$this->warnings[] = EmailValidator::DEPREC_QP;
}
protected function parseComments()
{
$this->openedParenthesis = 1;
$this->isUnclosedComment();
$this->warnings[] = EmailValidator::CFWS_COMMENT;
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) {
$this->openedParenthesis++;
}
$this->warnEscaping();
$this->lexer->moveNext();
}
$this->lexer->moveNext();
if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->isNextToken(EmailLexer::S_AT)) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
}
}
protected function isUnclosedComment()
{
try {
$this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
return true;
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDCOMMENT');
}
}
protected function parseFWS()
{
$previous = $this->lexer->getPrevious();
$this->checkCRLFInFWS();
if ($this->lexer->token['type'] === EmailLexer::S_CR) {
throw new \InvalidArgumentException('ERR_CR_NO_LF');
}
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_ATEXT_AFTER_CFWS');
}
if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_CTEXT');
}
if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
} else {
$this->warnings[] = EmailValidator::CFWS_FWS;
}
}
protected function checkConsecutiveDots()
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEDOTS');
}
}
protected function isFWS()
{
if ($this->escaped()) {
return false;
}
if ($this->lexer->token['type'] === EmailLexer::S_SP ||
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
$this->lexer->token['type'] === EmailLexer::S_CR ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::CRLF
) {
return true;
}
return false;
}
protected function escaped()
{
$previous = $this->lexer->getPrevious();
if ($previous['type'] === EmailLexer::S_BACKSLASH
&&
$this->lexer->token['type'] !== EmailLexer::GENERIC
) {
return true;
}
return false;
}
protected function warnEscaping()
{
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
return false;
}
if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
return false;
}
$this->warnings[] = EmailValidator::DEPREC_QP;
return true;
}
protected function checkDQUOTE($hasClosingQuote)
{
if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) {
return $hasClosingQuote;
}
if ($hasClosingQuote) {
return $hasClosingQuote;
}
$previous = $this->lexer->getPrevious();
if ($previous['type'] === EmailLexer::GENERIC && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
$this->warnings[] = EmailValidator::RFC5321_QUOTEDSTRING;
try {
$this->lexer->find(EmailLexer::S_DQUOTE);
$hasClosingQuote = true;
} catch (\Exception $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDQUOTEDSTR');
}
return $hasClosingQuote;
}
protected function checkCRLFInFWS()
{
if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
return;
}
if ($this->lexer->isNextToken(EmailLexer::CRLF)) {
throw new \InvalidArgumentException('ERR_FWS_CRLF_X2');
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
throw new \InvalidArgumentException('ERR_FWS_CRLF_END');
}
}
}

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;

View file

@ -0,0 +1,33 @@
<?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;

View file

@ -0,0 +1,34 @@
<?php
use Egulias\EmailValidator\EmailValidator;
require __DIR__ . '/../../bootstrap.php';
require __DIR__ . '/../../../../isemail/is_email.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 = is_email($testingMail);
}
$b = microtime(true);
echo ($b - $a) . ' seconds with is_email' . 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;

View file

@ -0,0 +1,161 @@
<?php
namespace Egulias\EmailValidator\Tests;
use Egulias\EmailValidator\EmailLexer;
class EmailLexerTests extends \PHPUnit_Framework_TestCase
{
public function testLexerExtendsLib()
{
$lexer = new EmailLexer();
$this->assertInstanceOf('Doctrine\Common\Lexer\AbstractLexer', $lexer);
}
/**
* @dataProvider getTokens
*
*/
public function testLexerTokens($str, $token)
{
$lexer = new EmailLexer();
$lexer->setInput($str);
$lexer->moveNext();
$lexer->moveNext();
$this->assertEquals($token, $lexer->token['type']);
}
public function testLexerParsesMultipleSpaces()
{
$lexer = new EmailLexer();
$lexer->setInput(' ');
$lexer->moveNext();
$lexer->moveNext();
$this->assertEquals(EmailLexer::S_SP, $lexer->token['type']);
$lexer->moveNext();
$this->assertEquals(EmailLexer::S_SP, $lexer->token['type']);
}
/**
* @dataProvider invalidUTF8CharsProvider
*/
public function testLexerParsesInvalidUTF8($char)
{
$lexer = new EmailLexer();
$lexer->setInput($char);
$lexer->moveNext();
$lexer->moveNext();
$this->assertEquals(EmailLexer::INVALID, $lexer->token['type']);
}
public function invalidUTF8CharsProvider()
{
$chars = array();
for ($i = 0; $i < 0x100; ++$i) {
$c = $this->utf8Chr($i);
if (preg_match('/(?=\p{Cc})(?=[^\t\n\n\r])/u', $c) && !preg_match('/\x{0000}/u', $c)) {
$chars[] = array($c);
}
}
return $chars;
}
protected function utf8Chr($code_point)
{
if ($code_point < 0 || 0x10FFFF < $code_point || (0xD800 <= $code_point && $code_point <= 0xDFFF)) {
return '';
}
if ($code_point < 0x80) {
$hex[0] = $code_point;
$ret = chr($hex[0]);
} elseif ($code_point < 0x800) {
$hex[0] = 0x1C0 | $code_point >> 6;
$hex[1] = 0x80 | $code_point & 0x3F;
$ret = chr($hex[0]).chr($hex[1]);
} elseif ($code_point < 0x10000) {
$hex[0] = 0xE0 | $code_point >> 12;
$hex[1] = 0x80 | $code_point >> 6 & 0x3F;
$hex[2] = 0x80 | $code_point & 0x3F;
$ret = chr($hex[0]).chr($hex[1]).chr($hex[2]);
} else {
$hex[0] = 0xF0 | $code_point >> 18;
$hex[1] = 0x80 | $code_point >> 12 & 0x3F;
$hex[2] = 0x80 | $code_point >> 6 & 0x3F;
$hex[3] = 0x80 | $code_point & 0x3F;
$ret = chr($hex[0]).chr($hex[1]).chr($hex[2]).chr($hex[3]);
}
return $ret;
}
public function testLexerForTab()
{
$lexer = new EmailLexer();
$lexer->setInput("foo\tbar");
$lexer->moveNext();
$lexer->skipUntil(EmailLexer::S_HTAB);
$lexer->moveNext();
$this->assertEquals(EmailLexer::S_HTAB, $lexer->token['type']);
}
public function testLexerForUTF8()
{
$lexer = new EmailLexer();
$lexer->setInput("áÇ@bar.com");
$lexer->moveNext();
$lexer->moveNext();
$this->assertEquals(EmailLexer::GENERIC, $lexer->token['type']);
$lexer->moveNext();
$this->assertEquals(EmailLexer::GENERIC, $lexer->token['type']);
}
public function testLexerSearchToken()
{
$lexer = new EmailLexer();
$lexer->setInput("foo\tbar");
$lexer->moveNext();
$this->assertTrue($lexer->find(EmailLexer::S_HTAB));
}
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_SLASH),
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),
array(chr(226), EmailLexer::GENERIC),
array(chr(0), EmailLexer::C_NUL)
);
}
}

View file

@ -0,0 +1,385 @@
<?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 testInvalidUTF8Email()
{
$validator = new EmailValidator;
$email = "\x80\x81\x82@\x83\x84\x85.\x86\x87\x88";
$this->assertFalse($validator->isValid($email));
}
public function getValidEmails()
{
return array(
array('â@iana.org'),
array('fabien@symfony.com'),
array('example@example.co.uk'),
array('fabien_potencier@example.fr'),
array('example@localhost'),
array('fab\'ien@symfony.com'),
array('fab\ ien@symfony.com'),
array('example((example))@fakedfake.co.uk'),
array('example@faked(fake).co.uk'),
array('fabien+@symfony.com'),
array('инфо@письмо.рф'),
array('"username"@example.com'),
array('"user,name"@example.com'),
array('"user name"@example.com'),
array('"user@name"@example.com'),
array('"\a"@iana.org'),
array('"test\ test"@iana.org'),
array('""@iana.org'),
array('"\""@iana.org'),
array('müller@möller.de'),
array('test@email*'),
array('test@email!'),
array('test@email&'),
array('test@email^'),
array('test@email%'),
array('test@email$'),
array('test@email.com.au'),
array('1500111@профи-инвест.рф'),
);
}
/**
* @dataProvider getInvalidEmails
*/
public function testInvalidEmails($email)
{
$this->assertFalse($this->validator->isValid($email));
}
public function getInvalidEmails()
{
return array(
array('test@example.com test'),
array('user name@example.com'),
array('user name@example.com'),
array('example.@example.co.uk'),
array('example@example@example.co.uk'),
array('(test_exampel@example.fr)'),
array('example(example)example@example.co.uk'),
array('.example@localhost'),
array('ex\ample@localhost'),
array('example@local\host'),
array('example@localhost\\'),
array('example@localhost.'),
array('user name@example.com'),
array('username@ example . com'),
array('example@(fake).com'),
array('example@(fake.com'),
array('username@example,com'),
array('usern,ame@example.com'),
array('user[na]me@example.com'),
array('"""@iana.org'),
array('"\"@iana.org'),
array('"test"test@iana.org'),
array('"test""test"@iana.org'),
array('"test"."test"@iana.org'),
array('"test".test@iana.org'),
array('"test"' . chr(0) . '@iana.org'),
array('"test\"@iana.org'),
array(chr(226) . '@iana.org'),
array('test@' . chr(226) . '.org'),
array('\r\ntest@iana.org'),
array('\r\n test@iana.org'),
array('\r\n \r\ntest@iana.org'),
array('\r\n \r\ntest@iana.org'),
array('\r\n \r\n test@iana.org'),
array('test@iana.org \r\n'),
array('test@iana.org \r\n '),
array('test@iana.org \r\n \r\n'),
array('test@iana.org \r\n\r\n'),
array('test@iana.org \r\n\r\n '),
array('test@iana/icann.org'),
array('test@foo;bar.com'),
array('test;123@foobar.com'),
array('test@example..com'),
array('email.email@email."'),
array('test@email>'),
array('test@email<'),
array('test@email{'),
array('test@email.com]'),
array('test@ema[il.com'),
);
}
/**
* @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_UNOPENEDCOMMENT, 'comment)example@localhost'),
array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example(comment))@localhost'),
array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example@comment)localhost'),
array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example@localhost(comment))'),
array(EmailValidator::ERR_UNOPENEDCOMMENT, 'example@(comment))example.com'),
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"),
);
}
/**
* @dataProvider getInvalidEmailsWithWarnings
*/
public function testValidEmailsWithWarningsCheck($warnings, $email)
{
$this->assertFalse($this->validator->isValid($email, true));
$this->assertEquals($warnings, $this->validator->getWarnings());
}
/**
* @dataProvider getInvalidEmailsWithWarnings
*/
public function testInvalidEmailsWithDnsCheckAndStrictMode($warnings, $email)
{
$this->assertFalse($this->validator->isValid($email, true, true));
$this->assertEquals($warnings, $this->validator->getWarnings());
}
public function getInvalidEmailsWithWarnings()
{
return array(
array(
array(
EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD
),
'example @invalid.example.com'
),
array(
array(
EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD
),
'example@ invalid.example.com'
),
array(
array(
EmailValidator::CFWS_COMMENT,
EmailValidator::DNSWARN_NO_RECORD
),
'example@invalid.example(examplecomment).com'
),
array(
array(
EmailValidator::CFWS_COMMENT,
EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD,
),
'example(examplecomment)@invalid.example.com'
),
array(
array(
EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::CFWS_FWS,
EmailValidator::DNSWARN_NO_RECORD,
),
"\"\t\"@invalid.example.com"
),
array(
array(
EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::CFWS_FWS,
EmailValidator::DNSWARN_NO_RECORD
),
"\"\r\"@invalid.example.com"
),
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::RFC5321_ADDRESSLITERAL,
EmailValidator::RFC5322_IPV6_COLONEND,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:]'
),
array(
array(
EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::DNSWARN_NO_RECORD
),
'"example"@invalid.example.com'
),
array(
array(
EmailValidator::RFC5322_LOCAL_TOOLONG,
EmailValidator::DNSWARN_NO_RECORD
),
'too_long_localpart_too_long_localpart_too_long_localpart_too_long_localpart@invalid.example.com'
),
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'
),
array(
array(
EmailValidator::DNSWARN_NO_RECORD,
),
'test@test'
),
);
}
public function testInvalidEmailsWithStrict()
{
$this->assertFalse($this->validator->isValid('"test"@test', false, true));
}
}