Compare commits

...

46 Commits

Author SHA1 Message Date
Eduardo Gulias Davis 04c6cdf987 Merge pull request #84 from egulias/82-brackets
#82 orphan brackets
2015-11-11 04:28:32 +01:00
Eduardo Gulias Davis cc9defe645 fixes #82 2015-11-11 04:24:15 +01:00
Eduardo Gulias Davis 50aaa0b18f Control tests for .com.xx domains 2015-11-11 03:59:45 +01:00
Andrei Sozonov de448a30fa opened parenthesis converted to class field 2015-10-12 00:34:23 +02:00
Andrei Sozonov a7b385301d #80 validate number of closing parenthesis 2015-10-12 00:34:23 +02:00
Eduardo Gulias Davis f6447f8b99 Merge pull request #77 from alexpott/patch-1
Remove duplicated tokens
2015-07-16 08:29:32 +02:00
alexpott d4001715b2 Update EmailLexer.php
Removing duplicate array keys
2015-07-15 15:39:42 +01:00
Eduardo Gulias Davis 4b7fcf6796 Merge pull request #75 from xabbuh/patch-1
update installation instructions
2015-06-29 23:49:12 +02:00
Christian Flothmann 2a1145ffbf update installation instructions
Given that Composer will install the latest version of a package
matching the stability restrictions that apply to project, users can
get an undesired version of package when they don't specify a version
constraint.
2015-06-26 19:35:00 +02:00
Eduardo Gulias Davis af864423f5 Composer udpates 2015-06-22 23:07:51 +02:00
Eduardo Gulias Davis 12f16f70aa Merge pull request #70 from cilefen/master
Replaces dos-type line endings. Fixes issue #69.
2015-05-18 23:32:19 +02:00
Chris McCafferty 0afaa6a3b6 Replaces dos-type line endings. Fixes issue #69. 2015-05-17 16:37:57 -04:00
Eduardo Gulias Davis 5631c3fe2e Merge pull request #59 from egulias/next-min-ver
Improve UTF8 detection, domain part validation, others
2015-04-26 17:49:00 +02:00
Eduardo Gulias Davis c383717824 Improve UTF8 detection within the regexp, update tests for non-ascii char, fix #55 2015-04-26 17:38:14 +02:00
Eduardo Gulias Davis aabed98198 Improve domain part controls for non allowed chars. Fix #57 for DQUOTE 2015-04-26 17:38:14 +02:00
Eduardo Gulias Davis 3b0fd01aa3 Merge pull request #58 from xabbuh/travis-config
update Travis configuration
2015-04-23 11:44:43 +02:00
Christian Flothmann 4efe697504 update Travis configuration
* run builds on the container-based infrastructure
* add a build for PHP 7
* report the build status as soon as possible (either when a mandatory
  job fails or when only jobs allowed to fail are left)
2015-04-23 10:08:34 +02:00
Eduardo Gulias Davis 7a4c5714f5 Merge pull request #50 from xabbuh/test-deps
test low and high dependency versions
2015-04-23 00:10:58 +02:00
Eduardo Gulias Davis 0a26375a7a Merge pull request #54 from xabbuh/update-readme-markdown
fix readme markup
2015-03-08 01:24:16 +01:00
Christian Flothmann 773fe5f4a3 fix readme markup
Before, the character would have been rendered as is instead of being
treated as the final character of the headline.
2015-03-05 20:05:56 +01:00
Eduardo Gulias Davis 4a209951ee Merge pull request #49 from DavidPrevot/lexer
The latest php-doctrine-lexer is needed
2015-02-08 22:30:23 +01:00
Christian Flothmann a8b0bab5aa test low and high dependency versions 2015-02-06 10:43:29 +01:00
David Prévot cc0b91f311 The latest php-doctrine-lexer is needed
The tests actually fail with version 1.0.
2015-02-03 14:23:48 -04:00
Eduardo Gulias Davis 8092ecaeff Merge pull request #48 from 0x46616c6b/tests-doubledot-german-umlauts
Tests: Double dots in domain part, German Umlauts as valid e-mail
2015-01-27 01:03:43 +01:00
Eduardo Gulias Davis f19defb373 Merge pull request #47 from 0x46616c6b/vendors-phpunit
add phpunit to require-dev
2015-01-27 01:02:44 +01:00
louis 9a518fb1bd extends tests with invalid domain part with two dots and valid email with german umlauts 2015-01-26 14:13:13 +01:00
louis 9b184605e8 add phpunit to require-dev 2015-01-26 14:05:53 +01:00
Eduardo Gulias Davis 7a4842c60f Merge pull request #45 from kbond/semicolon-name
added test for email with semi-colon in name
2015-01-05 23:23:13 +01:00
Kevin Bond 687ab6a66e added test for email with semi-colon in name 2015-01-05 11:39:38 -05:00
Eduardo Gulias Davis 116d4054fa #21 - Test for words after domain part 2015-01-04 23:54:28 +01:00
Eduardo Gulias Davis af9417f765 #40 - require latest lexer 2015-01-04 23:42:32 +01:00
Eduardo Gulias Davis ab0bcfb424 Merge pull request #44 from egulias/kbond-semicolon-fail
From #43 - Kbond semicolon fail
2015-01-04 17:36:06 +01:00
Eduardo Gulias Davis 833b3e7924 #41 - Semicolon in domain 2015-01-04 17:28:58 +01:00
Eduardo Gulias Davis fec1609458 Update tests for DNSCheck 2015-01-04 17:24:36 +01:00
Eduardo Gulias Davis add559b99c Add PhpStorm logo
Thanks for supporting!
2015-01-04 17:03:09 +01:00
Kevin Bond 19e390d863 added failing test for email with semi-colon in domain 2014-12-18 11:00:21 -05:00
Eduardo Gulias Davis 5320c26b38 Merge pull request #39 from shakaran/patch-1
Rename Ohter.md to Other.md
2014-11-30 19:03:44 +01:00
Ángel Guzmán Maeso 3b6ab3bcd5 Rename Ohter.md to Other.md
Fix small filename typo.
2014-11-30 02:17:28 +01:00
Eduardo Gulias Davis 7a64ea18af Merge pull request #38 from egulias/improved-utf8
#30 - Improved utf8
2014-11-29 10:09:14 +01:00
Eduardo Gulias Davis 9103f4f99b #30 - Improved control for UTF8 chars 2014-11-29 10:05:19 +01:00
Eduardo Gulias Davis 3f623e9006 Merge pull request #37 from egulias/multiple_spaces
#36 - Multiple spaces in local part
2014-11-17 01:23:05 +01:00
Eduardo Gulias Davis 833eb65135 #30 - [WIP] - improve utf-8 control 2014-11-17 01:18:11 +01:00
Eduardo Gulias Davis e61481fefb #36 - Multiple spaces in local part 2014-11-16 19:15:26 +01:00
Eduardo Gulias Davis 518f80a0ff Merge pull request #35 from craue/fix-strict-check
make strict check work again
2014-11-06 09:59:44 +01:00
Christian Raue 2ec7babda6 fixed strict check 2014-11-05 01:09:55 +01:00
Christian Raue 05367768d6 improved tests for strict checking 2014-11-04 14:01:13 +01:00
15 changed files with 1602 additions and 490 deletions
+19 -3
View File
@@ -1,3 +1,5 @@
sudo: false
language: php language: php
php: php:
@@ -5,11 +7,25 @@ php:
- 5.4 - 5.4
- 5.5 - 5.5
- 5.6 - 5.6
- 7.0
- hhvm - hhvm
before_script: env:
- wget http://getcomposer.org/composer.phar global:
- php composer.phar install --dev --no-interaction - deps=no
matrix:
fast_finish: true
include:
- php: 5.3
env: deps=low
- php: 5.6
env: deps=high
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: script:
- mkdir -p build/logs - mkdir -p build/logs
+5 -2
View File
@@ -1,12 +1,15 @@
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) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6/small.png)](https://insight.sensiolabs.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6)[![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) #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## ##Installation##
Run the command below to install via Composer Run the command below to install via Composer
```shell ```shell
composer require egulias/email-validator composer require egulias/email-validator "~1.2"
``` ```
##Usage## ##Usage##
+5 -4
View File
@@ -3,22 +3,23 @@
"description": "A library for validating emails", "description": "A library for validating emails",
"homepage": "https://github.com/egulias/EmailValidator", "homepage": "https://github.com/egulias/EmailValidator",
"type": "Library", "type": "Library",
"keywords": ["email", "validation", "validator"], "keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"],
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{"name": "Eduardo Gulias Davis"} {"name": "Eduardo Gulias Davis"}
], ],
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.3.x-dev" "dev-master": "2.0.x-dev"
} }
}, },
"require": { "require": {
"php": ">= 5.3.3", "php": ">= 5.3.3",
"doctrine/lexer": "~1.0" "doctrine/lexer": "~1.0,>=1.0.1"
}, },
"require-dev" : { "require-dev" : {
"satooshi/php-coveralls": "dev-master" "satooshi/php-coveralls": "dev-master",
"phpunit/phpunit": "~4.4"
}, },
"autoload": { "autoload": {
"psr-0": { "psr-0": {
Generated
+1037 -123
View File
File diff suppressed because it is too large Load Diff
+26 -19
View File
@@ -67,16 +67,12 @@ class EmailLexer extends AbstractLexer
"\n" => self::S_LF, "\n" => self::S_LF,
"\r\n" => self::CRLF, "\r\n" => self::CRLF,
'IPv6' => self::S_IPV6TAG, 'IPv6' => self::S_IPV6TAG,
'<' => self::S_LOWERTHAN,
'>' => self::S_GREATERTHAN,
'{' => self::S_OPENQBRACKET, '{' => self::S_OPENQBRACKET,
'}' => self::S_CLOSEQBRACKET, '}' => self::S_CLOSEQBRACKET,
'' => self::S_EMPTY, '' => self::S_EMPTY,
'\0' => self::C_NUL, '\0' => self::C_NUL,
); );
protected $invalidASCII = array(226 => 1,);
protected $hasInvalidTokens = false; protected $hasInvalidTokens = false;
protected $previous; protected $previous;
@@ -138,12 +134,12 @@ class EmailLexer extends AbstractLexer
protected function getCatchablePatterns() protected function getCatchablePatterns()
{ {
return array( return array(
'[a-zA-Z_]+[46]?', '[a-zA-Z_]+[46]?', //ASCII and domain literal
'[^\x00-\x7F]', //UTF-8
'[0-9]+', '[0-9]+',
'\r\n', '\r\n',
'::', '::',
'\s+', '\s+?',
'[\x10-\x1F]+',
'.', '.',
); );
} }
@@ -155,7 +151,7 @@ class EmailLexer extends AbstractLexer
*/ */
protected function getNonCatchablePatterns() protected function getNonCatchablePatterns()
{ {
return array('[\x7f-\xff]+'); return array('[\xA0-\xff]+');
} }
/** /**
@@ -167,16 +163,15 @@ class EmailLexer extends AbstractLexer
*/ */
protected function getType(&$value) protected function getType(&$value)
{ {
if ($this->isNullType($value)) { if ($this->isNullType($value)) {
return self::C_NUL; return self::C_NUL;
} }
if (isset($this->charValue[$value])) { if ($this->isValid($value)) {
return $this->charValue[$value]; return $this->charValue[$value];
} }
if ($this->isInvalid($value)) { if ($this->isUTF8Invalid($value)) {
$this->hasInvalidTokens = true; $this->hasInvalidTokens = true;
return self::INVALID; return self::INVALID;
} }
@@ -184,8 +179,18 @@ class EmailLexer extends AbstractLexer
return self::GENERIC; return self::GENERIC;
} }
protected function isValid($value)
{
if (isset($this->charValue[$value])) {
return true;
}
return false;
}
/** /**
* @param string $value * @param $value
* @return bool
*/ */
protected function isNullType($value) protected function isNullType($value)
{ {
@@ -197,18 +202,20 @@ class EmailLexer extends AbstractLexer
} }
/** /**
* @param string $value * @param $value
* @return bool
*/ */
protected function isInvalid($value) protected function isUTF8Invalid($value)
{ {
if (preg_match('/[\x10-\x1F]+/', $value)) { if (preg_match('/\p{Cc}+/u', $value)) {
return true;
}
if (isset($this->invalidASCII[ord($value)])) {
return true; return true;
} }
return false; return false;
} }
protected function getModifiers()
{
return 'iu';
}
} }
+6 -4
View File
@@ -29,7 +29,8 @@ class EmailParser
} }
/** /**
* @param string $str * @param $str
* @return array
*/ */
public function parse($str) public function parse($str)
{ {
@@ -39,15 +40,16 @@ class EmailParser
throw new \InvalidArgumentException('ERR_NOLOCALPART'); throw new \InvalidArgumentException('ERR_NOLOCALPART');
} }
if ($this->lexer->hasInvalidTokens()) {
throw new \InvalidArgumentException('ERR_INVALID_ATEXT');
}
$this->localPartParser->parse($str); $this->localPartParser->parse($str);
$this->domainPartParser->parse($str); $this->domainPartParser->parse($str);
$this->setParts($str); $this->setParts($str);
if ($this->lexer->hasInvalidTokens()) {
throw new \InvalidArgumentException('ERR_INVALID_ATEXT');
}
return array('local' => $this->localPart, 'domain' => $this->domainPart); return array('local' => $this->localPart, 'domain' => $this->domainPart);
} }
@@ -33,6 +33,7 @@ class EmailValidator
const ERR_FWS_CRLF_END = 149; const ERR_FWS_CRLF_END = 149;
const ERR_CR_NO_LF = 150; const ERR_CR_NO_LF = 150;
const ERR_DEPREC_REACHED = 151; const ERR_DEPREC_REACHED = 151;
const ERR_UNOPENEDCOMMENT = 152;
const RFC5321_TLD = 9; const RFC5321_TLD = 9;
const RFC5321_TLDNUMERIC = 10; const RFC5321_TLDNUMERIC = 10;
const RFC5321_QUOTEDSTRING = 11; const RFC5321_QUOTEDSTRING = 11;
@@ -84,7 +85,7 @@ class EmailValidator
return false; return false;
} }
$dns = false; $dns = true;
if ($checkDNS) { if ($checkDNS) {
$dns = $this->checkDNS(); $dns = $this->checkDNS();
} }
@@ -95,12 +96,7 @@ class EmailValidator
return false; return false;
} }
return ($strict) ? $this->checkStrict($dns) : true; return !$strict || (!$this->hasWarnings() && $dns);
}
private function checkStrict($dns)
{
return !($this->hasWarnings() && !$dns);
} }
/** /**
@@ -4,7 +4,6 @@
namespace Egulias\EmailValidator\Parser; namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer; use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Parser\Parser;
use Egulias\EmailValidator\EmailValidator; use Egulias\EmailValidator\EmailValidator;
class DomainPart extends Parser class DomainPart extends Parser
@@ -103,6 +102,8 @@ class DomainPart extends Parser
protected function doParseDomainPart() protected function doParseDomainPart()
{ {
$domain = ''; $domain = '';
$openedParenthesis = 0;
$openBrackets = false;
do { do {
$prev = $this->lexer->getPrevious(); $prev = $this->lexer->getPrevious();
@@ -112,13 +113,25 @@ class DomainPart extends Parser
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments(); $this->parseComments();
$openedParenthesis += $this->getOpenedParenthesis();
$this->lexer->moveNext(); $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->checkConsecutiveDots();
$this->checkDomainPartExceptions($prev); $this->checkDomainPartExceptions($prev);
if ($this->hasBrackets()) { if ($openBrackets = $this->hasBrackets($openBrackets)) {
$this->parseDomainLiteral(); $this->parseDomainLiteral();
} }
@@ -179,7 +192,7 @@ class DomainPart extends Parser
} }
if ($this->lexer->isNextToken(EmailLexer::S_CR)) { if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
throw new \InvalidArgumentException("ERR_CR_NO_LF"); throw new \InvalidArgumentException('ERR_CR_NO_LF');
} }
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) { if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT; $this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
@@ -217,9 +230,6 @@ class DomainPart extends Parser
return $addressLiteral; return $addressLiteral;
} }
/**
* @param string $addressLiteral
*/
protected function checkIPV4Tag($addressLiteral) protected function checkIPV4Tag($addressLiteral)
{ {
$matchesIP = array(); $matchesIP = array();
@@ -245,6 +255,17 @@ class DomainPart extends Parser
protected function checkDomainPartExceptions($prev) 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) { if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
throw new \InvalidArgumentException('ERR_COMMA_IN_DOMAIN'); throw new \InvalidArgumentException('ERR_COMMA_IN_DOMAIN');
} }
@@ -267,8 +288,12 @@ class DomainPart extends Parser
} }
} }
protected function hasBrackets() 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) { if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
return false; return false;
} }
@@ -4,8 +4,6 @@ namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer; use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator; use Egulias\EmailValidator\EmailValidator;
use \InvalidArgumentException;
class LocalPart extends Parser class LocalPart extends Parser
{ {
@@ -13,9 +11,9 @@ class LocalPart extends Parser
{ {
$parseDQuote = true; $parseDQuote = true;
$closingQuote = false; $closingQuote = false;
$openedParenthesis = 0;
while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) { while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) {
if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) { if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) {
throw new \InvalidArgumentException('ERR_DOT_START'); throw new \InvalidArgumentException('ERR_DOT_START');
} }
@@ -27,12 +25,19 @@ class LocalPart extends Parser
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments(); $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(); $this->checkConsecutiveDots();
if ( if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$this->lexer->token['type'] === EmailLexer::S_DOT &&
$this->lexer->isNextToken(EmailLexer::S_AT) $this->lexer->isNextToken(EmailLexer::S_AT)
) { ) {
throw new \InvalidArgumentException('ERR_DOT_END'); throw new \InvalidArgumentException('ERR_DOT_END');
@@ -83,7 +88,7 @@ class LocalPart extends Parser
$this->lexer->moveNext(); $this->lexer->moveNext();
if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) { if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
throw new InvalidArgumentException("ERR_EXPECTED_ATEXT"); throw new \InvalidArgumentException('ERR_EXPECTED_ATEXT');
} }
} }
@@ -91,12 +96,12 @@ class LocalPart extends Parser
if ($prev['type'] === EmailLexer::S_BACKSLASH) { if ($prev['type'] === EmailLexer::S_BACKSLASH) {
if (!$this->checkDQUOTE(false)) { 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) { 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; return $parseAgain;
+17 -10
View File
@@ -9,6 +9,7 @@ abstract class Parser
{ {
protected $warnings = array(); protected $warnings = array();
protected $lexer; protected $lexer;
protected $openedParenthesis = 0;
public function __construct(EmailLexer $lexer) public function __construct(EmailLexer $lexer)
{ {
@@ -20,7 +21,13 @@ abstract class Parser
return $this->warnings; return $this->warnings;
} }
abstract function parse($str); abstract public function parse($str);
/** @return int */
public function getOpenedParenthesis()
{
return $this->openedParenthesis;
}
/** /**
* validateQuotedPair * validateQuotedPair
@@ -35,15 +42,15 @@ abstract class Parser
$this->warnings[] = EmailValidator::DEPREC_QP; $this->warnings[] = EmailValidator::DEPREC_QP;
} }
/**
* @return string the the comment
* @throws \InvalidArgumentException
*/
protected function parseComments() protected function parseComments()
{ {
$this->openedParenthesis = 1;
$this->isUnclosedComment(); $this->isUnclosedComment();
$this->warnings[] = EmailValidator::CFWS_COMMENT; $this->warnings[] = EmailValidator::CFWS_COMMENT;
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) {
$this->openedParenthesis++;
}
$this->warnEscaping(); $this->warnEscaping();
$this->lexer->moveNext(); $this->lexer->moveNext();
} }
@@ -75,11 +82,11 @@ abstract class Parser
$this->checkCRLFInFWS(); $this->checkCRLFInFWS();
if ($this->lexer->token['type'] === EmailLexer::S_CR) { if ($this->lexer->token['type'] === EmailLexer::S_CR) {
throw new \InvalidArgumentException("ERR_CR_NO_LF"); throw new \InvalidArgumentException('ERR_CR_NO_LF');
} }
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) { if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException("ERR_ATEXT_AFTER_CFWS"); throw new \InvalidArgumentException('ERR_ATEXT_AFTER_CFWS');
} }
if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) { if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
@@ -160,7 +167,7 @@ abstract class Parser
return $hasClosingQuote; return $hasClosingQuote;
} }
$previous = $this->lexer->getPrevious(); $previous = $this->lexer->getPrevious();
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) { if ($previous['type'] === EmailLexer::GENERIC && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT'); throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
} }
@@ -181,10 +188,10 @@ abstract class Parser
return; return;
} }
if ($this->lexer->isNextToken(EmailLexer::CRLF)) { if ($this->lexer->isNextToken(EmailLexer::CRLF)) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_X2"); throw new \InvalidArgumentException('ERR_FWS_CRLF_X2');
} }
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_END"); throw new \InvalidArgumentException('ERR_FWS_CRLF_END');
} }
} }
} }
@@ -26,6 +26,73 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
$this->assertEquals($token, $lexer->token['type']); $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() public function testLexerForTab()
{ {
$lexer = new EmailLexer(); $lexer = new EmailLexer();
@@ -36,6 +103,17 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
$this->assertEquals(EmailLexer::S_HTAB, $lexer->token['type']); $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() public function testLexerSearchToken()
{ {
$lexer = new EmailLexer(); $lexer = new EmailLexer();
@@ -44,15 +122,6 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
$this->assertTrue($lexer->find(EmailLexer::S_HTAB)); $this->assertTrue($lexer->find(EmailLexer::S_HTAB));
} }
public function testLexerHasInvalidTokens()
{
$lexer = new EmailLexer();
$lexer->setInput(chr(226));
$lexer->moveNext();
$lexer->moveNext();
$this->assertTrue($lexer->hasInvalidTokens());
}
public function getTokens() public function getTokens()
{ {
return array( return array(
@@ -85,7 +154,7 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
array('}', EmailLexer::S_CLOSEQBRACKET), array('}', EmailLexer::S_CLOSEQBRACKET),
array('', EmailLexer::S_EMPTY), array('', EmailLexer::S_EMPTY),
array(chr(31), EmailLexer::INVALID), array(chr(31), EmailLexer::INVALID),
array(chr(226), EmailLexer::INVALID), array(chr(226), EmailLexer::GENERIC),
array(chr(0), EmailLexer::C_NUL) array(chr(0), EmailLexer::C_NUL)
); );
} }
@@ -26,9 +26,18 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->validator->isValid($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() public function getValidEmails()
{ {
return array( return array(
array('â@iana.org'),
array('fabien@symfony.com'), array('fabien@symfony.com'),
array('example@example.co.uk'), array('example@example.co.uk'),
array('fabien_potencier@example.fr'), array('fabien_potencier@example.fr'),
@@ -47,6 +56,14 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array('"test\ test"@iana.org'), array('"test\ test"@iana.org'),
array('""@iana.org'), array('""@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'),
); );
} }
@@ -61,7 +78,9 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
public function getInvalidEmails() public function getInvalidEmails()
{ {
return array( 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.co.uk'),
array('example@example@example.co.uk'), array('example@example@example.co.uk'),
array('(test_exampel@example.fr)'), array('(test_exampel@example.fr)'),
@@ -98,6 +117,15 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
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.org \r\n\r\n '),
array('test@iana/icann.org'), 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'),
); );
} }
@@ -127,6 +155,11 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array(EmailValidator::ERR_DOT_END, 'example@localhost.'), array(EmailValidator::ERR_DOT_END, 'example@localhost.'),
array(EmailValidator::ERR_DOT_END, 'example.@example.co.uk'), array(EmailValidator::ERR_DOT_END, 'example.@example.co.uk'),
array(EmailValidator::ERR_UNCLOSEDCOMMENT, '(example@localhost'), 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_UNCLOSEDQUOTEDSTR, '"example@localhost'),
array(EmailValidator::ERR_EXPECTING_ATEXT, 'exa"mple@localhost'), array(EmailValidator::ERR_EXPECTING_ATEXT, 'exa"mple@localhost'),
//This was the original. But atext is not allowed after \n //This was the original. But atext is not allowed after \n
@@ -150,16 +183,45 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($warnings, $this->validator->getWarnings()); $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() public function getInvalidEmailsWithWarnings()
{ {
return array( return array(
array(array( EmailValidator::DEPREC_CFWS_NEAR_AT,), 'example @example.co.uk'), array(
array(array( EmailValidator::DEPREC_CFWS_NEAR_AT,), 'example@ example.co.uk'), array(
array(array( EmailValidator::CFWS_COMMENT,), 'example@example(examplecomment).co.uk'), EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD
),
'example @example.co.uk'
),
array(
array(
EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD
),
'example@ example.co.uk'
),
array(
array(
EmailValidator::CFWS_COMMENT,
EmailValidator::DNSWARN_NO_RECORD
),
'example@example(examplecomment).co.uk'
),
array( array(
array( array(
EmailValidator::CFWS_COMMENT, EmailValidator::CFWS_COMMENT,
EmailValidator::DEPREC_CFWS_NEAR_AT, EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD,
), ),
'example(examplecomment)@example.co.uk' 'example(examplecomment)@example.co.uk'
), ),
@@ -167,6 +229,7 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array( array(
EmailValidator::RFC5321_QUOTEDSTRING, EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::CFWS_FWS, EmailValidator::CFWS_FWS,
EmailValidator::DNSWARN_NO_RECORD,
), ),
"\"\t\"@example.co.uk" "\"\t\"@example.co.uk"
), ),
@@ -174,6 +237,7 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array( array(
EmailValidator::RFC5321_QUOTEDSTRING, EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::CFWS_FWS, EmailValidator::CFWS_FWS,
EmailValidator::DNSWARN_NO_RECORD
), ),
"\"\r\"@example.co.uk" "\"\r\"@example.co.uk"
), ),
@@ -265,12 +329,14 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array( array(
array( array(
EmailValidator::RFC5321_QUOTEDSTRING, EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::DNSWARN_NO_RECORD
), ),
'"example"@example.co.uk' '"example"@example.co.uk'
), ),
array( array(
array( array(
EmailValidator::RFC5322_LOCAL_TOOLONG, EmailValidator::RFC5322_LOCAL_TOOLONG,
EmailValidator::DNSWARN_NO_RECORD
), ),
'too_long_localpart_too_long_localpart_too_long_localpart_too_long_localpart@example.co.uk' 'too_long_localpart_too_long_localpart_too_long_localpart_too_long_localpart@example.co.uk'
), ),
@@ -301,14 +367,15 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
'parttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart'. 'parttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart'.
'toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpar' 'toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpar'
), ),
array(
array(
EmailValidator::DNSWARN_NO_RECORD,
),
'test@test'
),
); );
} }
public function testInvalidEmailsWithDNSAndStrict()
{
$this->assertFalse($this->validator->isValid('test@test', true, true));
}
public function testInvalidEmailsWithStrict() public function testInvalidEmailsWithStrict()
{ {
$this->assertFalse($this->validator->isValid('"test"@test', false, true)); $this->assertFalse($this->validator->isValid('"test"@test', false, true));