diff --git a/src/Egulias/EmailValidator/EmailLexer.php b/src/Egulias/EmailValidator/EmailLexer.php index 3d5cd69..721190f 100644 --- a/src/Egulias/EmailValidator/EmailLexer.php +++ b/src/Egulias/EmailValidator/EmailLexer.php @@ -84,6 +84,7 @@ public function find($type) if (!$search->lookahead) { throw new \UnexpectedValueException($type . ' not found'); } + return true; } /** diff --git a/src/Egulias/EmailValidator/Parser/LocalPart.php b/src/Egulias/EmailValidator/Parser/LocalPart.php index bd88583..47c7cc6 100644 --- a/src/Egulias/EmailValidator/Parser/LocalPart.php +++ b/src/Egulias/EmailValidator/Parser/LocalPart.php @@ -3,7 +3,6 @@ namespace Egulias\EmailValidator\Parser; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Parser\Parser; use Egulias\EmailValidator\EmailValidator; @@ -11,7 +10,9 @@ class LocalPart extends Parser { public function parse($localPart) { + $parseDQuote = true; $closingQuote = false; + while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) { if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) { @@ -19,6 +20,10 @@ public function parse($localPart) } $closingQuote = $this->checkDQUOTE($closingQuote); + if ($closingQuote && $parseDQuote) { + $this->parseDoubleQuote(); + $parseDQuote = false; + } if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { $this->parseComments(); @@ -26,20 +31,15 @@ public function parse($localPart) $this->checkConsecutiveDots(); - if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_AT)) { + if ( + $this->lexer->token['type'] === EmailLexer::S_DOT && + $this->lexer->isNextToken(EmailLexer::S_AT) + ) { throw new \InvalidArgumentException('ERR_DOT_END'); } $this->warnEscaping(); - - if ($this->lexer->isNextTokenAny( - array( - EmailLexer::INVALID, EmailLexer::S_LOWERTHAN, EmailLexer::S_GREATERTHAN - ) - ) - ) { - throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT'); - } + $this->isInvalidToken($this->lexer->token, $closingQuote); if ($this->isFWS()) { $this->parseFWS(); @@ -53,4 +53,42 @@ public function parse($localPart) $this->warnings[] = EmailValidator::RFC5322_LOCAL_TOOLONG; } } + + protected function parseDoubleQuote() + { + $special = array ( + EmailLexer::S_CR => true, + EmailLexer::S_HTAB => true, + EmailLexer::S_LF => true + ); + $setSpecialsWarning = true; + + $this->lexer->moveNext(); + while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && $this->lexer->token) { + if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) { + $this->warnings[] = EmailValidator::CFWS_FWS; + $setSpecialsWarning = false; + } + $this->lexer->moveNext(); + } + } + + + protected function isInvalidToken($token, $closingQuote) + { + $forbidden = array( + EmailLexer::S_COMMA, + EmailLexer::S_CLOSEBRACKET, + EmailLexer::S_OPENBRACKET, + EmailLexer::S_GREATERTHAN, + EmailLexer::S_LOWERTHAN, + EmailLexer::S_COLON, + EmailLexer::S_SEMICOLON, + EmailLexer::INVALID + ); + + if (in_array($token['type'], $forbidden) && !$closingQuote) { + throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT'); + } + } } diff --git a/src/Egulias/EmailValidator/Parser/Parser.php b/src/Egulias/EmailValidator/Parser/Parser.php index 3f4b3e3..b62b405 100644 --- a/src/Egulias/EmailValidator/Parser/Parser.php +++ b/src/Egulias/EmailValidator/Parser/Parser.php @@ -102,6 +102,10 @@ protected function checkConsecutiveDots() 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 || @@ -114,6 +118,21 @@ protected function isFWS() return false; } + protected function escaped() + { + $previous = $this->lexer->getPrevious(); + + if ($previous['type'] === EmailLexer::S_BACKSLASH + && + ($this->lexer->token['type'] === EmailLexer::S_SP || + $this->lexer->token['type'] === EmailLexer::S_HTAB) + ) { + return true; + } + + return false; + } + protected function warnEscaping() { if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { diff --git a/tests/egulias/Tests/EmailValidator/EmailLexerTest.php b/tests/egulias/Tests/EmailValidator/EmailLexerTest.php index 27f3d68..5ee3f3e 100644 --- a/tests/egulias/Tests/EmailValidator/EmailLexerTest.php +++ b/tests/egulias/Tests/EmailValidator/EmailLexerTest.php @@ -36,6 +36,14 @@ public function testLexerForTab() $this->assertEquals(EmailLexer::S_HTAB, $lexer->token['type']); } + public function testLexerSearchToken() + { + $lexer = new EmailLexer(); + $lexer->setInput("foo\tbar"); + $lexer->moveNext(); + $this->assertTrue($lexer->find(EmailLexer::S_HTAB)); + } + public function getTokens() { return array( diff --git a/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php b/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php index c279157..81fcf4d 100644 --- a/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php +++ b/tests/egulias/Tests/EmailValidator/EmailValidatorTest.php @@ -34,11 +34,15 @@ public function getValidEmails() array('fabien_potencier@example.fr'), array('example@localhost'), array('fab\'ien@symfony.com'), + array('fab\ ien@symfony.com'), array('example((example))@fakedfake.co.uk'), array('example@faked(fake).co.uk'), array('fabien+@symfony.com'), array('инфо@письмо.рф'), - + array('"username"@example.com'), + array('"user,name"@example.com'), + array('"user name"@example.com'), + array('"user@name"@example.com'), ); } @@ -66,6 +70,8 @@ public function getInvalidEmails() array('example@(fake).com'), array('example@(fake.com'), array('username@example,com'), + array('usern,ame@example.com'), + array('user[na]me@example.com'), ); } @@ -105,14 +111,13 @@ public function getInvalidEmailsWithErrors() array(EmailValidator::ERR_CR_NO_LF, "example@exa\rmple.co.uk"), array(EmailValidator::ERR_CR_NO_LF, "example@[\r]"), array(EmailValidator::ERR_CR_NO_LF, "exam\rple@example.co.uk"), - array(EmailValidator::ERR_CR_NO_LF, "\"\r\"@localhost"), ); } /** * @dataProvider getInvalidEmailsWithWarnings */ - public function testInvalidEmailsWithWarningsCheck($warnings, $email) + public function testValidEmailsWithWarningsCheck($warnings, $email) { $this->assertTrue($this->validator->isValid($email, true)); @@ -139,6 +144,13 @@ public function getInvalidEmailsWithWarnings() ), "\"\t\"@example.co.uk" ), + array( + array( + EmailValidator::RFC5321_QUOTEDSTRING, + EmailValidator::CFWS_FWS, + ), + "\"\r\"@example.co.uk" + ), array( array( EmailValidator::RFC5321_ADDRESSLITERAL,