diff --git a/composer.json b/composer.json index f2c6796e..a9023aad 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,13 @@ "require": { "ext-mbstring": "*" }, + "require-dev": { + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-intl": "*", + "true/punycode": "to handle UTF-8 emails, if you don't have ext-intl" + }, "autoload": { "psr-0": { "Assert": "lib/" diff --git a/lib/Assert/Assertion.php b/lib/Assert/Assertion.php index b17cafc2..cc447937 100644 --- a/lib/Assert/Assertion.php +++ b/lib/Assert/Assertion.php @@ -1117,7 +1117,7 @@ public static function email($value, $message = null, $propertyPath = null) { static::string($value, $message, $propertyPath); - if ( ! filter_var($value, FILTER_VALIDATE_EMAIL)) { + if ( ! filter_var(self::convertEmailToIdna($value), FILTER_VALIDATE_EMAIL)) { $message = sprintf( $message ?: 'Value "%s" was expected to be a valid e-mail address.', self::stringify($value) @@ -1139,6 +1139,29 @@ public static function email($value, $message = null, $propertyPath = null) } } + private static function convertEmailToIdna($email) + { + $convertIdna = function ($value) { + if (function_exists('idn_to_ascii')) { + return idn_to_ascii($value); + } elseif (class_exists('\True\Punycode')) { + $prevEncoding = mb_internal_encoding(); + mb_internal_encoding('utf-8'); + $punycode = new \True\Punycode(); + $result = $punycode->encode($value); + mb_internal_encoding($prevEncoding); + + return $result; + } + + return $value; + }; + + $result = implode('@', array_map($convertIdna, explode('@', $email))); + + return $result; + } + /** * Assert that value is an URL. * diff --git a/tests/Assert/Tests/AssertTest.php b/tests/Assert/Tests/AssertTest.php index e268769c..1f294011 100644 --- a/tests/Assert/Tests/AssertTest.php +++ b/tests/Assert/Tests/AssertTest.php @@ -457,25 +457,48 @@ public function testValidRange() Assertion::range(2.5, 2.25, 2.75); } - public function testInvalidEmail() + /** + * @param string $invalidEmail + * @dataProvider dataInvalidEmail + */ + public function testInvalidEmail($invalidEmail) { $this->setExpectedException('Assert\AssertionFailedException', null, Assertion::INVALID_EMAIL); - Assertion::email("foo"); + Assertion::email($invalidEmail); } - public function testValidEmail() + public static function dataInvalidEmail() { - Assertion::email("123hello+world@email.provider.com"); + return array( + 'no @' => array("foo"), + 'no domain' => array("foo@"), + 'two ats' => array("foo@baz@example.com"), + 'double quot' => array('foo"a@example.com'), + 'double quot in domain' => array('fooa@exam"ple.com'), + "\xb2 symbol" => array("foo\xb2a@example.com"), + 'contains space' => array("fo o@example.com"), + 'leading space' => array(" foo@example.com"), + 'trailing space' => array("foo@example.com "), + ); } /** - * @dataProvider dataInvalidUrl + * @param string $validEmail + * @dataProvider dataValidEmail */ - public function testInvalidUrl($url) + public function testValidEmail($validEmail) { - $this->setExpectedException('Assert\AssertionFailedException', null, Assertion::INVALID_URL); + Assertion::email($validEmail); + } - Assertion::url($url); + public static function dataValidEmail() + { + return array( + 'regular email' => array("123hello+world@email.provider.com"), + 'mixed case' => array("soMeUser@email.Provider.com"), + 'valid special chars' => array("!#$%&'*+-=?^_`{|}~@example.com"), + 'cyrillic characters ' => array("вася@домен.рф"), + ); } public static function dataInvalidUrl()