Commit d2bd009e authored by Georg Ringer's avatar Georg Ringer Committed by Frank Nägler
Browse files

[FEATURE] Use Egulias\EmailValidator for email validation

GeneralUtility::validEmail uses now Egulias\EmailValidator instead of
the method `filter_var` for validating an email address. This allows
more email addresses to be valid.

Resolves: #90370
Releases: master
Change-Id: I29bf4c994d730c789828cdecc247080dd669d9fb
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63235


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Tested-by: Frank Nägler's avatarFrank Nägler <frank.naegler@typo3.org>
Reviewed-by: Oliver Klee's avatarOliver Klee <typo3-coding@oliverklee.de>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Frank Nägler's avatarFrank Nägler <frank.naegler@typo3.org>
parent ae10a2b5
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1fb6111e64333a404a34904a84b8834f",
"content-hash": "5ecf79898d6ee3254f173cd59c27d260",
"packages": [
{
"name": "cogpowered/finediff",
......@@ -486,27 +486,27 @@
},
{
"name": "egulias/email-validator",
"version": "2.1.11",
"version": "2.1.16",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23"
"reference": "5065fafc8c29d229ff207f2a89b02175f479a909"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23",
"reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/5065fafc8c29d229ff207f2a89b02175f479a909",
"reference": "5065fafc8c29d229ff207f2a89b02175f479a909",
"shasum": ""
},
"require": {
"doctrine/lexer": "^1.0.1",
"php": ">= 5.5"
"php": ">=5.5",
"symfony/polyfill-intl-idn": "^1.10"
},
"require-dev": {
"dominicsayers/isemail": "dev-master",
"phpunit/phpunit": "^4.8.35||^5.7||^6.0",
"satooshi/php-coveralls": "^1.0.1",
"symfony/phpunit-bridge": "^4.4@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"
......@@ -540,7 +540,7 @@
"validation",
"validator"
],
"time": "2019-08-13T17:33:27+00:00"
"time": "2020-02-12T22:28:28+00:00"
},
{
"name": "guzzlehttp/guzzle",
......@@ -6211,8 +6211,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
"role": "lead",
"email": "sebastian@phpunit.de"
}
],
"description": "Utility class for timing",
......@@ -7079,8 +7079,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
"role": "lead",
"email": "sebastian@phpunit.de"
}
],
"description": "Collection of value objects that represent the types of the PHP type system",
......
......@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Utility;
* The TYPO3 project - inspiring people to share!
*/
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
use GuzzleHttp\Exception\RequestException;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerAwareInterface;
......@@ -848,16 +850,6 @@ class GeneralUtility
/**
* Checking syntax of input email address
*
* http://tools.ietf.org/html/rfc3696
* International characters are allowed in email. So the whole address needs
* to be converted to punicode before passing it to filter_var().
*
* Also the @ sign may appear multiple times in an address. If not used as
* a boundary marker between the user- and domain part, it must be escaped
* with a backslash: \@. This mean we can not just explode on the @ sign and
* expect to get just two parts. So we pop off the domain and then glue the
* rest together again.
*
* @param string $email Input string to evaluate
* @return bool Returns TRUE if the $email address (input string) is valid
*/
......@@ -867,20 +859,11 @@ class GeneralUtility
if (!is_string($email)) {
return false;
}
$atPosition = strrpos($email, '@');
if (!$atPosition || $atPosition + 1 === strlen($email)) {
// Return if no @ found or it is placed at the very beginning or end of the email
if (trim($email) !== $email) {
return false;
}
$domain = substr($email, $atPosition + 1);
$user = substr($email, 0, $atPosition);
if (!preg_match('/^[a-z0-9.\\-]*$/i', $domain)) {
$domain = HttpUtility::idn_to_ascii($domain);
if ($domain === false) {
return false;
}
}
return filter_var($user . '@' . $domain, FILTER_VALIDATE_EMAIL) !== false;
$validator = new EmailValidator();
return $validator->isValid($email, new RFCValidation());
}
/**
......
.. include:: ../../Includes.txt
=================================================================
Feature: #90370 - Use Egulias\EmailValidator for email validation
=================================================================
See :issue:`90370`
Description
===========
:php:`\TYPO3\CMS\Core\Utility\GeneralUtility::validEmail` uses now the package `Egulias\EmailValidator` and the `RFCValidation` for validating the provided email address.
This allows to follow the RFC more closely.
Impact
======
The following email addresses are now valid:
- `foo@äöüfoo.com`
- `foo@bar.123`
- `test@localhost`
- `äöüfoo@bar.com`
- `Abc@def"@example.com`
.. index:: PHP-API, ext:core
......@@ -918,7 +918,11 @@ class GeneralUtilityTest extends UnitTestCase
'hash in local part' => ['foo#bar@example.com'],
'dot in local part' => ['firstname.lastname@employee.2something.com'],
'dash as local part' => ['-@foo.com'],
'umlauts in domain part' => ['foo@äöüfoo.com']
'umlauts in domain part' => ['foo@äöüfoo.com'],
'number as top level domain' => ['foo@bar.123'],
'top level domain only' => ['test@localhost'],
'umlauts in local part' => ['äöüfoo@bar.com'],
'quoted @ char' => ['"Abc@def"@example.com'],
];
}
......@@ -953,20 +957,18 @@ class GeneralUtilityTest extends UnitTestCase
'closing parenthesis in local part' => ['foo)bar@example.com'],
'opening square bracket in local part' => ['foo[bar@example.com'],
'closing square bracket as local part' => [']@example.com'],
'top level domain only' => ['test@com'],
'dash as second level domain' => ['foo@-.com'],
'domain part starting with dash' => ['foo@-foo.com'],
'domain part ending with dash' => ['foo@foo-.com'],
'number as top level domain' => ['foo@bar.123'],
'dot at beginning of domain part' => ['test@.com'],
'local part ends with dot' => ['e.x.a.m.p.l.e.@example.com'],
'umlauts in local part' => ['äöüfoo@bar.com'],
'trailing whitespace' => ['test@example.com '],
'trailing carriage return' => ['test@example.com' . CR],
'trailing linefeed' => ['test@example.com' . LF],
'trailing carriage return linefeed' => ['test@example.com' . CRLF],
'trailing tab' => ['test@example.com' . "\t"],
'prohibited input characters' => ['“mailto:test@example.com”'],
'escaped @ char' => ['abc\@def@example.com'], // known bug, see https://github.com/egulias/EmailValidator/issues/181
];
}
......
......@@ -24,6 +24,7 @@
"doctrine/dbal": "^2.10",
"doctrine/instantiator": "^1.1",
"doctrine/lexer": "^1.0",
"egulias/email-validator": "^2.1",
"guzzlehttp/guzzle": "^6.3.0",
"nikic/php-parser": "^4.3",
"psr/container": "^1.0",
......
......@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Validation\Validator;
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
/**
......@@ -21,79 +22,51 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
*/
class EmailAddressValidatorTest extends UnitTestCase
{
/**
* Data provider with valid email addresses
*
* @return array
*/
public function validAddresses()
{
return [
['andreas.foerthner@netlogix.de'],
['user@localhost.localdomain'],
['info@guggenheim.museum'],
['just@test.invalid'],
['just+spam@test.de'],
];
}
/**
* @test
* @dataProvider validAddresses
* @param mixed $address
*/
public function emailAddressValidatorReturnsNoErrorsForAValidEmailAddress($address)
public function emailAddressValidatorReturnsNoErrorsForAValidEmailAddress(): void
{
/** @var \TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator|\PHPUnit\Framework\MockObject\MockObject $subject */
$subject = $this->getMockBuilder(\TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator::class)
->setMethods(['translateErrorMessage'])
$subject = $this->getMockBuilder(EmailAddressValidator::class)
->onlyMethods(['translateErrorMessage'])
->getMock();
self::assertFalse($subject->validate($address)->hasErrors());
self::assertFalse($subject->validate('valid.email@example.com')->hasErrors());
}
/**
* Data provider with invalid email addresses
*
* @return array
* @test
*/
public function invalidAddresses()
public function emailAddressValidatorReturnsFalseForAnInvalidEmailAddress(): void
{
return [
['andreas.foerthner@'],
['@typo3.org'],
['someone@typo3.'],
['local@192.168.2'],
['local@192.168.270.1'],
['foo@bar.com' . "\0"],
['foo@bar.org' . chr(10)],
['andreas@foerthner@example.com'],
['some@one.net ']
];
/** @var \TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator|\PHPUnit\Framework\MockObject\MockObject $subject */
$subject = $this->getMockBuilder(EmailAddressValidator::class)
->onlyMethods(['translateErrorMessage'])
->getMock();
self::assertTrue($subject->validate('@typo3.org')->hasErrors());
}
/**
* @test
* @dataProvider invalidAddresses
* @param mixed $address
*/
public function emailAddressValidatorReturnsFalseForAnInvalidEmailAddress($address)
public function emailAddressValidatorReturnsFalseForNonStringAddress(): void
{
/** @var \TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator|\PHPUnit\Framework\MockObject\MockObject $subject */
$subject = $this->getMockBuilder(\TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator::class)
->setMethods(['translateErrorMessage'])
$subject = $this->getMockBuilder(EmailAddressValidator::class)
->onlyMethods(['translateErrorMessage'])
->getMock();
self::assertTrue($subject->validate($address)->hasErrors());
self::assertTrue($subject->validate(123)->hasErrors());
}
/**
* @test
*/
public function emailValidatorCreatesTheCorrectErrorForAnInvalidEmailAddress()
public function emailValidatorCreatesTheCorrectErrorForAnInvalidEmailAddress(): void
{
/** @var \TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator|\PHPUnit\Framework\MockObject\MockObject $subject */
$subject = $this->getMockBuilder(\TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator::class)
->setMethods(['translateErrorMessage'])
$subject = $this->getMockBuilder(EmailAddressValidator::class)
->onlyMethods(['translateErrorMessage'])
->getMock();
self::assertEquals(1, count($subject->validate('notAValidMail@Address')->getErrors()));
self::assertCount(1, $subject->validate('someone@typo3.')->getErrors());
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment