From 3dc66acf64ef79ed86fdaabb42cff85d827bc3d1 Mon Sep 17 00:00:00 2001 From: Eduardo Gulias Davis Date: Sat, 5 Dec 2020 22:58:13 +0100 Subject: [PATCH] domain improvements over UTF16 and invalid ascii chars --- .gitignore | 1 + src/EmailLexer.php | 121 ++++++++++-------- src/Parser/DomainLiteral.php | 4 +- src/Parser/DomainPart.php | 2 +- tests/EmailValidator/EmailLexerTest.php | 4 + .../RFCValidationDomainPartTest.php | 22 ++++ .../Validation/RFCValidationTest.php | 3 +- 7 files changed, 100 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index fb2edef..9523478 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +phpunit.result.cache .idea composer.lock report/ diff --git a/src/EmailLexer.php b/src/EmailLexer.php index 9463e0e..fa57ae4 100644 --- a/src/EmailLexer.php +++ b/src/EmailLexer.php @@ -7,50 +7,52 @@ use Doctrine\Common\Lexer\AbstractLexer; class EmailLexer extends AbstractLexer { //ASCII values - const C_DEL = 127; + const S_EMPTY = null; const C_NUL = 0; - const S_AT = 64; - const S_BACKSLASH = 92; - const S_DOT = 46; + const S_HTAB = 9; + const S_LF = 10; + const S_CR = 13; + const S_SP = 32; + const EXCLAMATION = 33; const S_DQUOTE = 34; + const NUMBER_SIGN = 35; + const DOLLAR = 36; + const PERCENTAGE = 37; + const AMPERSAND = 38; const S_SQUOTE = 39; + const S_OPENPARENTHESIS = 40; + const S_CLOSEPARENTHESIS = 41; + const ASTERISK = 42; + const S_PLUS = 43; + const S_COMMA = 44; + const S_HYPHEN = 45; + const S_DOT = 46; + const S_SLASH = 47; + const S_COLON = 58; + const S_SEMICOLON = 59; + const S_LOWERTHAN = 60; + const S_EQUAL = 61; + const S_GREATERTHAN = 62; + const QUESTIONMARK = 63; + const S_AT = 64; + const S_OPENBRACKET = 91; + const S_BACKSLASH = 92; + const S_CLOSEBRACKET = 93; + const CARET = 94; + const S_UNDERSCORE = 95; const S_BACKTICK = 96; const S_OPENCURLYBRACES = 123; - const S_CLOSECURLYBRACES = 125; - const S_OPENPARENTHESIS = 49; - const S_CLOSEPARENTHESIS = 261; - const S_OPENBRACKET = 262; - const S_CLOSEBRACKET = 263; - const S_HYPHEN = 264; - const S_COLON = 265; - const S_DOUBLECOLON = 266; const S_PIPE = 124; + const S_CLOSECURLYBRACES = 125; const S_TILDE = 126; - const S_UNDERSCORE = 95; - const S_PLUS = 43; - const S_EQUAL = 61; - const S_SP = 267; - const S_HTAB = 268; - const S_CR = 269; - const S_LF = 270; - const S_IPV6TAG = 271; - const S_LOWERTHAN = 272; - const S_GREATERTHAN = 273; - const S_COMMA = 274; - const S_SEMICOLON = 275; - const S_OPENQBRACKET = 276; - const S_CLOSEQBRACKET = 277; - const S_SLASH = 278; - const S_EMPTY = null; + const C_DEL = 127; + const INVERT_QUESTIONMARK= 168; + const INVERT_EXCLAMATION = 173; const GENERIC = 300; - const CRLF = 301; - const ASTERISK = 42; - const EXCLAMATION = 33; - const AMPERSAND = 38; - const CARET = 94; - const PERCENTAGE = 37; - const DOLLAR = 36; + const S_IPV6TAG = 301; const INVALID = 302; + const CRLF = 1310; + const S_DOUBLECOLON = 5858; const ASCII_INVALID_FROM = 127; const ASCII_INVALID_TO = 199; @@ -99,6 +101,11 @@ class EmailLexer extends AbstractLexer '_' => self::S_UNDERSCORE, '=' => self::S_EQUAL, '+' => self::S_PLUS, + '¿' => self::INVERT_QUESTIONMARK, + '?' => self::QUESTIONMARK, + '#' => self::NUMBER_SIGN, + '¡' => self::INVERT_EXCLAMATION, + null => self::S_EMPTY, ); /** @@ -247,7 +254,9 @@ class EmailLexer extends AbstractLexer */ protected function getNonCatchablePatterns() { - return array('[\xA0-\xff]+'); + return [ + '[\xA0-\xff]+', + ]; } /** @@ -259,28 +268,38 @@ class EmailLexer extends AbstractLexer */ protected function getType(&$value) { - if ($this->isNullType($value)) { + $encoded = $value; + + if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') { + $encoded = utf8_encode($value); + } + + if ($this->isValid($encoded)) { + return $this->charValue[$encoded]; + } + + if ($this->isNullType($encoded)) { return self::C_NUL; } - if ($this->isValid($value)) { - return $this->charValue[$value]; - } - - if ($this->isUTF8Invalid($value)) { + if ($this->isInvalidChar($encoded)) { $this->hasInvalidTokens = true; return self::INVALID; } + return self::GENERIC; } - /** - * @param string $value - * - * @return bool - */ - protected function isValid($value) + protected function isInvalidChar(string $value) : bool + { + if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) { + return false; + } + return true; + } + + protected function isValid(string $value) : bool { if (isset($this->charValue[$value])) { return true; @@ -302,11 +321,7 @@ class EmailLexer extends AbstractLexer return false; } - /** - * @param string $value - * @return bool - */ - protected function isUTF8Invalid($value) + protected function isUTF8Invalid(string $value) : bool { if (preg_match('/\p{Cc}+/u', $value)) { return true; diff --git a/src/Parser/DomainLiteral.php b/src/Parser/DomainLiteral.php index 8c5fe1f..c4d0e7b 100644 --- a/src/Parser/DomainLiteral.php +++ b/src/Parser/DomainLiteral.php @@ -36,7 +36,7 @@ class DomainLiteral extends Parser $this->addObsoleteWarnings(); - if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) { + if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENBRACKET, EmailLexer::S_OPENBRACKET))) { return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->token['value']); } @@ -58,7 +58,7 @@ class DomainLiteral extends Parser $IPv6TAG = true; } - if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) { + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEBRACKET) { break; } diff --git a/src/Parser/DomainPart.php b/src/Parser/DomainPart.php index 29a3255..79c106a 100644 --- a/src/Parser/DomainPart.php +++ b/src/Parser/DomainPart.php @@ -256,7 +256,7 @@ class DomainPart extends Parser $validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true; } - if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) { + if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET && $prev['type'] !== EmailLexer::S_AT) { return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->token['value']); } diff --git a/tests/EmailValidator/EmailLexerTest.php b/tests/EmailValidator/EmailLexerTest.php index 3102284..61a93b2 100644 --- a/tests/EmailValidator/EmailLexerTest.php +++ b/tests/EmailValidator/EmailLexerTest.php @@ -159,6 +159,10 @@ class EmailLexerTests extends TestCase array('~', EmailLexer::S_TILDE), array('=', EmailLexer::S_EQUAL), array('+', EmailLexer::S_PLUS), + array('¿', EmailLexer::INVERT_QUESTIONMARK), + array('?', EmailLexer::QUESTIONMARK), + array('#', EmailLexer::NUMBER_SIGN), + array('¡', EmailLexer::INVERT_EXCLAMATION), array('_', EmailLexer::S_UNDERSCORE), array('', EmailLexer::S_EMPTY), array(chr(31), EmailLexer::INVALID), diff --git a/tests/EmailValidator/Validation/RFCValidationDomainPartTest.php b/tests/EmailValidator/Validation/RFCValidationDomainPartTest.php index b8bc50a..6e6760d 100644 --- a/tests/EmailValidator/Validation/RFCValidationDomainPartTest.php +++ b/tests/EmailValidator/Validation/RFCValidationDomainPartTest.php @@ -140,6 +140,13 @@ class RFCValidationDomainPartTest extends TestCase ['test@email=a.com'], ['test@email+a.com'], ['test@email_a.com'], + ['test@email¡a.com'], + ['test@email?a.com'], + ['test@email#a.com'], + ['test@email¨a.com'], + ['test@email€a.com'], + ['test@email$a.com'], + ['test@email£a.com'], ]; } @@ -220,5 +227,20 @@ class RFCValidationDomainPartTest extends TestCase ]; } + public function invalidUTF16Chars() + { + return [ + ['example@symƒony.com'], + ]; + } + + /** + * @dataProvider invalidUTF16Chars + */ + public function testInvalidUTF16($email) + { + $this->markTestSkipped('Util finding a way to control this kind of chars'); + $this->assertFalse($this->validator->isValid($email, $this->lexer)); + } } \ No newline at end of file diff --git a/tests/EmailValidator/Validation/RFCValidationTest.php b/tests/EmailValidator/Validation/RFCValidationTest.php index 31318a6..f910c64 100644 --- a/tests/EmailValidator/Validation/RFCValidationTest.php +++ b/tests/EmailValidator/Validation/RFCValidationTest.php @@ -148,13 +148,14 @@ class RFCValidationTest extends TestCase ['\r\n \r\ntest@iana.org'], ['\r\n \r\n test@iana.org'], ['test;123@foobar.com'], + ['examp║le@symfony.com'], ]; } /** * @dataProvider getInvalidEmailsWithErrors */ - public function testInvalidEmailsWithErrorsCheck($error, $email) + public function testInvalidDEmailsWithErrorsCheck($error, $email) { $this->assertFalse($this->validator->isValid($email, $this->lexer)); $this->assertEquals($error, $this->validator->getError());