LICENSE000064400000002055151743410500005553 0ustar00Copyright (c) 2013-2016 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. composer.json000064400000001744151743410500007274 0ustar00{ "name": "egulias/email-validator", "description": "A library for validating emails against several RFCs", "homepage": "https://github.com/egulias/EmailValidator", "keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"], "license": "MIT", "authors": [ {"name": "Eduardo Gulias Davis"} ], "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, "require": { "php": ">=5.5", "doctrine/lexer": "^1.0.1", "symfony/polyfill-intl-idn": "^1.10" }, "require-dev": { "dominicsayers/isemail": "^3.0.7", "phpunit/phpunit": "^4.8.36|^7.5.15", "satooshi/php-coveralls": "^1.0.1" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" }, "autoload": { "psr-4": { "Egulias\\EmailValidator\\": "src" } }, "autoload-dev": { "psr-4": { "Egulias\\EmailValidator\\Tests\\": "tests" } } } src/EmailLexer.php000064400000014165151743410500010102 0ustar00 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, ); /** * @var bool */ protected $hasInvalidTokens = false; /** * @var array * * @psalm-var array{value:string, type:null|int, position:int}|array */ protected $previous = []; /** * The last matched/seen token. * * @var array * * @psalm-var array{value:string, type:null|int, position:int} */ public $token; /** * The next token in the input. * * @var array|null */ public $lookahead; /** * @psalm-var array{value:'', type:null, position:0} */ private static $nullToken = [ 'value' => '', 'type' => null, 'position' => 0, ]; public function __construct() { $this->previous = $this->token = self::$nullToken; $this->lookahead = null; } /** * @return void */ public function reset() { $this->hasInvalidTokens = false; parent::reset(); $this->previous = $this->token = self::$nullToken; } /** * @return bool */ public function hasInvalidTokens() { return $this->hasInvalidTokens; } /** * @param int $type * @throws \UnexpectedValueException * @return boolean * * @psalm-suppress InvalidScalarArgument */ public function find($type) { $search = clone $this; $search->skipUntil($type); if (!$search->lookahead) { throw new \UnexpectedValueException($type . ' not found'); } return true; } /** * getPrevious * * @return array */ public function getPrevious() { return $this->previous; } /** * moveNext * * @return boolean */ public function moveNext() { $this->previous = $this->token; $hasNext = parent::moveNext(); $this->token = $this->token ?: self::$nullToken; return $hasNext; } /** * 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; } /** * @param string $value * * @return bool */ protected function isValid($value) { if (isset($this->charValue[$value])) { return true; } return false; } /** * @param string $value * @return bool */ protected function isNullType($value) { if ($value === "\0") { return true; } return false; } /** * @param string $value * @return bool */ protected function isUTF8Invalid($value) { if (preg_match('/\p{Cc}+/u', $value)) { return true; } return false; } /** * @return string */ protected function getModifiers() { return 'iu'; } } src/EmailParser.php000064400000005652151743410500010260 0ustar00 */ class EmailParser { const EMAIL_MAX_LENGTH = 254; /** * @var array */ protected $warnings = []; /** * @var string */ protected $domainPart = ''; /** * @var string */ protected $localPart = ''; /** * @var EmailLexer */ protected $lexer; /** * @var LocalPart */ protected $localPartParser; /** * @var DomainPart */ protected $domainPartParser; public function __construct(EmailLexer $lexer) { $this->lexer = $lexer; $this->localPartParser = new LocalPart($this->lexer); $this->domainPartParser = new DomainPart($this->lexer); } /** * @param string $str * @return array */ public function parse($str) { $this->lexer->setInput($str); if (!$this->hasAtToken()) { throw new NoLocalPart(); } $this->localPartParser->parse($str); $this->domainPartParser->parse($str); $this->setParts($str); if ($this->lexer->hasInvalidTokens()) { throw new ExpectingATEXT(); } return array('local' => $this->localPart, 'domain' => $this->domainPart); } /** * @return Warning\Warning[] */ 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; } /** * @return string */ public function getParsedDomainPart() { return $this->domainPart; } /** * @param string $email */ protected function setParts($email) { $parts = explode('@', $email); $this->domainPart = $this->domainPartParser->getDomainPart(); $this->localPart = $parts[0]; } /** * @return bool */ 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[EmailTooLong::CODE] = new EmailTooLong(); } } } src/EmailValidator.php000064400000002330151743410500010737 0ustar00lexer = new EmailLexer(); } /** * @param string $email * @param EmailValidation $emailValidation * @return bool */ public function isValid($email, EmailValidation $emailValidation) { $isValid = $emailValidation->isValid($email, $this->lexer); $this->warnings = $emailValidation->getWarnings(); $this->error = $emailValidation->getError(); return $isValid; } /** * @return boolean */ public function hasWarnings() { return !empty($this->warnings); } /** * @return array */ public function getWarnings() { return $this->warnings; } /** * @return InvalidEmail|null */ public function getError() { return $this->error; } } src/Exception/AtextAfterCFWS.php000064400000000245151743410500012535 0ustar00lexer->moveNext(); $this->performDomainStartChecks(); $domain = $this->doParseDomainPart(); $prev = $this->lexer->getPrevious(); $length = strlen($domain); if ($prev['type'] === EmailLexer::S_DOT) { throw new DotAtEnd(); } if ($prev['type'] === EmailLexer::S_HYPHEN) { throw new DomainHyphened(); } if ($length > self::DOMAIN_MAX_LENGTH) { $this->warnings[DomainTooLong::CODE] = new DomainTooLong(); } if ($prev['type'] === EmailLexer::S_CR) { throw new CRLFAtTheEnd(); } $this->domainPart = $domain; } private function performDomainStartChecks() { $this->checkInvalidTokensAfterAT(); $this->checkEmptyDomain(); if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment(); $this->parseDomainComments(); } } private function checkEmptyDomain() { $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY || ($this->lexer->token['type'] === EmailLexer::S_SP && !$this->lexer->isNextToken(EmailLexer::GENERIC)); if ($thereIsNoDomain) { throw new NoDomainPart(); } } private function checkInvalidTokensAfterAT() { if ($this->lexer->token['type'] === EmailLexer::S_DOT) { throw new DotAtStart(); } if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) { throw new DomainHyphened(); } } /** * @return string */ public function getDomainPart() { return $this->domainPart; } /** * @param string $addressLiteral * @param int $maxGroups */ public function checkIPV6Tag($addressLiteral, $maxGroups = 8) { $prev = $this->lexer->getPrevious(); if ($prev['type'] === EmailLexer::S_COLON) { $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd(); } $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[IPV6BadChar::CODE] = new IPV6BadChar(); } if ($colons === false) { // We need exactly the right number of groups if ($groupCount !== $maxGroups) { $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount(); } return; } if ($colons !== strrpos($IPv6, '::')) { $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon(); 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[IPV6MaxGroups::CODE] = new IPV6MaxGroups(); } elseif ($groupCount === $maxGroups) { $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated(); } } /** * @return string */ protected function doParseDomainPart() { $domain = ''; $openedParenthesis = 0; 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 UnopenedComment(); } else { $openedParenthesis--; } } $this->checkConsecutiveDots(); $this->checkDomainPartExceptions($prev); if ($this->hasBrackets()) { $this->parseDomainLiteral(); } $this->checkLabelLength($prev); if ($this->isFWS()) { $this->parseFWS(); } $domain .= $this->lexer->token['value']; $this->lexer->moveNext(); } while (null !== $this->lexer->token['type']); return $domain; } private function checkNotAllowedChars(array $token) { $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true]; if (isset($notAllowed[$token['type']])) { throw new CharNotAllowed(); } } /** * @return string|false */ protected function parseDomainLiteral() { if ($this->lexer->isNextToken(EmailLexer::S_COLON)) { $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); } if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) { $lexer = clone $this->lexer; $lexer->moveNext(); if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) { $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); } } return $this->doParseDomainLiteral(); } /** * @return string|false */ protected function doParseDomainLiteral() { $IPv6TAG = false; $addressLiteral = ''; do { if ($this->lexer->token['type'] === EmailLexer::C_NUL) { throw new ExpectingDTEXT(); } if ($this->lexer->token['type'] === EmailLexer::INVALID || $this->lexer->token['type'] === EmailLexer::C_DEL || $this->lexer->token['type'] === EmailLexer::S_LF ) { $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); } if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) { throw new ExpectingDTEXT(); } if ($this->lexer->isNextTokenAny( array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF) )) { $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); $this->parseFWS(); } if ($this->lexer->isNextToken(EmailLexer::S_CR)) { throw new CRNoLF(); } if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) { $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); $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[DomainLiteral::CODE] = new DomainLiteral(); return $addressLiteral; } $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); $this->checkIPV6Tag($addressLiteral); return $addressLiteral; } /** * @param string $addressLiteral * * @return string|false */ 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[AddressLiteral::CODE] = new AddressLiteral(); return false; } // Convert IPv4 part to IPv6 format for further testing $addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0'; } return $addressLiteral; } protected function checkDomainPartExceptions(array $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 ExpectingATEXT(); } if ($this->lexer->token['type'] === EmailLexer::S_COMMA) { throw new CommaInDomain(); } if ($this->lexer->token['type'] === EmailLexer::S_AT) { throw new ConsecutiveAt(); } if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) { throw new ExpectingATEXT(); } if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) { throw new DomainHyphened(); } if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::GENERIC)) { throw new ExpectingATEXT(); } } /** * @return bool */ protected function hasBrackets() { if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) { return false; } try { $this->lexer->find(EmailLexer::S_CLOSEBRACKET); } catch (\RuntimeException $e) { throw new ExpectingDomainLiteralClose(); } return true; } protected function checkLabelLength(array $prev) { if ($this->lexer->token['type'] === EmailLexer::S_DOT && $prev['type'] === EmailLexer::GENERIC && strlen($prev['value']) > 63 ) { $this->warnings[LabelTooLong::CODE] = new LabelTooLong(); } } 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 ExpectingATEXT(); } } protected function addTLDWarnings() { if ($this->warnings[DomainLiteral::CODE]) { $this->warnings[TLD::CODE] = new TLD(); } } } src/Parser/LocalPart.php000064400000010731151743410500011163 0ustar00lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) { if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) { throw new DotAtStart(); } $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 UnopenedComment(); } $openedParenthesis--; } $this->checkConsecutiveDots(); if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_AT) ) { throw new DotAtEnd(); } $this->warnEscaping(); $this->isInvalidToken($this->lexer->token, $closingQuote); if ($this->isFWS()) { $this->parseFWS(); } $totalLength += strlen($this->lexer->token['value']); $this->lexer->moveNext(); } if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) { $this->warnings[LocalTooLong::CODE] = new LocalTooLong(); } } /** * @return bool */ 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 && null !== $this->lexer->token['type']) { $parseAgain = false; if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) { $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); $setSpecialsWarning = false; } if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) { $this->lexer->moveNext(); } $this->lexer->moveNext(); if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) { throw new ExpectingATEXT(); } } $prev = $this->lexer->getPrevious(); if ($prev['type'] === EmailLexer::S_BACKSLASH) { if (!$this->checkDQUOTE(false)) { throw new UnclosedQuotedString(); } } if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) { throw new ExpectingAT(); } return $parseAgain; } /** * @param bool $closingQuote */ protected function isInvalidToken(array $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 ExpectingATEXT(); } } } src/Parser/Parser.php000064400000015335151743410500010543 0ustar00lexer = $lexer; } /** * @return \Egulias\EmailValidator\Warning\Warning[] */ public function getWarnings() { return $this->warnings; } /** * @param string $str */ 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 ExpectingQPair(); } $this->warnings[QuotedPart::CODE] = new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); } protected function parseComments() { $this->openedParenthesis = 1; $this->isUnclosedComment(); $this->warnings[Comment::CODE] = new 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 ExpectingATEXT(); } if ($this->lexer->isNextToken(EmailLexer::S_AT)) { $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); } } /** * @return bool */ protected function isUnclosedComment() { try { $this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS); return true; } catch (\RuntimeException $e) { throw new UnclosedComment(); } } protected function parseFWS() { $previous = $this->lexer->getPrevious(); $this->checkCRLFInFWS(); if ($this->lexer->token['type'] === EmailLexer::S_CR) { throw new CRNoLF(); } if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) { throw new AtextAfterCFWS(); } if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) { throw new ExpectingCTEXT(); } if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) { $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); } else { $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); } } protected function checkConsecutiveDots() { if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) { throw new ConsecutiveDot(); } } /** * @return bool */ 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; } /** * @return bool */ protected function escaped() { $previous = $this->lexer->getPrevious(); if ($previous && $previous['type'] === EmailLexer::S_BACKSLASH && $this->lexer->token['type'] !== EmailLexer::GENERIC ) { return true; } return false; } /** * @return bool */ protected function warnEscaping() { if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { return false; } if ($this->lexer->isNextToken(EmailLexer::GENERIC)) { throw new ExpectingATEXT(); } if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) { return false; } $this->warnings[QuotedPart::CODE] = new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); return true; } /** * @param bool $hasClosingQuote * * @return bool */ protected function checkDQUOTE($hasClosingQuote) { if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) { return $hasClosingQuote; } if ($hasClosingQuote) { return $hasClosingQuote; } $previous = $this->lexer->getPrevious(); if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) { throw new ExpectingATEXT(); } try { $this->lexer->find(EmailLexer::S_DQUOTE); $hasClosingQuote = true; } catch (\Exception $e) { throw new UnclosedQuotedString(); } $this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']); return $hasClosingQuote; } protected function checkCRLFInFWS() { if ($this->lexer->token['type'] !== EmailLexer::CRLF) { return; } 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(); } } } src/Validation/DNSCheckValidation.php000064400000010426151743410500013536 0ustar00error = new LocalOrReservedDomain(); return false; } return $this->checkDns($host); } public function getError() { return $this->error; } public function getWarnings() { return $this->warnings; } /** * @param string $host * * @return bool */ protected function checkDns($host) { $variant = INTL_IDNA_VARIANT_UTS46; $host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.'; return $this->validateDnsRecords($host); } /** * Validate the DNS records for given host. * * @param string $host A set of DNS records in the format returned by dns_get_record. * * @return bool True on success. */ private function validateDnsRecords($host) { // Get all MX, A and AAAA DNS records for host // Using @ as workaround to fix https://bugs.php.net/bug.php?id=73149 $dnsRecords = @dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA); // No MX, A or AAAA DNS records if (empty($dnsRecords) || !$dnsRecords) { $this->error = new NoDNSRecord(); return false; } // For each DNS record foreach ($dnsRecords as $dnsRecord) { if (!$this->validateMXRecord($dnsRecord)) { return false; } } // No MX records (fallback to A or AAAA records) if (empty($this->mxRecords)) { $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord(); } return true; } /** * Validate an MX record * * @param array $dnsRecord Given DNS record. * * @return bool True if valid. */ private function validateMxRecord($dnsRecord) { if ($dnsRecord['type'] !== 'MX') { return true; } // "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505) if (empty($dnsRecord['target']) || $dnsRecord['target'] === '.') { $this->error = new DomainAcceptsNoMail(); return false; } $this->mxRecords[] = $dnsRecord; return true; } } src/Validation/EmailValidation.php000064400000001357151743410500013206 0ustar00errors = $errors; parent::__construct(); } /** * @return InvalidEmail[] */ public function getErrors() { return $this->errors; } } src/Validation/MultipleValidationWithAnd.php000064400000005611151743410500015226 0ustar00validations = $validations; $this->mode = $mode; } /** * {@inheritdoc} */ public function isValid($email, EmailLexer $emailLexer) { $result = true; $errors = []; foreach ($this->validations as $validation) { $emailLexer->reset(); $validationResult = $validation->isValid($email, $emailLexer); $result = $result && $validationResult; $this->warnings = array_merge($this->warnings, $validation->getWarnings()); $errors = $this->addNewError($validation->getError(), $errors); if ($this->shouldStop($result)) { break; } } if (!empty($errors)) { $this->error = new MultipleErrors($errors); } return $result; } /** * @param \Egulias\EmailValidator\Exception\InvalidEmail|null $possibleError * @param \Egulias\EmailValidator\Exception\InvalidEmail[] $errors * * @return \Egulias\EmailValidator\Exception\InvalidEmail[] */ private function addNewError($possibleError, array $errors) { if (null !== $possibleError) { $errors[] = $possibleError; } return $errors; } /** * @param bool $result * * @return bool */ private function shouldStop($result) { return !$result && $this->mode === self::STOP_ON_ERROR; } /** * Returns the validation errors. * * @return MultipleErrors|null */ public function getError() { return $this->error; } /** * {@inheritdoc} */ public function getWarnings() { return $this->warnings; } } src/Validation/NoRFCWarningsValidation.php000064400000001437151743410500014576 0ustar00getWarnings())) { return true; } $this->error = new RFCWarnings(); return false; } /** * {@inheritdoc} */ public function getError() { return $this->error ?: parent::getError(); } } src/Validation/RFCValidation.php000064400000001707151743410500012570 0ustar00parser = new EmailParser($emailLexer); try { $this->parser->parse((string)$email); } catch (InvalidEmail $invalid) { $this->error = $invalid; return false; } $this->warnings = $this->parser->getWarnings(); return true; } public function getError() { return $this->error; } public function getWarnings() { return $this->warnings; } } src/Validation/SpoofCheckValidation.php000064400000002113151743410500014172 0ustar00setChecks(Spoofchecker::SINGLE_SCRIPT); if ($checker->isSuspicious($email)) { $this->error = new SpoofEmail(); } return $this->error === null; } /** * @return InvalidEmail|null */ public function getError() { return $this->error; } public function getWarnings() { return []; } } src/Warning/AddressLiteral.php000064400000000373151743410500012356 0ustar00message = 'Address literal in domain part'; $this->rfcNumber = 5321; } } src/Warning/CFWSNearAt.php000064400000000335151743410500011307 0ustar00message = "Deprecated folding white space near @"; } } src/Warning/CFWSWithFWS.php000064400000000355151743410500011432 0ustar00message = 'Folding whites space followed by folding white space'; } } src/Warning/Comment.php000064400000000321151743410500011047 0ustar00message = "Comments found in this email"; } } src/Warning/DeprecatedComment.php000064400000000322151743410500013031 0ustar00message = 'Deprecated comments'; } } src/Warning/DomainLiteral.php000064400000000352151743410500012175 0ustar00message = 'Domain Literal'; $this->rfcNumber = 5322; } } src/Warning/DomainTooLong.php000064400000000402151743410500012156 0ustar00message = 'Domain is too long, exceeds 255 chars'; $this->rfcNumber = 5322; } } src/Warning/EmailTooLong.php000064400000000436151743410500012005 0ustar00message = 'Email is too long, exceeds ' . EmailParser::EMAIL_MAX_LENGTH; } } src/Warning/IPV6BadChar.php000064400000000371151743410500011403 0ustar00message = 'Bad char in IPV6 domain literal'; $this->rfcNumber = 5322; } } src/Warning/IPV6ColonEnd.php000064400000000404151743410500011615 0ustar00message = ':: found at the end of the domain literal'; $this->rfcNumber = 5322; } } src/Warning/IPV6ColonStart.php000064400000000410151743410500012201 0ustar00message = ':: found at the start of the domain literal'; $this->rfcNumber = 5322; } } src/Warning/IPV6Deprecated.php000064400000000364151743410500012161 0ustar00message = 'Deprecated form of IPV6'; $this->rfcNumber = 5321; } } src/Warning/IPV6DoubleColon.php000064400000000377151743410500012332 0ustar00message = 'Double colon found after IPV6 tag'; $this->rfcNumber = 5322; } } src/Warning/IPV6GroupCount.php000064400000000372151743410500012225 0ustar00message = 'Group count is not IPV6 valid'; $this->rfcNumber = 5322; } } src/Warning/IPV6MaxGroups.php000064400000000415151743410500012043 0ustar00message = 'Reached the maximum number of IPV6 groups allowed'; $this->rfcNumber = 5321; } } src/Warning/LabelTooLong.php000064400000000351151743410500011771 0ustar00message = 'Label too long'; $this->rfcNumber = 5322; } } src/Warning/LocalTooLong.php000064400000000456151743410500012012 0ustar00message = 'Local part is too long, exceeds 64 chars (octets)'; $this->rfcNumber = 5322; } } src/Warning/NoDNSMXRecord.php000064400000000404151743410500011774 0ustar00message = 'No MX DSN record was found for this email'; $this->rfcNumber = 5321; } } src/Warning/ObsoleteDTEXT.php000064400000000374151743410500012042 0ustar00rfcNumber = 5322; $this->message = 'Obsolete DTEXT in domain literal'; } } src/Warning/QuotedPart.php000064400000000536151743410500011545 0ustar00message = "Deprecated Quoted String found between $prevToken and $postToken"; } } src/Warning/QuotedString.php000064400000000525151743410500012103 0ustar00message = "Quoted String found between $prevToken and $postToken"; } } src/Warning/TLD.php000064400000000274151743410500010077 0ustar00message = "RFC5321, TLD"; } } src/Warning/Warning.php000064400000001270151743410500011056 0ustar00message; } /** * @return int */ public function code() { return self::CODE; } /** * @return int */ public function RFCNumber() { return $this->rfcNumber; } public function __toString() { return $this->message() . " rfc: " . $this->rfcNumber . "interal code: " . static::CODE; } }