Merge branch 'master' into 60-custom-exceptions

Conflicts:
	src/Egulias/EmailValidator/Parser/DomainPart.php
	src/Egulias/EmailValidator/Parser/LocalPart.php
	src/Egulias/EmailValidator/Parser/Parser.php
	tests/egulias/Tests/EmailValidator/EmailValidatorTest.php
This commit is contained in:
Eduardo Gulias Davis
2015-10-12 11:53:11 +02:00
15 changed files with 986 additions and 23 deletions
+1 -3
View File
@@ -3,8 +3,6 @@ sudo: false
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
@@ -17,7 +15,7 @@ env:
matrix:
fast_finish: true
include:
- php: 5.3
- php: 5.5
env: deps=low
- php: 5.6
env: deps=high
+1 -1
View File
@@ -14,7 +14,7 @@
}
},
"require": {
"php": ">= 5.3.3",
"php": ">= 5.5",
"doctrine/lexer": "~1.0,>=1.0.1"
},
"require-dev" : {
Generated
+3 -3
View File
@@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "9426f5a00cf0f71d83c287a7c0ff392b",
"hash": "eaf5eb6f0b3811e996fb205760c9dd3f",
"packages": [
{
"name": "doctrine/lexer",
@@ -1492,7 +1492,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">= 5.3.3"
"php": ">= 5.5"
},
"platform-dev": []
}
Binary file not shown.
+102
View File
@@ -0,0 +1,102 @@
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 = [];
protected domainPart = "";
protected localPart = "";
protected lexer;
protected localPartParser;
protected domainPartParser;
public function __construct(<EmailLexer> lexer) -> void
{
let this->lexer = lexer;
let this->localPartParser = new LocalPart(this->lexer);
let this->domainPartParser = new DomainPart(this->lexer);
}
/**
* @param $str
* @return array
*/
public function parse(str) -> array
{
var tmpArray384eacd3233973d48b46f6665fe87aa5;
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");
}
let tmpArray384eacd3233973d48b46f6665fe87aa5 = ["local" : this->localPart, "domain" : this->domainPart];
return tmpArray384eacd3233973d48b46f6665fe87aa5;
}
public function getWarnings()
{
var localPartWarnings, domainPartWarnings;
let localPartWarnings = this->localPartParser->getWarnings();
let domainPartWarnings = this->domainPartParser->getWarnings();
let 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) -> void
{
var parts;
let parts = explode("@", email);
let this->domainPart = this->domainPartParser->getDomainPart();
let 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(string localPart, string parsedDomainPart) -> void
{
if strlen(localPart . "@" . parsedDomainPart) > self::EMAIL_MAX_LENGTH {
let this->warnings[] = EmailValidator::RFC5322_TOOLONG;
}
}
}
+172
View File
@@ -0,0 +1,172 @@
namespace Egulias\EmailValidator;
/**
* EmailValidator
*
* @author Eduardo Gulias Davis <me@egulias.com>
*/
class EmailValidator
{
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 = [];
protected error;
protected threshold = 255;
public function __construct() -> void
{
let this->parser = new EmailParser(new EmailLexer());
}
public function isValid(email, checkDNS = false, strict = false)
{
var e, rClass, dns, error;
try {
this->parser->parse((string) email);
let this->warnings = this->parser->getWarnings();
} catch \Exception, e {
let rClass = new \ReflectionClass(this);
let this->error = rClass->getConstant(e->getMessage());
return false;
}
let dns = true;
if checkDNS {
let dns = this->checkDNS();
}
if this->hasWarnings() && (int) max(this->warnings) > this->threshold {
let this->error = self::ERR_DEPREC_REACHED;
return false;
}
return !strict || !this->hasWarnings() && dns;
}
/**
* @return boolean
*/
public function hasWarnings() -> boolean
{
return !empty(this->warnings);
}
/**
* @return array
*/
public function getWarnings() -> array
{
return this->warnings;
}
/**
* @return string
*/
public function getError() -> string
{
return this->error;
}
/**
* @param int $threshold
*
* @return EmailValidator
*/
public function setThreshold(int threshold)
{
let this->threshold = (int) threshold;
return this;
}
/**
* @return int
*/
public function getThreshold() -> int
{
return this->threshold;
}
protected function checkDNS()
{
var checked, result;
let checked = true;
let result = checkdnsrr(trim(this->parser->getParsedDomainPart()), "MX");
if !result {
let this->warnings[] = self::DNSWARN_NO_RECORD;
let checked = false;
this->addTLDWarnings();
}
return checked;
}
protected function addTLDWarnings() -> void
{
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) {
let this->warnings[] = self::RFC5321_TLD;
}
}
}
@@ -0,0 +1,326 @@
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Parser\Parser;
use Egulias\EmailValidator\EmailValidator;
class DomainPart extends Parser
{
const DOMAIN_MAX_LENGTH = 254;
protected domainPart = "";
public function parse(domainPart) -> void
{
var domain, prev, length;
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 {
let this->warnings[] = EmailValidator::DEPREC_COMMENT;
this->parseDomainComments();
}
let domain = this->doParseDomainPart();
let prev = this->lexer->getPrevious();
let 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 {
let this->warnings[] = EmailValidator::RFC5322_DOMAIN_TOOLONG;
}
if prev["type"] === EmailLexer::S_CR {
throw new \InvalidArgumentException("ERR_FWS_CRLF_END");
}
let this->domainPart = domain;
}
public function getDomainPart()
{
return this->domainPart;
}
public function checkIPV6Tag(addressLiteral, maxGroups = 8)
{
var prev, IPv6, matchesIP, groupCount, colons;
let prev = this->lexer->getPrevious();
if prev["type"] === EmailLexer::S_COLON {
let this->warnings[] = EmailValidator::RFC5322_IPV6_COLONEND;
}
let IPv6 = substr(addressLiteral, 5);
//Daniel Marschall's new IPv6 testing strategy
let matchesIP = explode(":", IPv6);
let groupCount = count(matchesIP);
let colons = strpos(IPv6, "::");
if count(preg_grep("/^[0-9A-Fa-f]{0,4}$/", matchesIP, PREG_GREP_INVERT)) !== 0 {
let this->warnings[] = EmailValidator::RFC5322_IPV6_BADCHAR;
}
if colons === false {
// We need exactly the right number of groups
if groupCount !== maxGroups {
let this->warnings[] = EmailValidator::RFC5322_IPV6_GRPCOUNT;
}
return;
}
if colons !== strrpos(IPv6, "::") {
let 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
let maxGroups++;
}
if groupCount > maxGroups {
let this->warnings[] = EmailValidator::RFC5322_IPV6_MAXGRPS;
} elseif groupCount === maxGroups {
let this->warnings[] = EmailValidator::RFC5321_IPV6DEPRECATED;
}
}
protected function doParseDomainPart()
{
var domain, prev;
let domain = "";
do {
let prev = this->lexer->getPrevious();
if this->lexer->token["type"] === EmailLexer::S_SLASH {
throw new \InvalidArgumentException("ERR_DOMAIN_CHAR_NOT_ALLOWED");
}
if this->lexer->token["type"] === EmailLexer::S_OPENPARENTHESIS {
this->parseComments();
this->lexer->moveNext();
}
this->checkConsecutiveDots();
this->checkDomainPartExceptions(prev);
if this->hasBrackets() {
this->parseDomainLiteral();
}
this->checkLabelLength(prev);
if this->isFWS() {
this->parseFWS();
}
let domain .= this->lexer->token["value"];
this->lexer->moveNext();
} while (this->lexer->token);
return domain;
}
protected function parseDomainLiteral()
{
var lexer;
if this->lexer->isNextToken(EmailLexer::S_COLON) {
let this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
if this->lexer->isNextToken(EmailLexer::S_IPV6TAG) {
let lexer = clone this->lexer;
lexer->moveNext();
if lexer->isNextToken(EmailLexer::S_DOUBLECOLON) {
let this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
}
return this->doParseDomainLiteral();
}
protected function doParseDomainLiteral()
{
var IPv6TAG, addressLiteral, tmpArray660bd629014c38ffb14ea5f4192457b6, tmpArray9934a9b75899ddf8df794ea12f1b354a;
let IPv6TAG = false;
let 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 {
let this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
}
let tmpArray660bd629014c38ffb14ea5f4192457b6 = [EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET];
if this->lexer->isNextTokenAny(tmpArray660bd629014c38ffb14ea5f4192457b6) {
throw new \InvalidArgumentException("ERR_EXPECTING_DTEXT");
}
let tmpArray9934a9b75899ddf8df794ea12f1b354a = [EmailLexer::S_HTAB, EmailLexer::S_SP, this->lexer->token["type"] === EmailLexer::CRLF];
if this->lexer->isNextTokenAny(tmpArray9934a9b75899ddf8df794ea12f1b354a) {
let 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 {
let this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
let addressLiteral .= this->lexer->token["value"];
this->lexer->moveNext();
this->validateQuotedPair();
}
if this->lexer->token["type"] === EmailLexer::S_IPV6TAG {
let IPv6TAG = true;
}
if this->lexer->token["type"] === EmailLexer::S_CLOSEQBRACKET {
break;
}
let addressLiteral .= this->lexer->token["value"];
} while (this->lexer->moveNext());
let addressLiteral = str_replace("[", "", addressLiteral);
let addressLiteral = this->checkIPV4Tag(addressLiteral);
if addressLiteral === false {
return addressLiteral;
}
if !IPv6TAG {
let this->warnings[] = EmailValidator::RFC5322_DOMAINLITERAL;
return addressLiteral;
}
let this->warnings[] = EmailValidator::RFC5321_ADDRESSLITERAL;
this->checkIPV6Tag(addressLiteral);
return addressLiteral;
}
protected function checkIPV4Tag(addressLiteral)
{
var matchesIP, index;
let matchesIP = [];
// 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 {
let index = strrpos(addressLiteral, matchesIP[0]);
if index === 0 {
let this->warnings[] = EmailValidator::RFC5321_ADDRESSLITERAL;
return false;
}
// Convert IPv4 part to IPv6 format for further testing
let addressLiteral = substr(addressLiteral, 0, index) . "0:0";
}
return addressLiteral;
}
protected function checkDomainPartExceptions(prev) -> void
{
var invalidDomainTokens;
let invalidDomainTokens = [EmailLexer::S_DQUOTE : true, EmailLexer::S_SEMICOLON : true, EmailLexer::S_GREATERTHAN : true, EmailLexer::S_LOWERTHAN : true];
if isset var tmpArray;
this->lexer->token["type"];
invalidDomainTokens[this->lexer->token] {
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()
{
var e;
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) -> void
{
if this->lexer->token["type"] === EmailLexer::S_DOT && prev["type"] === EmailLexer::GENERIC && strlen(prev["value"]) > 63 {
let this->warnings[] = EmailValidator::RFC5322_LABEL_TOOLONG;
}
}
protected function parseDomainComments() -> void
{
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");
}
}
}
+108
View File
@@ -0,0 +1,108 @@
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
use InvalidArgumentException;
class LocalPart extends Parser
{
public function parse(localPart) -> void
{
var parseDQuote, closingQuote, prev;
let parseDQuote = true;
let closingQuote = false;
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");
}
let closingQuote = this->checkDQUOTE(closingQuote);
if closingQuote && parseDQuote {
let parseDQuote = this->parseDoubleQuote();
}
if this->lexer->token["type"] === EmailLexer::S_OPENPARENTHESIS {
this->parseComments();
}
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();
}
let prev = this->lexer->getPrevious();
if strlen(prev["value"]) > EmailValidator::RFC5322_LOCAL_TOOLONG {
let this->warnings[] = EmailValidator::RFC5322_LOCAL_TOOLONG;
}
}
protected function parseDoubleQuote()
{
var parseAgain, special, invalid, setSpecialsWarning, prev;
let parseAgain = true;
let special = [EmailLexer::S_CR : true, EmailLexer::S_HTAB : true, EmailLexer::S_LF : true];
let invalid = [EmailLexer::C_NUL : true, EmailLexer::S_HTAB : true, EmailLexer::S_CR : true, EmailLexer::S_LF : true];
let setSpecialsWarning = true;
this->lexer->moveNext();
while (this->lexer->token["type"] !== EmailLexer::S_DQUOTE && this->lexer->token) {
let parseAgain = false;
if isset var tmpArray;
this->lexer->token["type"];
special[this->lexer->token] && setSpecialsWarning {
let this->warnings[] = EmailValidator::CFWS_FWS;
let setSpecialsWarning = false;
}
this->lexer->moveNext();
if !this->escaped() && isset var tmpArray;
this->lexer->token["type"];
invalid[this->lexer->token] {
throw new InvalidArgumentException("ERR_EXPECTED_ATEXT");
}
}
let 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) -> void
{
var forbidden;
let forbidden = [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");
}
}
}
+210
View File
@@ -0,0 +1,210 @@
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
abstract class Parser
{
protected warnings = [];
protected lexer;
public function __construct(<EmailLexer> lexer) -> void
{
let this->lexer = lexer;
}
public function getWarnings()
{
return this->warnings;
}
abstract function parse(str) -> void;
/**
* validateQuotedPair
*/
protected function validateQuotedPair() -> void
{
if !(this->lexer->token["type"] === EmailLexer::INVALID || this->lexer->token["type"] === EmailLexer::C_DEL) {
throw new \InvalidArgumentException("ERR_EXPECTING_QPAIR");
}
let this->warnings[] = EmailValidator::DEPREC_QP;
}
/**
* @return string the the comment
* @throws \InvalidArgumentException
*/
protected function parseComments() -> string
{
var tmpArray63f0c3c6be7adf1793a82cc46795d34e;
this->isUnclosedComment();
let this->warnings[] = EmailValidator::CFWS_COMMENT;
while (!this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
this->warnEscaping();
this->lexer->moveNext();
}
this->lexer->moveNext();
let tmpArray63f0c3c6be7adf1793a82cc46795d34e = [EmailLexer::GENERIC, EmailLexer::S_EMPTY];
if this->lexer->isNextTokenAny(tmpArray63f0c3c6be7adf1793a82cc46795d34e) {
throw new \InvalidArgumentException("ERR_EXPECTING_ATEXT");
}
if this->lexer->isNextToken(EmailLexer::S_AT) {
let this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
}
}
protected function isUnclosedComment()
{
var e;
try {
this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
return true;
} catch \RuntimeException, e {
throw new \InvalidArgumentException("ERR_UNCLOSEDCOMMENT");
}
}
protected function parseFWS() -> void
{
var previous;
let 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 {
let this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
} else {
let this->warnings[] = EmailValidator::CFWS_FWS;
}
}
protected function checkConsecutiveDots() -> void
{
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()
{
var previous;
let previous = this->lexer->getPrevious();
if previous["type"] === EmailLexer::S_BACKSLASH && this->lexer->token["type"] !== EmailLexer::GENERIC {
return true;
}
return false;
}
protected function warnEscaping()
{
var tmpArray612d23a3c73245450bf1b63fe4fddaa8;
if this->lexer->token["type"] !== EmailLexer::S_BACKSLASH {
return false;
}
if this->lexer->isNextToken(EmailLexer::GENERIC) {
throw new \InvalidArgumentException("ERR_EXPECTING_ATEXT");
}
let tmpArray612d23a3c73245450bf1b63fe4fddaa8 = [EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL];
if !this->lexer->isNextTokenAny(tmpArray612d23a3c73245450bf1b63fe4fddaa8) {
return false;
}
let this->warnings[] = EmailValidator::DEPREC_QP;
return true;
}
protected function checkDQUOTE(hasClosingQuote)
{
var previous, e;
if this->lexer->token["type"] !== EmailLexer::S_DQUOTE {
return hasClosingQuote;
}
if hasClosingQuote {
return hasClosingQuote;
}
let previous = this->lexer->getPrevious();
if this->lexer->isNextToken(EmailLexer::GENERIC) && previous["type"] === EmailLexer::GENERIC {
throw new \InvalidArgumentException("ERR_EXPECTING_ATEXT");
}
let this->warnings[] = EmailValidator::RFC5321_QUOTEDSTRING;
try {
this->lexer->find(EmailLexer::S_DQUOTE);
let hasClosingQuote = true;
} catch \Exception, e {
throw new \InvalidArgumentException("ERR_UNCLOSEDQUOTEDSTR");
}
return hasClosingQuote;
}
protected function checkCRLFInFWS()
{
var tmpArray1ece19b1ca3512633175a37153944cbe;
if this->lexer->token["type"] !== EmailLexer::CRLF {
return;
}
if this->lexer->isNextToken(EmailLexer::CRLF) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_X2");
}
let tmpArray1ece19b1ca3512633175a37153944cbe = [EmailLexer::S_SP, EmailLexer::S_HTAB];
if !this->lexer->isNextTokenAny(tmpArray1ece19b1ca3512633175a37153944cbe) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_END");
}
}
}
@@ -12,6 +12,7 @@ use Egulias\EmailValidator\InvalidEmail;
class EmailValidator
{
const ERR_DEPREC_REACHED = 151;
const ERR_UNOPENEDCOMMENT = 152;
const RFC5321_TLD = 9;
const RFC5321_TLDNUMERIC = 10;
const RFC5321_QUOTEDSTRING = 11;
@@ -0,0 +1,11 @@
<?php
namespace Egulias\EmailValidator\Exception;
use Egulias\EmailValidator\InvalidEmail;
class UnopenedComment extends InvalidEmail
{
const CODE = 152;
const REASON = "No opening comment token found";
}
@@ -10,12 +10,11 @@ use Egulias\EmailValidator\Exception\CRNoLF;
use Egulias\EmailValidator\Exception\DomainHyphened;
use Egulias\EmailValidator\Exception\DotAtEnd;
use Egulias\EmailValidator\Exception\DotAtStart;
use Egulias\EmailValidator\Exception\ExpectedQPair;
use Egulias\EmailValidator\Exception\ExpectingATEXT;
use Egulias\EmailValidator\Exception\ExpectingDTEXT;
use Egulias\EmailValidator\Exception\NoDomainPart;
use Egulias\EmailValidator\Parser\Parser;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Exception\UnopenedComment;
class DomainPart extends Parser
{
@@ -113,8 +112,8 @@ class DomainPart extends Parser
protected function doParseDomainPart()
{
$domain = '';
$openedParenthesis = 0;
do {
$prev = $this->lexer->getPrevious();
if ($this->lexer->token['type'] === EmailLexer::S_SLASH) {
@@ -123,7 +122,19 @@ class DomainPart extends Parser
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 UnopenedComment();
} else {
$openedParenthesis--;
}
}
$this->checkConsecutiveDots();
@@ -192,6 +203,7 @@ class DomainPart extends Parser
if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
throw new CRNoLF();
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
$addressLiteral .= $this->lexer->token['value'];
@@ -7,7 +7,7 @@ use Egulias\EmailValidator\Exception\DotAtStart;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Exception\ExpectingATEXT;
use \InvalidArgumentException;
use Egulias\EmailValidator\Exception\UnopenedComment;
class LocalPart extends Parser
{
@@ -15,9 +15,9 @@ class LocalPart extends Parser
{
$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 DotAtStart();
}
@@ -29,12 +29,19 @@ class LocalPart extends Parser
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 UnopenedComment();
} else {
$openedParenthesis--;
}
}
$this->checkConsecutiveDots();
if (
$this->lexer->token['type'] === EmailLexer::S_DOT &&
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$this->lexer->isNextToken(EmailLexer::S_AT)
) {
throw new DotAtEnd();
@@ -85,7 +92,7 @@ class LocalPart extends Parser
$this->lexer->moveNext();
if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
throw new InvalidArgumentException("ERR_EXPECTED_ATEXT");
throw new \InvalidArgumentException('ERR_EXPECTED_ATEXT');
}
}
@@ -93,12 +100,12 @@ class LocalPart extends Parser
if ($prev['type'] === EmailLexer::S_BACKSLASH) {
if (!$this->checkDQUOTE(false)) {
throw new \InvalidArgumentException("ERR_UNCLOSED_DQUOTE");
throw new \InvalidArgumentException('ERR_UNCLOSED_DQUOTE');
}
}
if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) {
throw new \InvalidArgumentException("ERR_EXPECED_AT");
throw new \InvalidArgumentException('ERR_EXPECED_AT');
}
return $parseAgain;
+15 -6
View File
@@ -19,6 +19,7 @@ abstract class Parser
{
protected $warnings = array();
protected $lexer;
protected $openedParenthesis = 0;
public function __construct(EmailLexer $lexer)
{
@@ -30,7 +31,13 @@ abstract class Parser
return $this->warnings;
}
abstract function parse($str);
abstract public function parse($str);
/** @return int */
public function getOpenedParenthesis()
{
return $this->openedParenthesis;
}
/**
* validateQuotedPair
@@ -45,15 +52,15 @@ abstract class Parser
$this->warnings[] = EmailValidator::DEPREC_QP;
}
/**
* @return string the the comment
* @throws \InvalidArgumentException
*/
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();
}
@@ -190,9 +197,11 @@ abstract class Parser
if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
return;
}
if ($this->lexer->isNextToken(EmailLexer::CRLF)) {
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
throw new CRLFX2();
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
throw new CRLFAtTheEnd();
}
@@ -16,6 +16,7 @@ use Egulias\EmailValidator\Exception\NoDomainPart;
use Egulias\EmailValidator\Exception\NoLocalPart;
use Egulias\EmailValidator\Exception\UnclosedComment;
use Egulias\EmailValidator\Exception\UnclosedQuotedString;
use Egulias\EmailValidator\Exception\UnopenedComment;
class EmailValidatorTest extends \PHPUnit_Framework_TestCase
{
@@ -167,6 +168,12 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array(UnclosedComment::CODE, '(example@localhost'),
array(UnclosedQuotedString::CODE, '"example@localhost'),
array(ExpectingATEXT::CODE, 'exa"mple@localhost'),
array(UnclosedComment::CODE, '(example@localhost'),
array(UnopenedComment::CODE, 'comment)example@localhost'),
array(UnopenedComment::CODE, 'example(comment))@localhost'),
array(UnopenedComment::CODE, 'example@comment)localhost'),
array(UnopenedComment::CODE, 'example@localhost(comment))'),
array(UnopenedComment::CODE, 'example@(comment))example.com'),
//This was the original. But atext is not allowed after \n
//array(EmailValidator::ERR_EXPECTING_ATEXT, "exampl\ne@example.co.uk"),
array(AtextAfterCFWS::CODE, "exampl\ne@example.co.uk"),