Compare commits

..

66 Commits

Author SHA1 Message Date
David 5fa792ad18 Email addresses with single quote should not be valid (#268)
* Fixes issue for email addresses with single quote in domain part
* specify version in cli for older php versions

Co-authored-by: Eduardo Gulias Davis <eduardomgulias@gmail.com>
2020-10-31 21:37:35 +01:00
Eduardo Gulias Davis 68e418ec08 fix #217 (#266) 2020-09-26 17:48:38 +02:00
Phil Davis ef615f1d74 Minor typos (#265) 2020-09-26 16:40:57 +02:00
kishor 563d0cdde5 Issue-257: Fix PHP 7.3 compatibility issues. (#264) 2020-09-19 16:37:56 +02:00
zloKOMAtic c7ab280976 Update UnclosedComment.php (#263) 2020-09-19 16:37:41 +02:00
Eduardo Gulias Davis f46887bc48 Fix #260 with workaround (#261) 2020-09-06 15:44:32 +02:00
BenHarris 840d5603eb Reimplement DNSCheckValidation (#250)
* Reimplement DNSCheckValidation to utilise get_dns_record and extend support for detection of Null MX records (RFC7505) and reserved, mDNS and private namespaces (RFC2606 & RFC6762)

* Change valid email tests to use github.com instead of example.com as example.com now (correctly) fails the tests due to the Null MX case

* Tweak to pass CI psalm issue

* Encapsulate dns check code. Simplify local and reserved domain checks. Switch test domain

* Set PHPDoc return type

* Use strict comparisons
2020-08-08 23:28:19 +02:00
Remi Collet feba9587ec Fix paths for tests (#244) 2020-07-23 23:58:29 +02:00
Viktor Szépe 5568c52464 Fix links and formatting in README (#252) 2020-07-23 23:49:55 +02:00
Simon Schaufelberger cfa3d44471 Move files in typical src and tests folder (#242)
* Move files in typical src and tests folder

* Fix psalm error
2020-06-16 22:11:17 +02:00
Florent Morselli 2f38a470f9 Update .gitattributes (#237)
This PR removes information/debug/testing files from the distribution
2020-03-02 22:43:01 +01:00
Marcin Michalski ade6887fd9 Fix local part length check (#233) 2020-02-13 23:36:52 +01:00
Denis Ryabov 5065fafc8c Fix "Trying to access array offset on value of type null" (#228)
Added a check that `$previous` is not null (i.e. previous token exists)
2020-02-12 23:28:28 +01:00
SignpostMarv e834eea530 Enabling psalm on php >= 7.1, fixing #125, #168, #169, partially fixing #177 (#190)
* experimenting with psalm

* loading in symfony polyfill, partially resolves egulias/EmailValidator#177, but SpoofChecker does not seem to be polyfilled in symfony/polyfill-intl-*

* removing unused imports, fixes egulias/EmailValidator#168 and egulias/EmailValidator#169

* not actually used for object storage

* Egulias\Tests\EmailValidator\Validation\SpoofCheckValidationTest::testEmailWithSpoofsIsInvalid fails if extension is not installed

* satisfying psalm

* amending indentation

* disabling psalm on 5.6

* attempting to resolve psalm issue on 7.0 & 7.1

* dropping psalm from 7.0 matrix

* ext-intl removed as per egulias/EmailValidator#231

* updating baseline with psalm 3.8.3

* suppressing issue with Spoofchecker not being present, pending resolution of egulias/EmailValidator#231

* enable psalm on travis config for php 7.3
2020-01-20 22:40:59 +01:00
SignpostMarv c4b8d12921 Composer tweaks (#227)
* lockfile is not present in git repo

* correcting autoloader

* package is listed on packagist, repo listing in composer.json causes install issues on windows

Co-authored-by: Eduardo Gulias Davis <eduardomgulias@gmail.com>
2020-01-05 15:11:20 +01:00
Graham Campbell 3d09cd0b58 Update .gitignore (#225) 2020-01-05 14:48:40 +01:00
Graham Campbell 3e3b1e562b Removed unneeded repositories section from composer.json (#226) 2020-01-02 13:23:54 +01:00
SignpostMarv ed32faa118 using ::class instead of FQCN literal, fixes egulias/EmailValidator#125 (#230) 2020-01-02 13:02:44 +01:00
Graham Campbell 834593d590 Fixed tests (#223) 2019-12-30 09:14:25 +01:00
Graham Campbell a6255605af Important compser.json fix (#220) 2019-12-20 13:49:39 +01:00
felipeAraujo 950d0663dc making the docs more precise in order to some lint tools don't give error (#216) 2019-09-15 22:23:48 +02:00
Nicolas Grekas 92dd169c32 Advertise return annotations from doctrine/lexer (#209) 2019-08-13 19:33:27 +02:00
Nicolas Grekas a6c8d7101b Fix PHP 7.4 support (#203)
Adding support for deprecated PHP behaviours.
2019-07-19 22:52:08 +02:00
Eduardo Gulias Davis 128cc721d7 fix(201) - empty domain 2019-06-23 12:14:27 +02:00
Viktor Szépe c26463ff92 Fix class name and parameter types (#200)
* Fix class name and paramtere types

* HHVM is not a thing anymore
2019-05-17 00:02:54 +02:00
Victor Bocharsky a09af2de87 Minor: Use SVG format for some bad looking badges in README (#197) 2019-05-07 22:08:52 +02:00
Victor Bocharsky c802f106b3 Simplify installation command in README (#198)
Composer will automatically use BC constraint, like `^2.1.7`
2019-05-07 22:08:00 +02:00
Joël Harkes 709f21f927 Update MultipleValidationWithAnd.php (#179)
* Update MultipleValidationWithAnd.php

Fix all validations actually get executed when mode is ALLOW_ALL_ERRORS (1)

* this should validate not breake out.

* Actually fix the unit tests

* add real invalid email test
2018-12-04 23:38:24 +01:00
Eduardo Gulias Davis e68c26e065 Update supported RFCs (#186)
solves #186
2018-12-03 11:33:36 +01:00
Andrey Bolonin 8036f2bd05 Update .travis.yml (#182) 2018-11-03 22:52:10 +01:00
nikitos569 0043eab0c5 Update README.md (#180) 2018-10-19 00:14:56 +02:00
SAMUEL NELA 0578b32b30 Added autoload-dev (#178) 2018-09-25 22:47:26 +02:00
Tom Sommer 54859fabea INTL_IDNA_VARIANT_UTS46 is not supported on all systems (#173)
* INTL_IDNA_VARIANT_UTS46 is not supported on all systems

Fall back to INTL_IDNA_VARIANT_2003 if INTL_IDNA_VARIANT_UTS46 is not found.

* Fix syntax error

* Update DNSCheckValidation.php
2018-08-16 22:49:45 +02:00
Tom Sommer 135444003f Correctly check for intl extension in SpoofCheck (#174) 2018-08-13 14:43:59 +02:00
Tom Sommer 9dde0df1e3 DNSCheckValidation requires the Intl extension (#175)
* DNSCheckValidation requires the Intl extension as well

* Check for Intl extension in DNSCheckValidation
2018-08-13 14:43:24 +02:00
Lode Claassen 8790f59415 DNS check should convert unicode domains first (#166)
* fix checkdnsrr not handling unicode domains properly

* fix for php7.2
2018-04-10 12:11:19 +02:00
masterwto 114b663f20 Update SpoofCheckValidation.php (#163)
removing spaces
2018-01-16 18:13:22 +01:00
Gabriel Caruso a0e0fadfce Use dedicated PHPUnit assertion methods (#159) 2018-01-05 23:47:59 +01:00
Robbert 74c0f07434 Clarification fix (#162)
You implement an interface.
2018-01-05 23:40:27 +01:00
Rotzbua ad1a0e9773 Update README.md (#157)
changed links from http to https if supported
2017-11-23 01:15:37 +01:00
Rotzbua aeb6851388 Update Other.md (#158)
change links from http to https if supported
2017-11-23 01:15:20 +01:00
Remi Collet a23fcc1315 Improve test requirements and add versions to test matrix
* allow phpunit 5.7 and 6

* add PHP 7.1 and 7.2 to travis
2017-11-17 00:41:28 +01:00
Gabriel Caruso 1bec00a100 Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase (#155)
* Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase

* Use installed PHPUnit version not global
2017-11-16 00:40:40 +01:00
Santiago Castro 3abad65421 Fix broken Markdown headings (#149) 2017-04-18 23:06:35 +02:00
Dany Maillard 3fdfa261ad Remove duplicat < and > char value (#147) 2017-04-13 10:55:46 +02:00
Christian Flothmann 14ca3785c1 improve feedback on missing Intl extension (#143) 2017-03-02 11:09:01 +01:00
Eduardo Gulias Davis bc31baa11e Le shadow master (#139)
* Compatibility with RFC 2181

According to RFC2181, every DNS query needs to have a . at the end of
the name record you are requesting.

This will show that that name record is the root of the domain you are
asking
In some cases, when the . is missing, the hostname of the server the
code is running on will be appended to the name record when
checkdnsrr() is being executed and this will in turn return a false
positive when there is a wildcard DNS record for the root domain of the
hostname of the server.

* Test deprecation warnings removed
2017-01-30 23:07:36 +01:00
Simone c0c888ae10 removes trailing spaces (#136) 2017-01-25 22:45:20 +01:00
gjenjoying 181a2fc726 update readme (#135)
* update readme

use Egulias\EmailValidator\Validation\RFCValidation  is required.
2016-12-15 20:18:15 +01:00
Alf Eaton a1e85fa9fb Fix NoRFCWarningsValidation name in README (#134) 2016-11-10 20:04:43 +01:00
BradChesney79 7ff8caaae5 Update composer.json to use the right extension string (#127)
By using
"ext-intl": "*"
in the require block, the suggestion will be hidden if it is already available. ext/php-intl has few google search results and will confuse less knowledgeable users.
2016-08-16 00:24:12 +02:00
Eduardo Gulias Davis 5d56347ddf Typo 2016-08-07 23:26:30 +02:00
Eduardo Gulias Davis 80ad87acdd Update composer instructions 2016-08-07 23:25:34 +02:00
Eduardo Gulias Davis 30562b69fc Merge branch 'taz77-correct-readme-php-example-code-php-dependencies' 2016-08-07 23:18:59 +02:00
Eduardo Gulias Davis cbb8b9e152 PHPStorm help and composer suggestion 2016-08-07 23:16:06 +02:00
Eduardo Gulias Davis cf670406e5 Merge branch 'correct-readme-php-example-code-php-dependencies' of https://github.com/taz77/EmailValidator into taz77-correct-readme-php-example-code-php-dependencies 2016-08-07 23:11:36 +02:00
Issei.M 4d446bf971 NoRFCWarningsValidation now has an InvalidEmail instance if invalid email is passed (#126)
* NoRFCWarningsValidation now has an InvalidEmail instance if invalid email is passed

* fix mentioned point
2016-08-03 22:42:12 +02:00
Issei.M 4f0f9023f0 MultipleValidationWithAnd::getError() now returns NULL if no errors (#124)
* MultipleValidationWithAnd::getError() now returns NULL if no errors

* improve adding error logic

* reword something, add tips

* fix CS
remove wrong tips
2016-07-29 22:43:15 +02:00
Brady 9160e26b13 List and links
Turned requirements into an unordered list. Removed the PHPstorm logo as it doesn't exist and could not find one to replace it.
2016-07-19 06:49:04 +02:00
Brady eb036b7d1c Correct PHP example. Add a requirements section.
Corrected PHP example for missing semi-colons. Added a new requirements section that explains the need for composer and the need of the PHP Intl library to use the spoof validation.
2016-07-18 11:55:21 +02:00
Issei.M 19811e0082 MultipleValidatorWithAnd now can break out of loop when error occurs (#122)
* MultipleValidatorWithAnd now can break out of loop when error occurs

* improved logic

* updated doc

* bugfix
2016-07-12 15:22:10 +02:00
Michele Locati b366d54b0a Exclude some dirs and files from repository auto-generated ZIP archives (#113)
People that install this library via composer don't need the test stuff, they just need the library itself.
This PR removes the development directories and files from the repository auto-generated ZIP archives.
Since these development files/directories will still be available via `git clone`, Travis is happy, developers are happy, and so are end-users (they won't have unneeded stuff in production machines).
2016-07-03 23:53:17 +02:00
Issei.M 2bc46e23e5 fixed type missing and removed unused code (#117) 2016-06-25 00:47:43 +02:00
Issei.M 7701238433 fixed some problems in DNSCheckValidation (#116) 2016-06-25 00:43:29 +02:00
Issei.M afde3d3874 fixed DNSCheckValidation bug (#115) 2016-06-21 17:31:36 +02:00
Pedro Maia e9c46eed92 Fix typo (#112) 2016-05-19 00:20:10 +02:00
93 changed files with 1194 additions and 2136 deletions
+7
View File
@@ -0,0 +1,7 @@
/documentation export-ignore
/tests export-ignore
/.* export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
/psalm.baseline.xml export-ignore
/README.md export-ignore
+2
View File
@@ -1,2 +1,4 @@
.idea
composer.lock
report/
vendor/
+33 -22
View File
@@ -1,34 +1,45 @@
sudo: false
language: php
php:
- 5.5
- 5.6
- 7.0
- hhvm
env:
global:
- deps=no
matrix:
fast_finish: true
include:
- php: 5.5
- php: 5.5.9
dist: trusty
env: deps=low
- php: 5.5
dist: trusty
- php: 5.6
env: deps=high
env:
dist: xenial
- php: 7.0
dist: xenial
- php: 7.1
env:
- psalm=yes
dist: bionic
- php: 7.2
env:
- psalm=yes
dist: bionic
- php: 7.3
dist: bionic
env:
- psalm=yes
- php: 7.4
env:
- psalm=yes
dist: bionic
install:
- if [ "$deps" = "no" ]; then composer install; fi
- if [ "$deps" = "low" ]; then composer update --prefer-lowest; fi
- if [ "$deps" = "high" ]; then composer update; fi
- if [ "$deps" = "low" ]; then composer update --prefer-lowest; else composer install; fi
- if [ "$psalm" = "yes" ]; then composer require --dev vimeo/psalm:3.18.2; fi
before_script:
- mkdir -p build/logs
script:
- mkdir -p build/logs
- phpunit --coverage-clover build/logs/clover.xml
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml
- if [ "$psalm" = "yes" ]; then vendor/bin/psalm; fi
after_script:
- php vendor/bin/coveralls
- php vendor/bin/coveralls
@@ -1,73 +0,0 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
use Egulias\EmailValidator\Exception\NoDNSRecord;
class DNSCheckValidation implements EmailValidation
{
/**
* @var EmailParser
*/
private $parser;
/**
* @var array
*/
private $warnings = [];
/**
* @var InvalidEmail
*/
private $error;
public function isValid($email, EmailLexer $emailLexer)
{
// use the input to check DNS if we cannot extract something similar to a domain
$host = $email;
// Arguable pattern to extract the domain. Not aiming to validate the domain nor the email
$pattern = "/^[a-z'0-9]+([._-][a-z'0-9]+)*@([a-z0-9]+([._-][a-z0-9]+)+)+$/";
if (preg_match($pattern, $email, $result)) {
$host = $this->extractHost($result);
}
return $this->checkDNS($host);
}
public function getError()
{
return $this->error;
}
public function getWarnings()
{
return $this->warnings;
}
private function extractHost(array $result)
{
foreach ($result as $match) {
$onlyDomainPattern = "/^([a-z0-9]+([._-][a-z0-9]+))+$/";
if (preg_match($onlyDomainPattern, $match, $domainResult)) {
return $domainResult[0];
}
}
}
protected function checkDNS($host)
{
$Aresult = true;
$MXresult = checkdnsrr($host, 'MX');
if (!$MXresult) {
$this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
$Aresult = checkdnsrr($host, 'A');
if (!$Aresult) {
$this->error = new NoDNSRecord();
}
}
return $MXresult || $Aresult;
}
}
@@ -1,20 +0,0 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
interface EmailValidation
{
public function isValid($email, EmailLexer $emailLexer);
/**
* @return InvalidEmail
*/
public function getError();
/**
* @return array of Warning
*/
public function getWarnings();
}
@@ -1,47 +0,0 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Validation\Exception\EmptyValidationList;
class MultipleValidationWithAnd implements EmailValidation
{
private $validations = [];
private $warnings = [];
private $error;
public function __construct(array $validations)
{
if (count($validations) == 0) {
throw new EmptyValidationList();
}
$this->validations = $validations;
}
public function isValid($email, EmailLexer $emailLexer)
{
$result = true;
$errors = [];
foreach ($this->validations as $validation) {
$emailLexer->reset();
$result = $result && $validation->isValid($email, $emailLexer);
$this->warnings = array_merge($this->warnings, $validation->getWarnings());
$errors[] = $validation->getError();
}
$this->error = new MultipleErrors($errors);
return $result;
}
public function getError()
{
return $this->error;
}
public function getWarnings()
{
return $this->warnings;
}
}
@@ -1,13 +0,0 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
class NoRFCWarningsValidation extends RFCValidation
{
public function isValid($email, EmailLexer $emailLexer)
{
return parent::isValid($email, $emailLexer) && empty($this->getWarnings());
}
}
+45 -27
View File
@@ -1,18 +1,31 @@
#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
# EmailValidator
![Powered by PhpStorm](https://www.jetbrains.com/phpstorm/documentation/docs/logo_phpstorm.png)
##Installation##
[![Build Status](https://travis-ci.org/egulias/EmailValidator.svg?branch=master)](https://travis-ci.org/egulias/EmailValidator)
[![Coverage Status](https://coveralls.io/repos/egulias/EmailValidator/badge.svg?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)
## Supported RFCs ##
This library aims to support:
RFC 5321, 5322, 6530, 6531, 6532.
## Requirements ##
* [Composer](https://getcomposer.org) is required for installation
* [Spoofchecking](/src/Validation/SpoofCheckValidation.php) and [DNSCheckValidation](/src/Validation/DNSCheckValidation.php) validation requires that your PHP system has the [PHP Internationalization Libraries](https://php.net/manual/en/book.intl.php) (also known as PHP Intl)
## Installation ##
Run the command below to install via Composer
```shell
composer require egulias/email-validator "~2.0"
composer require egulias/email-validator
```
##Getting Started##
## Getting Started ##
`EmailValidator`requires you to decide which (or combination of them) validation/s strategy/ies you'd like to follow for each [validation](#available-validations).
A basic example with the RFC validation
@@ -20,22 +33,23 @@ A basic example with the RFC validation
<?php
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
$validator = new EmailValidator();
$validator->isValid("example@example.com", new RFCValidation()) //true
$validator->isValid("example@example.com", new RFCValidation()); //true
```
###Available validations###
### Available validations ###
1. [RFCValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/RFCValidation.php)
2. [NoWarningsRFCValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/NoRFCWarningsValidation.php)
3. [DNSCheckValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/DNSCheckValidation.php)
4. [SpoofCheckValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/SpoofCheckValidation.php)
5. [MultipleValidationsWithAnd](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/MultipleValidationWithAnd.php)
1. [RFCValidation](/src/Validation/RFCValidation.php)
2. [NoRFCWarningsValidation](/src/Validation/NoRFCWarningsValidation.php)
3. [DNSCheckValidation](/src/Validation/DNSCheckValidation.php)
4. [SpoofCheckValidation](/src/Validation/SpoofCheckValidation.php)
5. [MultipleValidationWithAnd](/src/Validation/MultipleValidationWithAnd.php)
6. [Your own validation](#how-to-extend)
`MultipleValidationsWithAnd`
`MultipleValidationWithAnd`
It is a validation that operates over other validations performing a logical and (&&) over the result of each validation.
@@ -43,29 +57,33 @@ It is a validation that operates over other validations performing a logical and
<?php
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\DNSCheckValidation;
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
use Egulias\EmailValidator\Validation\RFCValidation;
$validator = new EmailValidator();
$multipleValidations = new MultipleValidationsWithAnd([
$multipleValidations = new MultipleValidationWithAnd([
new RFCValidation(),
new DNSCheckValidation()
])
$validator->isValid("example@example.com", $multipleValidations) //true
]);
$validator->isValid("example@example.com", $multipleValidations); //true
```
###How to extend###
### How to extend ###
It's easy! You just need to extend [EmailValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/EmailValidation.php) and you can use your own validation.
It's easy! You just need to implement [EmailValidation](/src/Validation/EmailValidation.php) and you can use your own validation.
##Other Contributors##
## Other Contributors ##
(You can find current contributors [here](https://github.com/egulias/EmailValidator/graphs/contributors))
As this is a port from another library and work, here are other people related to the previous one:
* Ricard Clau [@ricardclau](http://github.com/ricardclau): Performance against PHP built-in filter_var
* Josepf Bielawski [@stloyd](http://github.com/stloyd): For its first re-work of Dominic's lib
* Dominic Sayers [@dominicsayers](http://github.com/dominicsayers): The original isemail function
* Ricard Clau [@ricardclau](https://github.com/ricardclau): Performance against PHP built-in filter_var
* Josepf Bielawski [@stloyd](https://github.com/stloyd): For its first re-work of Dominic's lib
* Dominic Sayers [@dominicsayers](https://github.com/dominicsayers): The original isemail function
## License ##
##License##
Released under the MIT License attached with this code.
@@ -1,39 +0,0 @@
<?php
namespace Egulias\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\NoDNSRecord;
use Egulias\EmailValidator\Validation\DNSCheckValidation;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
class DNSCheckValidationTest extends \PHPUnit_Framework_TestCase
{
public function testValidDNS()
{
$validation = new DNSCheckValidation();
$this->assertTrue($validation->isValid("example@example.com", new EmailLexer()));
}
public function testInvalidDNS()
{
$validation = new DNSCheckValidation();
$this->assertFalse($validation->isValid("example@invalid.example.com", new EmailLexer()));
}
public function testDNSWarnings()
{
$validation = new DNSCheckValidation();
$expectedWarnings = [NoDNSMXRecord::CODE => new NoDNSMXRecord()];
$validation->isValid("example@invalid.example.com", new EmailLexer());
$this->assertEquals($expectedWarnings, $validation->getWarnings());
}
public function testNoDNSError()
{
$validation = new DNSCheckValidation();
$expectedError = new NoDNSRecord();
$validation->isValid("example@invalid.example.com", new EmailLexer());
$this->assertEquals($expectedError, $validation->getError());
}
}
@@ -1,82 +0,0 @@
<?php
namespace Egulias\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\Exception\CommaInDomain;
use Egulias\EmailValidator\Exception\NoDomainPart;
use Egulias\EmailValidator\Validation\MultipleErrors;
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
use Egulias\EmailValidator\Warning\AddressLiteral;
use Egulias\EmailValidator\Warning\DomainLiteral;
class MultipleValidationWitAndTest extends \PHPUnit_Framework_TestCase
{
public function testUsesAndLogicalOperation()
{
$lexer = $this->getMock("Egulias\\EmailValidator\\EmailLexer");
$validationTrue = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validationTrue->expects($this->any())->method("isValid")->willReturn(true);
$validationTrue->expects($this->any())->method("getWarnings")->willReturn([]);
$validationFalse = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validationFalse->expects($this->any())->method("isValid")->willReturn(false);
$validationFalse->expects($this->any())->method("getWarnings")->willReturn([]);
$multipleValidation = new MultipleValidationWithAnd([$validationTrue, $validationFalse]);
$this->assertFalse($multipleValidation->isValid("exmpale@example.com", $lexer));
}
/**
* @expectedException Egulias\EmailValidator\Validation\Exception\EmptyValidationList
*/
public function testEmptyListIsNotAllowed()
{
new MultipleValidationWithAnd([]);
}
public function testAccumulatesWarnings()
{
$warnings1 = [
AddressLiteral::CODE => new AddressLiteral()
];
$warnings2 = [
DomainLiteral::CODE => new DomainLiteral()
];
$expectedResult = array_merge($warnings1, $warnings2);
$lexer = $this->getMock("Egulias\\EmailValidator\\EmailLexer");
$validation1 = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validation1->expects($this->any())->method("isValid")->willReturn(true);
$validation1->expects($this->once())->method("getWarnings")->willReturn($warnings1);
$validation2 = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validation2->expects($this->any())->method("isValid")->willReturn(false);
$validation2->expects($this->once())->method("getWarnings")->willReturn($warnings2);
$multipleValidation = new MultipleValidationWithAnd([$validation1, $validation2]);
$multipleValidation->isValid("example@example.com", $lexer);
$this->assertEquals($expectedResult, $multipleValidation->getWarnings());
}
public function testGathersAllTheErrors()
{
$error1 = new CommaInDomain();
$error2 = new NoDomainPart();
$expectedResult = new MultipleErrors([$error1, $error2]);
$lexer = $this->getMock("Egulias\\EmailValidator\\EmailLexer");
$validation1 = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validation1->expects($this->any())->method("isValid")->willReturn(true);
$validation1->expects($this->once())->method("getWarnings")->willReturn([]);
$validation1->expects($this->once())->method("getError")->willReturn($error1);
$validation2 = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validation2->expects($this->any())->method("isValid")->willReturn(false);
$validation2->expects($this->once())->method("getWarnings")->willReturn([]);
$validation2->expects($this->once())->method("getError")->willReturn($error2);
$multipleValidation = new MultipleValidationWithAnd([$validation1, $validation2]);
$multipleValidation->isValid("example@example.com", $lexer);
$this->assertEquals($expectedResult, $multipleValidation->getError());
}
}
@@ -1,23 +0,0 @@
<?php
namespace Egulias\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
class NoWarningsRFCValidationTest extends \PHPUnit_Framework_TestCase
{
public function testEmailWithWarningsIsInvalid()
{
$validation = new NoRFCWarningsValidation();
$this->assertFalse($validation->isValid('examp"l"e@example.com', new EmailLexer()));
}
public function testEmailWithoutWarningsIsValid()
{
$validation = new NoRFCWarningsValidation();
$this->assertTrue($validation->isValid('example@example.com', new EmailLexer()));
}
}
+18 -16
View File
@@ -2,7 +2,6 @@
"name": "egulias/email-validator",
"description": "A library for validating emails against several RFCs",
"homepage": "https://github.com/egulias/EmailValidator",
"type": "Library",
"keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"],
"license": "MIT",
"authors": [
@@ -10,27 +9,30 @@
],
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"dev-master": "2.1.x-dev"
}
},
"repositories": [
{
"type": "git",
"url": "https://github.com/dominicsayers/isemail"
}
],
"require": {
"php": ">= 5.5",
"doctrine/lexer": "^1.0.1"
"require": {
"php": ">=5.5",
"doctrine/lexer": "^1.0.1",
"symfony/polyfill-intl-idn": "^1.10"
},
"require-dev" : {
"satooshi/php-coveralls": "dev-master",
"phpunit/phpunit": "^4.8.0",
"dominicsayers/isemail": "dev-master"
"require-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"
},
"autoload": {
"psr-4": {
"Egulias\\EmailValidator\\": "EmailValidator"
"Egulias\\EmailValidator\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Egulias\\EmailValidator\\Tests\\": "tests"
}
}
}
Generated
-1661
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,23 +1,23 @@
Email length
------------
http://tools.ietf.org/html/rfc5321#section-4.1.2
https://tools.ietf.org/html/rfc5321#section-4.1.2
Forward-path = Path
Path = "<" [ A-d-l ":" ] Mailbox ">"
http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
http://tools.ietf.org/html/rfc1035#section-2.3.4
https://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
https://tools.ietf.org/html/rfc1035#section-2.3.4
DNS
---
http://tools.ietf.org/html/rfc5321#section-2.3.5
https://tools.ietf.org/html/rfc5321#section-2.3.5
Names that can
be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
in Section 5) are permitted, as are CNAME RRs whose targets can be
resolved, in turn, to MX or address RRs.
http://tools.ietf.org/html/rfc5321#section-5.1
https://tools.ietf.org/html/rfc5321#section-5.1
The lookup first attempts to locate an MX record associated with the
name. If a CNAME record is found, the resulting name is processed as
if it were the initial name. ... If an empty list of MXs is returned,
@@ -37,7 +37,7 @@ status to these addresses on the basis that they are more likely
to be typos than genuine addresses (unless we've already
established that the domain does have an MX record)
http://tools.ietf.org/html/rfc5321#section-2.3.5
https://tools.ietf.org/html/rfc5321#section-2.3.5
In the case
of a top-level domain used by itself in an email address, a single
string is used without any dots. This makes the requirement,
@@ -57,7 +57,7 @@ an ambiguity. The most authoritative statement on TLD formats that
the author can find is in a (rejected!) erratum to RFC 1123
submitted by John Klensin, the author of RFC 5321:
http://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
https://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
However, a valid host name can never have the dotted-decimal
form #.#.#.#, since this change does not permit the highest-level
component label to start with a digit even if it is not all-numeric.
@@ -66,4 +66,4 @@ Comments
--------
Comments at the start of the domain are deprecated in the text
Comments at the start of a subdomain are obs-domain
(http://tools.ietf.org/html/rfc5322#section-3.4.1)
(https://tools.ietf.org/html/rfc5322#section-3.4.1)
+4 -5
View File
@@ -8,19 +8,18 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="EmailValidator Test Suite">
<directory>./Tests/EmailValidator</directory>
<directory>./tests/EmailValidator</directory>
<exclude>./vendor/</exclude>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>./vendor</directory>
</blacklist>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="3.8.3@389af1bfc739bfdff3f9e3dc7bd6499aee51a831">
<file src="src/EmailLexer.php">
<DocblockTypeContradiction occurrences="1">
<code>self::$nullToken</code>
</DocblockTypeContradiction>
</file>
<file src="src/Parser/Parser.php">
<MissingReturnType occurrences="1">
<code>parse</code>
</MissingReturnType>
</file>
<file src="src/Validation/SpoofCheckValidation.php">
<UndefinedClass occurrences="2">
<code>Spoofchecker</code>
<code>Spoofchecker</code>
</UndefinedClass>
</file>
</files>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<psalm
requireVoidReturnType="false"
totallyTyped="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config ./vendor/vimeo/psalm/config.xsd"
errorBaseline="./psalm.baseline.xml"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
</issueHandlers>
</psalm>
@@ -13,6 +13,7 @@ class EmailLexer extends AbstractLexer
const S_BACKSLASH = 92;
const S_DOT = 46;
const S_DQUOTE = 34;
const S_SQUOTE = 39;
const S_OPENPARENTHESIS = 49;
const S_CLOSEPARENTHESIS = 261;
const S_OPENBRACKET = 262;
@@ -58,6 +59,7 @@ class EmailLexer extends AbstractLexer
'/' => self::S_SLASH,
',' => self::S_COMMA,
'.' => self::S_DOT,
"'" => self::S_SQUOTE,
'"' => self::S_DQUOTE,
'-' => self::S_HYPHEN,
'::' => self::S_DOUBLECOLON,
@@ -67,33 +69,79 @@ class EmailLexer extends AbstractLexer
"\n" => self::S_LF,
"\r\n" => self::CRLF,
'IPv6' => self::S_IPV6TAG,
'<' => self::S_LOWERTHAN,
'>' => self::S_GREATERTHAN,
'{' => self::S_OPENQBRACKET,
'}' => self::S_CLOSEQBRACKET,
'' => self::S_EMPTY,
'\0' => self::C_NUL,
);
/**
* @var bool
*/
protected $hasInvalidTokens = false;
protected $previous;
/**
* @var array
*
* @psalm-var array{value:string, type:null|int, position:int}|array<empty, empty>
*/
protected $previous = [];
/**
* The last matched/seen token.
*
* @var array
*
* @psalm-var array{value:string, type:null|int, position:int}
*/
public $token;
/**
* The next token in the input.
*
* @var array|null
*/
public $lookahead;
/**
* @psalm-var array{value:'', type:null, position:0}
*/
private static $nullToken = [
'value' => '',
'type' => null,
'position' => 0,
];
public function __construct()
{
$this->previous = $this->token = self::$nullToken;
$this->lookahead = null;
}
/**
* @return void
*/
public function reset()
{
$this->hasInvalidTokens = false;
parent::reset();
$this->previous = $this->token = self::$nullToken;
}
/**
* @return bool
*/
public function hasInvalidTokens()
{
return $this->hasInvalidTokens;
}
/**
* @param $type
* @param int $type
* @throws \UnexpectedValueException
* @return boolean
*
* @psalm-suppress InvalidScalarArgument
*/
public function find($type)
{
@@ -109,7 +157,7 @@ class EmailLexer extends AbstractLexer
/**
* getPrevious
*
* @return array token
* @return array
*/
public function getPrevious()
{
@@ -124,8 +172,10 @@ class EmailLexer extends AbstractLexer
public function moveNext()
{
$this->previous = $this->token;
$hasNext = parent::moveNext();
$this->token = $this->token ?: self::$nullToken;
return parent::moveNext();
return $hasNext;
}
/**
@@ -181,6 +231,11 @@ class EmailLexer extends AbstractLexer
return self::GENERIC;
}
/**
* @param string $value
*
* @return bool
*/
protected function isValid($value)
{
if (isset($this->charValue[$value])) {
@@ -191,7 +246,7 @@ class EmailLexer extends AbstractLexer
}
/**
* @param $value
* @param string $value
* @return bool
*/
protected function isNullType($value)
@@ -204,7 +259,7 @@ class EmailLexer extends AbstractLexer
}
/**
* @param $value
* @param string $value
* @return bool
*/
protected function isUTF8Invalid($value)
@@ -216,6 +271,9 @@ class EmailLexer extends AbstractLexer
return false;
}
/**
* @return string
*/
protected function getModifiers()
{
return 'iu';
@@ -17,11 +17,33 @@ class EmailParser
{
const EMAIL_MAX_LENGTH = 254;
protected $warnings;
/**
* @var array
*/
protected $warnings = [];
/**
* @var string
*/
protected $domainPart = '';
/**
* @var string
*/
protected $localPart = '';
/**
* @var EmailLexer
*/
protected $lexer;
/**
* @var LocalPart
*/
protected $localPartParser;
/**
* @var DomainPart
*/
protected $domainPartParser;
public function __construct(EmailLexer $lexer)
@@ -29,11 +51,10 @@ class EmailParser
$this->lexer = $lexer;
$this->localPartParser = new LocalPart($this->lexer);
$this->domainPartParser = new DomainPart($this->lexer);
$this->warnings = new \SplObjectStorage();
}
/**
* @param $str
* @param string $str
* @return array
*/
public function parse($str)
@@ -57,6 +78,9 @@ class EmailParser
return array('local' => $this->localPart, 'domain' => $this->domainPart);
}
/**
* @return Warning\Warning[]
*/
public function getWarnings()
{
$localPartWarnings = $this->localPartParser->getWarnings();
@@ -68,11 +92,17 @@ class EmailParser
return $this->warnings;
}
/**
* @return string
*/
public function getParsedDomainPart()
{
return $this->domainPart;
}
/**
* @param string $email
*/
protected function setParts($email)
{
$parts = explode('@', $email);
@@ -80,6 +110,9 @@ class EmailParser
$this->localPart = $parts[0];
}
/**
* @return bool
*/
protected function hasAtToken()
{
$this->lexer->moveNext();
@@ -11,24 +11,24 @@ class EmailValidator
* @var EmailLexer
*/
private $lexer;
/**
* @var array
*/
protected $warnings;
/**
* @var InvalidEmail
* @var Warning\Warning[]
*/
protected $warnings = [];
/**
* @var InvalidEmail|null
*/
protected $error;
public function __construct()
{
$this->lexer = new EmailLexer();
}
/**
* @param $email
* @param string $email
* @param EmailValidation $emailValidation
* @return bool
*/
@@ -37,7 +37,7 @@ class EmailValidator
$isValid = $emailValidation->isValid($email, $this->lexer);
$this->warnings = $emailValidation->getWarnings();
$this->error = $emailValidation->getError();
return $isValid;
}
@@ -58,7 +58,7 @@ class EmailValidator
}
/**
* @return string
* @return InvalidEmail|null
*/
public function getError()
{
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace Egulias\EmailValidator\Exception;
class DomainAcceptsNoMail extends InvalidEmail
{
const CODE = 154;
const REASON = 'Domain accepts no mail (Null MX, RFC7505)';
}
@@ -2,7 +2,7 @@
namespace Egulias\EmailValidator\Exception;
class ExpectedQPair extends InvalidEmail
class ExpectingQPair extends InvalidEmail
{
const CODE = 136;
const REASON = "Expecting QPAIR";
@@ -6,7 +6,7 @@ abstract class InvalidEmail extends \InvalidArgumentException
{
const REASON = "Invalid email";
const CODE = 0;
public function __construct()
{
parent::__construct(static::REASON, static::CODE);
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace Egulias\EmailValidator\Exception;
class LocalOrReservedDomain extends InvalidEmail
{
const CODE = 153;
const REASON = 'Local, mDNS or reserved domain (RFC2606, RFC6762)';
}
@@ -2,8 +2,6 @@
namespace Egulias\EmailValidator\Exception;
use Egulias\EmailValidator\Exception\InvalidEmail;
class NoDNSRecord extends InvalidEmail
{
const CODE = 5;
@@ -5,5 +5,5 @@ namespace Egulias\EmailValidator\Exception;
class UnclosedComment extends InvalidEmail
{
const CODE = 146;
const REASON = "No colosing comment token found";
const REASON = "No closing comment token found";
}
@@ -35,27 +35,17 @@ use Egulias\EmailValidator\Warning\TLD;
class DomainPart extends Parser
{
const DOMAIN_MAX_LENGTH = 254;
/**
* @var string
*/
protected $domainPart = '';
public function parse($domainPart)
{
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
throw new DotAtStart();
}
if ($this->lexer->token['type'] === EmailLexer::S_EMPTY) {
throw new NoDomainPart();
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
throw new DomainHyphened();
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
$this->parseDomainComments();
}
$this->performDomainStartChecks();
$domain = $this->doParseDomainPart();
@@ -77,11 +67,50 @@ class DomainPart extends Parser
$this->domainPart = $domain;
}
private function performDomainStartChecks()
{
$this->checkInvalidTokensAfterAT();
$this->checkEmptyDomain();
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
$this->parseDomainComments();
}
}
private function checkEmptyDomain()
{
$thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
($this->lexer->token['type'] === EmailLexer::S_SP &&
!$this->lexer->isNextToken(EmailLexer::GENERIC));
if ($thereIsNoDomain) {
throw new NoDomainPart();
}
}
private function checkInvalidTokensAfterAT()
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
throw new DotAtStart();
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
throw new DomainHyphened();
}
}
/**
* @return string
*/
public function getDomainPart()
{
return $this->domainPart;
}
/**
* @param string $addressLiteral
* @param int $maxGroups
*/
public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
{
$prev = $this->lexer->getPrevious();
@@ -125,6 +154,9 @@ class DomainPart extends Parser
}
}
/**
* @return string
*/
protected function doParseDomainPart()
{
$domain = '';
@@ -166,12 +198,15 @@ class DomainPart extends Parser
$domain .= $this->lexer->token['value'];
$this->lexer->moveNext();
} while ($this->lexer->token);
if ($this->lexer->token['type'] === EmailLexer::S_SP) {
throw new CharNotAllowed();
}
} while (null !== $this->lexer->token['type']);
return $domain;
}
private function checkNotAllowedChars($token)
private function checkNotAllowedChars(array $token)
{
$notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
if (isset($notAllowed[$token['type']])) {
@@ -179,6 +214,9 @@ class DomainPart extends Parser
}
}
/**
* @return string|false
*/
protected function parseDomainLiteral()
{
if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
@@ -195,6 +233,9 @@ class DomainPart extends Parser
return $this->doParseDomainLiteral();
}
/**
* @return string|false
*/
protected function doParseDomainLiteral()
{
$IPv6TAG = false;
@@ -262,6 +303,11 @@ class DomainPart extends Parser
return $addressLiteral;
}
/**
* @param string $addressLiteral
*
* @return string|false
*/
protected function checkIPV4Tag($addressLiteral)
{
$matchesIP = array();
@@ -279,16 +325,17 @@ class DomainPart extends Parser
return false;
}
// Convert IPv4 part to IPv6 format for further testing
$addressLiteral = substr($addressLiteral, 0, $index) . '0:0';
$addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0';
}
return $addressLiteral;
}
protected function checkDomainPartExceptions($prev)
protected function checkDomainPartExceptions(array $prev)
{
$invalidDomainTokens = array(
EmailLexer::S_DQUOTE => true,
EmailLexer::S_SQUOTE => true,
EmailLexer::S_SEMICOLON => true,
EmailLexer::S_GREATERTHAN => true,
EmailLexer::S_LOWERTHAN => true,
@@ -320,6 +367,9 @@ class DomainPart extends Parser
}
}
/**
* @return bool
*/
protected function hasBrackets()
{
if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
@@ -335,7 +385,7 @@ class DomainPart extends Parser
return true;
}
protected function checkLabelLength($prev)
protected function checkLabelLength(array $prev)
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$prev['type'] === EmailLexer::GENERIC &&
@@ -5,7 +5,6 @@ namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\Exception\DotAtEnd;
use Egulias\EmailValidator\Exception\DotAtStart;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Exception\ExpectingAT;
use Egulias\EmailValidator\Exception\ExpectingATEXT;
use Egulias\EmailValidator\Exception\UnclosedQuotedString;
@@ -20,9 +19,10 @@ class LocalPart extends Parser
$parseDQuote = true;
$closingQuote = false;
$openedParenthesis = 0;
$totalLength = 0;
while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) {
if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) {
while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) {
if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) {
throw new DotAtStart();
}
@@ -35,12 +35,13 @@ class LocalPart extends Parser
$this->parseComments();
$openedParenthesis += $this->getOpenedParenthesis();
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
if ($openedParenthesis === 0) {
throw new UnopenedComment();
} else {
$openedParenthesis--;
}
$openedParenthesis--;
}
$this->checkConsecutiveDots();
@@ -58,15 +59,18 @@ class LocalPart extends Parser
$this->parseFWS();
}
$totalLength += strlen($this->lexer->token['value']);
$this->lexer->moveNext();
}
$prev = $this->lexer->getPrevious();
if (strlen($prev['value']) > LocalTooLong::LOCAL_PART_LENGTH) {
if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) {
$this->warnings[LocalTooLong::CODE] = new LocalTooLong();
}
}
/**
* @return bool
*/
protected function parseDoubleQuote()
{
$parseAgain = true;
@@ -86,7 +90,7 @@ class LocalPart extends Parser
$this->lexer->moveNext();
while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && $this->lexer->token) {
while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && null !== $this->lexer->token['type']) {
$parseAgain = false;
if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) {
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
@@ -118,7 +122,10 @@ class LocalPart extends Parser
return $parseAgain;
}
protected function isInvalidToken($token, $closingQuote)
/**
* @param bool $closingQuote
*/
protected function isInvalidToken(array $token, $closingQuote)
{
$forbidden = array(
EmailLexer::S_COMMA,
@@ -8,7 +8,7 @@ use Egulias\EmailValidator\Exception\ConsecutiveDot;
use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
use Egulias\EmailValidator\Exception\CRLFX2;
use Egulias\EmailValidator\Exception\CRNoLF;
use Egulias\EmailValidator\Exception\ExpectedQPair;
use Egulias\EmailValidator\Exception\ExpectingQPair;
use Egulias\EmailValidator\Exception\ExpectingATEXT;
use Egulias\EmailValidator\Exception\ExpectingCTEXT;
use Egulias\EmailValidator\Exception\UnclosedComment;
@@ -21,8 +21,19 @@ use Egulias\EmailValidator\Warning\QuotedString;
abstract class Parser
{
/**
* @var array
*/
protected $warnings = [];
/**
* @var EmailLexer
*/
protected $lexer;
/**
* @var int
*/
protected $openedParenthesis = 0;
public function __construct(EmailLexer $lexer)
@@ -30,11 +41,17 @@ abstract class Parser
$this->lexer = $lexer;
}
/**
* @return \Egulias\EmailValidator\Warning\Warning[]
*/
public function getWarnings()
{
return $this->warnings;
}
/**
* @param string $str
*/
abstract public function parse($str);
/** @return int */
@@ -50,7 +67,7 @@ abstract class Parser
{
if (!($this->lexer->token['type'] === EmailLexer::INVALID
|| $this->lexer->token['type'] === EmailLexer::C_DEL)) {
throw new ExpectedQPair();
throw new ExpectingQPair();
}
$this->warnings[QuotedPart::CODE] =
@@ -80,6 +97,9 @@ abstract class Parser
}
}
/**
* @return bool
*/
protected function isUnclosedComment()
{
try {
@@ -122,6 +142,9 @@ abstract class Parser
}
}
/**
* @return bool
*/
protected function isFWS()
{
if ($this->escaped()) {
@@ -140,11 +163,14 @@ abstract class Parser
return false;
}
/**
* @return bool
*/
protected function escaped()
{
$previous = $this->lexer->getPrevious();
if ($previous['type'] === EmailLexer::S_BACKSLASH
if ($previous && $previous['type'] === EmailLexer::S_BACKSLASH
&&
$this->lexer->token['type'] !== EmailLexer::GENERIC
) {
@@ -154,6 +180,9 @@ abstract class Parser
return false;
}
/**
* @return bool
*/
protected function warnEscaping()
{
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
@@ -174,6 +203,11 @@ abstract class Parser
}
/**
* @param bool $hasClosingQuote
*
* @return bool
*/
protected function checkDQUOTE($hasClosingQuote)
{
if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) {
+166
View File
@@ -0,0 +1,166 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Exception\LocalOrReservedDomain;
use Egulias\EmailValidator\Exception\DomainAcceptsNoMail;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
use Egulias\EmailValidator\Exception\NoDNSRecord;
class DNSCheckValidation implements EmailValidation
{
/**
* @var array
*/
private $warnings = [];
/**
* @var InvalidEmail|null
*/
private $error;
/**
* @var array
*/
private $mxRecords = [];
public function __construct()
{
if (!function_exists('idn_to_ascii')) {
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
}
}
public function isValid($email, EmailLexer $emailLexer)
{
// use the input to check DNS if we cannot extract something similar to a domain
$host = $email;
// Arguable pattern to extract the domain. Not aiming to validate the domain nor the email
if (false !== $lastAtPos = strrpos($email, '@')) {
$host = substr($email, $lastAtPos + 1);
}
// Get the domain parts
$hostParts = explode('.', $host);
// Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
// mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
$reservedTopLevelDnsNames = [
// Reserved Top Level DNS Names
'test',
'example',
'invalid',
'localhost',
// mDNS
'local',
// Private DNS Namespaces
'intranet',
'internal',
'private',
'corp',
'home',
'lan',
];
$isLocalDomain = count($hostParts) <= 1;
$isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], $reservedTopLevelDnsNames, true);
// Exclude reserved top level DNS names
if ($isLocalDomain || $isReservedTopLevel) {
$this->error = new LocalOrReservedDomain();
return false;
}
return $this->checkDns($host);
}
public function getError()
{
return $this->error;
}
public function getWarnings()
{
return $this->warnings;
}
/**
* @param string $host
*
* @return bool
*/
protected function checkDns($host)
{
$variant = INTL_IDNA_VARIANT_UTS46;
$host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.';
return $this->validateDnsRecords($host);
}
/**
* Validate the DNS records for given host.
*
* @param string $host A set of DNS records in the format returned by dns_get_record.
*
* @return bool True on success.
*/
private function validateDnsRecords($host)
{
// Get all MX, A and AAAA DNS records for host
// Using @ as workaround to fix https://bugs.php.net/bug.php?id=73149
$dnsRecords = @dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA);
// No MX, A or AAAA DNS records
if (empty($dnsRecords)) {
$this->error = new NoDNSRecord();
return false;
}
// For each DNS record
foreach ($dnsRecords as $dnsRecord) {
if (!$this->validateMXRecord($dnsRecord)) {
return false;
}
}
// No MX records (fallback to A or AAAA records)
if (empty($this->mxRecords)) {
$this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
}
return true;
}
/**
* Validate an MX record
*
* @param array $dnsRecord Given DNS record.
*
* @return bool True if valid.
*/
private function validateMxRecord($dnsRecord)
{
if ($dnsRecord['type'] !== 'MX') {
return true;
}
// "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505)
if (empty($dnsRecord['target']) || $dnsRecord['target'] === '.') {
$this->error = new DomainAcceptsNoMail();
return false;
}
$this->mxRecords[] = $dnsRecord;
return true;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Warning\Warning;
interface EmailValidation
{
/**
* Returns true if the given email is valid.
*
* @param string $email The email you want to validate.
* @param EmailLexer $emailLexer The email lexer.
*
* @return bool
*/
public function isValid($email, EmailLexer $emailLexer);
/**
* Returns the validation error.
*
* @return InvalidEmail|null
*/
public function getError();
/**
* Returns the validation warnings.
*
* @return Warning[]
*/
public function getWarnings();
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Egulias\EmailValidator\Validation\Error;
use Egulias\EmailValidator\Exception\InvalidEmail;
class RFCWarnings extends InvalidEmail
{
const CODE = 997;
const REASON = 'Warnings were found.';
}
@@ -6,6 +6,9 @@ use Exception;
class EmptyValidationList extends \InvalidArgumentException
{
/**
* @param int $code
*/
public function __construct($code = 0, Exception $previous = null)
{
parent::__construct("Empty validation list is not allowed", $code, $previous);
@@ -9,16 +9,22 @@ class MultipleErrors extends InvalidEmail
const CODE = 999;
const REASON = "Accumulated errors for multiple validations";
/**
* @var array
* @var InvalidEmail[]
*/
private $errors = [];
/**
* @param InvalidEmail[] $errors
*/
public function __construct(array $errors)
{
$this->errors = $errors;
parent::__construct();
}
/**
* @return InvalidEmail[]
*/
public function getErrors()
{
return $this->errors;
@@ -0,0 +1,124 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Validation\Exception\EmptyValidationList;
class MultipleValidationWithAnd implements EmailValidation
{
/**
* If one of validations gets failure skips all succeeding validation.
* This means MultipleErrors will only contain a single error which first found.
*/
const STOP_ON_ERROR = 0;
/**
* All of validations will be invoked even if one of them got failure.
* So MultipleErrors will contain all causes.
*/
const ALLOW_ALL_ERRORS = 1;
/**
* @var EmailValidation[]
*/
private $validations = [];
/**
* @var array
*/
private $warnings = [];
/**
* @var MultipleErrors|null
*/
private $error;
/**
* @var int
*/
private $mode;
/**
* @param EmailValidation[] $validations The validations.
* @param int $mode The validation mode (one of the constants).
*/
public function __construct(array $validations, $mode = self::ALLOW_ALL_ERRORS)
{
if (count($validations) == 0) {
throw new EmptyValidationList();
}
$this->validations = $validations;
$this->mode = $mode;
}
/**
* {@inheritdoc}
*/
public function isValid($email, EmailLexer $emailLexer)
{
$result = true;
$errors = [];
foreach ($this->validations as $validation) {
$emailLexer->reset();
$validationResult = $validation->isValid($email, $emailLexer);
$result = $result && $validationResult;
$this->warnings = array_merge($this->warnings, $validation->getWarnings());
$errors = $this->addNewError($validation->getError(), $errors);
if ($this->shouldStop($result)) {
break;
}
}
if (!empty($errors)) {
$this->error = new MultipleErrors($errors);
}
return $result;
}
/**
* @param \Egulias\EmailValidator\Exception\InvalidEmail|null $possibleError
* @param \Egulias\EmailValidator\Exception\InvalidEmail[] $errors
*
* @return \Egulias\EmailValidator\Exception\InvalidEmail[]
*/
private function addNewError($possibleError, array $errors)
{
if (null !== $possibleError) {
$errors[] = $possibleError;
}
return $errors;
}
/**
* @param bool $result
*
* @return bool
*/
private function shouldStop($result)
{
return !$result && $this->mode === self::STOP_ON_ERROR;
}
/**
* Returns the validation errors.
*
* @return MultipleErrors|null
*/
public function getError()
{
return $this->error;
}
/**
* {@inheritdoc}
*/
public function getWarnings()
{
return $this->warnings;
}
}
@@ -0,0 +1,41 @@
<?php
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Validation\Error\RFCWarnings;
class NoRFCWarningsValidation extends RFCValidation
{
/**
* @var InvalidEmail|null
*/
private $error;
/**
* {@inheritdoc}
*/
public function isValid($email, EmailLexer $emailLexer)
{
if (!parent::isValid($email, $emailLexer)) {
return false;
}
if (empty($this->getWarnings())) {
return true;
}
$this->error = new RFCWarnings();
return false;
}
/**
* {@inheritdoc}
*/
public function getError()
{
return $this->error ?: parent::getError();
}
}
@@ -9,7 +9,7 @@ use Egulias\EmailValidator\Exception\InvalidEmail;
class RFCValidation implements EmailValidation
{
/**
* @var EmailParser
* @var EmailParser|null
*/
private $parser;
@@ -19,10 +19,10 @@ class RFCValidation implements EmailValidation
private $warnings = [];
/**
* @var InvalidEmail
* @var InvalidEmail|null
*/
private $error;
public function isValid($email, EmailLexer $emailLexer)
{
$this->parser = new EmailParser($emailLexer);
@@ -32,7 +32,7 @@ class RFCValidation implements EmailValidation
$this->error = $invalid;
return false;
}
$this->warnings = $this->parser->getWarnings();
return true;
}
@@ -3,28 +3,42 @@
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Validation\Error\SpoofEmail;
use \Spoofchecker;
class SpoofCheckValidation implements EmailValidation
{
/**
* @var InvalidEmail
* @var InvalidEmail|null
*/
private $error;
public function __construct()
{
if (!extension_loaded('intl')) {
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
}
}
/**
* @psalm-suppress InvalidArgument
*/
public function isValid($email, EmailLexer $emailLexer)
{
$checker = new Spoofchecker();
$checker->setChecks(Spoofchecker::SINGLE_SCRIPT);
if ($checker->isSuspicious($email)) {
$this->error = new SpoofEmail();
}
return $this->error === null;
}
/**
* @return InvalidEmail|null
*/
public function getError()
{
return $this->error;
@@ -6,6 +6,10 @@ class QuotedPart extends Warning
{
const CODE = 36;
/**
* @param scalar $prevToken
* @param scalar $postToken
*/
public function __construct($prevToken, $postToken)
{
$this->message = "Deprecated Quoted String found between $prevToken and $postToken";
@@ -6,6 +6,10 @@ class QuotedString extends Warning
{
const CODE = 11;
/**
* @param scalar $prevToken
* @param scalar $postToken
*/
public function __construct($prevToken, $postToken)
{
$this->message = "Quoted String found between $prevToken and $postToken";
@@ -5,24 +5,41 @@ namespace Egulias\EmailValidator\Warning;
abstract class Warning
{
const CODE = 0;
protected $message;
protected $rfcNumber;
/**
* @var string
*/
protected $message = '';
/**
* @var int
*/
protected $rfcNumber = 0;
/**
* @return string
*/
public function message()
{
return $this->message;
}
/**
* @return int
*/
public function code()
{
return self::CODE;
}
/**
* @return int
*/
public function RFCNumber()
{
return $this->rfcNumber;
}
public function __toString()
{
return $this->message() . " rfc: " . $this->rfcNumber . "interal code: " . static::CODE;
@@ -1,10 +1,11 @@
<?php
namespace Egulias\Tests\EmailValidator;
namespace Egulias\EmailValidator\Tests\EmailValidator;
use Egulias\EmailValidator\EmailLexer;
use PHPUnit\Framework\TestCase;
class EmailLexerTests extends \PHPUnit_Framework_TestCase
class EmailLexerTests extends TestCase
{
public function testLexerExtendsLib()
@@ -137,6 +138,7 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
array(":", EmailLexer::S_COLON),
array(".", EmailLexer::S_DOT),
array("\"", EmailLexer::S_DQUOTE),
array("'", EmailLexer::S_SQUOTE),
array("-", EmailLexer::S_HYPHEN),
array("\\", EmailLexer::S_BACKSLASH),
array("/", EmailLexer::S_SLASH),
@@ -1,27 +1,29 @@
<?php
namespace Egulias\Tests\EmailValidator;
namespace Egulias\EmailValidator\Tests\EmailValidator;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\EmailValidation;
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
use PHPUnit\Framework\TestCase;
class EmailValidatorTest extends \PHPUnit_Framework_TestCase
class EmailValidatorTest extends TestCase
{
public function testValidationIsUsed()
{
$validator = new EmailValidator();
$validation = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validation = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation->expects($this->once())->method("isValid")->willReturn(true);
$validation->expects($this->once())->method("getWarnings")->willReturn([]);
$validation->expects($this->once())->method("getError")->willReturn(null);
$this->assertTrue($validator->isValid("example@example.com", $validation));
}
public function testMultipleValidation()
{
$validator = new EmailValidator();
$validation = $this->getMock("Egulias\\EmailValidator\\Validation\\EmailValidation");
$validation = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation->expects($this->once())->method("isValid")->willReturn(true);
$validation->expects($this->once())->method("getWarnings")->willReturn([]);
$validation->expects($this->once())->method("getError")->willReturn(null);
@@ -0,0 +1,109 @@
<?php
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\NoDNSRecord;
use Egulias\EmailValidator\Exception\LocalOrReservedDomain;
use Egulias\EmailValidator\Exception\DomainAcceptsNoMail;
use Egulias\EmailValidator\Validation\DNSCheckValidation;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
use PHPUnit\Framework\TestCase;
class DNSCheckValidationTest extends TestCase
{
public function validEmailsProvider()
{
return [
// dot-atom
['Abc@ietf.org'],
['ABC@ietf.org'],
['Abc.123@ietf.org'],
['user+mailbox/department=shipping@ietf.org'],
['!#$%&\'*+-/=?^_`.{|}~@ietf.org'],
// quoted string
['"Abc@def"@ietf.org'],
['"Fred\ Bloggs"@ietf.org'],
['"Joe.\\Blow"@ietf.org'],
// unicode
['ñandu.cl'],
];
}
public function localOrReservedEmailsProvider()
{
return [
// Reserved Top Level DNS Names
['test'],
['example'],
['invalid'],
['localhost'],
// mDNS
['local'],
// Private DNS Namespaces
['intranet'],
['internal'],
['private'],
['corp'],
['home'],
['lan'],
];
}
/**
* @dataProvider validEmailsProvider
*/
public function testValidDNS($validEmail)
{
$validation = new DNSCheckValidation();
$this->assertTrue($validation->isValid($validEmail, new EmailLexer()));
}
public function testInvalidDNS()
{
$validation = new DNSCheckValidation();
$this->assertFalse($validation->isValid("example@invalid.example.com", new EmailLexer()));
}
/**
* @dataProvider localOrReservedEmailsProvider
*/
public function testLocalOrReservedDomainError($localOrReservedEmails)
{
$validation = new DNSCheckValidation();
$expectedError = new LocalOrReservedDomain();
$validation->isValid($localOrReservedEmails, new EmailLexer());
$this->assertEquals($expectedError, $validation->getError());
}
public function testDomainAcceptsNoMailError()
{
$validation = new DNSCheckValidation();
$expectedError = new DomainAcceptsNoMail();
$isValidResult = $validation->isValid("nullmx@example.com", new EmailLexer());
$this->assertEquals($expectedError, $validation->getError());
$this->assertFalse($isValidResult);
}
/*
public function testDNSWarnings()
{
$validation = new DNSCheckValidation();
$expectedWarnings = [NoDNSMXRecord::CODE => new NoDNSMXRecord()];
$validation->isValid("example@invalid.example.com", new EmailLexer());
$this->assertEquals($expectedWarnings, $validation->getWarnings());
}
*/
public function testNoDNSError()
{
$validation = new DNSCheckValidation();
$expectedError = new NoDNSRecord();
$validation->isValid("example@invalid.example.com", new EmailLexer());
$this->assertEquals($expectedError, $validation->getError());
}
}
@@ -1,14 +1,14 @@
<?php
namespace Egulias\Tests\EmailValidator\Validation;
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\DNSCheckValidation;
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
use Egulias\EmailValidator\Validation\RFCValidation;
use PHPUnit\Framework\TestCase;
class IsEmailFunctionTests extends \PHPUnit_Framework_TestCase
class IsEmailFunctionTests extends TestCase
{
/**
* @dataProvider isEmailTestSuite
@@ -27,9 +27,9 @@ class IsEmailFunctionTests extends \PHPUnit_Framework_TestCase
public function isEmailTestSuite()
{
$testSuite = dirname(__FILE__) . '/../../resources/is_email_tests.xml';
$document = new \DOMDocument();
$document->load($testSuite);
$testSuite = __DIR__ . '/../../resources/is_email_tests.xml';
$document = new \DOMDocument();
$document->load($testSuite);
$elements = $document->getElementsByTagName('test');
$tests = [];
@@ -40,5 +40,4 @@ class IsEmailFunctionTests extends \PHPUnit_Framework_TestCase
return $tests;
}
}
@@ -0,0 +1,159 @@
<?php
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\CommaInDomain;
use Egulias\EmailValidator\Exception\NoDomainPart;
use Egulias\EmailValidator\Validation\EmailValidation;
use Egulias\EmailValidator\Validation\MultipleErrors;
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
use Egulias\EmailValidator\Validation\RFCValidation;
use Egulias\EmailValidator\Warning\AddressLiteral;
use Egulias\EmailValidator\Warning\DomainLiteral;
use PHPUnit\Framework\TestCase;
class MultipleValidationWithAndTest extends TestCase
{
public function testUsesAndLogicalOperation()
{
$lexer = new EmailLexer();
$validationTrue = $this->getMockBuilder(EmailValidation::class)->getMock();
$validationTrue->expects($this->any())->method("isValid")->willReturn(true);
$validationTrue->expects($this->any())->method("getWarnings")->willReturn([]);
$validationFalse = $this->getMockBuilder(EmailValidation::class)->getMock();
$validationFalse->expects($this->any())->method("isValid")->willReturn(false);
$validationFalse->expects($this->any())->method("getWarnings")->willReturn([]);
$multipleValidation = new MultipleValidationWithAnd([$validationTrue, $validationFalse]);
$this->assertFalse($multipleValidation->isValid("exmpale@example.com", $lexer));
}
/**
* @expectedException \Egulias\EmailValidator\Validation\Exception\EmptyValidationList
*/
public function testEmptyListIsNotAllowed()
{
new MultipleValidationWithAnd([]);
}
public function testValidationIsValid()
{
$lexer = new EmailLexer();
$validation = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation->expects($this->any())->method("isValid")->willReturn(true);
$validation->expects($this->once())->method("getWarnings")->willReturn([]);
$multipleValidation = new MultipleValidationWithAnd([$validation]);
$this->assertTrue($multipleValidation->isValid("example@example.com", $lexer));
$this->assertNull($multipleValidation->getError());
}
public function testAccumulatesWarnings()
{
$warnings1 = [
AddressLiteral::CODE => new AddressLiteral()
];
$warnings2 = [
DomainLiteral::CODE => new DomainLiteral()
];
$expectedResult = array_merge($warnings1, $warnings2);
$lexer = new EmailLexer();
$validation1 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation1->expects($this->any())->method("isValid")->willReturn(true);
$validation1->expects($this->once())->method("getWarnings")->willReturn($warnings1);
$validation2 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation2->expects($this->any())->method("isValid")->willReturn(false);
$validation2->expects($this->once())->method("getWarnings")->willReturn($warnings2);
$multipleValidation = new MultipleValidationWithAnd([$validation1, $validation2]);
$multipleValidation->isValid("example@example.com", $lexer);
$this->assertEquals($expectedResult, $multipleValidation->getWarnings());
}
public function testGathersAllTheErrors()
{
$error1 = new CommaInDomain();
$error2 = new NoDomainPart();
$expectedResult = new MultipleErrors([$error1, $error2]);
$lexer = new EmailLexer();
$validation1 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation1->expects($this->once())->method("isValid")->willReturn(false);
$validation1->expects($this->once())->method("getWarnings")->willReturn([]);
$validation1->expects($this->once())->method("getError")->willReturn($error1);
$validation2 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation2->expects($this->once())->method("isValid")->willReturn(false);
$validation2->expects($this->once())->method("getWarnings")->willReturn([]);
$validation2->expects($this->once())->method("getError")->willReturn($error2);
$multipleValidation = new MultipleValidationWithAnd([$validation1, $validation2]);
$multipleValidation->isValid("example@example.com", $lexer);
$this->assertEquals($expectedResult, $multipleValidation->getError());
}
public function testStopsAfterFirstError()
{
$error1 = new CommaInDomain();
$error2 = new NoDomainPart();
$expectedResult = new MultipleErrors([$error1]);
$lexer = new EmailLexer();
$validation1 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation1->expects($this->any())->method("isValid")->willReturn(false);
$validation1->expects($this->once())->method("getWarnings")->willReturn([]);
$validation1->expects($this->once())->method("getError")->willReturn($error1);
$validation2 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation2->expects($this->any())->method("isValid")->willReturn(false);
$validation2->expects($this->never())->method("getWarnings")->willReturn([]);
$validation2->expects($this->never())->method("getError")->willReturn($error2);
$multipleValidation = new MultipleValidationWithAnd([$validation1, $validation2], MultipleValidationWithAnd::STOP_ON_ERROR);
$multipleValidation->isValid("example@example.com", $lexer);
$this->assertEquals($expectedResult, $multipleValidation->getError());
}
public function testBreakOutOfLoopWhenError()
{
$error = new CommaInDomain();
$expectedResult = new MultipleErrors([$error]);
$lexer = new EmailLexer();
$validation1 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation1->expects($this->any())->method("isValid")->willReturn(false);
$validation1->expects($this->once())->method("getWarnings")->willReturn([]);
$validation1->expects($this->once())->method("getError")->willReturn($error);
$validation2 = $this->getMockBuilder(EmailValidation::class)->getMock();
$validation2->expects($this->never())->method("isValid");
$validation2->expects($this->never())->method("getWarnings");
$validation2->expects($this->never())->method("getError");
$multipleValidation = new MultipleValidationWithAnd([$validation1, $validation2], MultipleValidationWithAnd::STOP_ON_ERROR);
$multipleValidation->isValid("example@example.com", $lexer);
$this->assertEquals($expectedResult, $multipleValidation->getError());
}
public function testBreakoutOnInvalidEmail()
{
$lexer = new EmailLexer();
$validationNotCalled = $this->getMockBuilder(EmailValidation::class)->getMock();
$validationNotCalled->expects($this->never())->method("isValid");
$validationNotCalled->expects($this->never())->method("getWarnings");
$validationNotCalled->expects($this->never())->method("getError");
$multipleValidation = new MultipleValidationWithAnd([new RFCValidation(), $validationNotCalled], MultipleValidationWithAnd::STOP_ON_ERROR);
$this->assertFalse($multipleValidation->isValid("invalid-email", $lexer));
}
}
@@ -0,0 +1,36 @@
<?php
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\NoDomainPart;
use Egulias\EmailValidator\Validation\Error\RFCWarnings;
use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
use PHPUnit\Framework\TestCase;
class NoRFCWarningsValidationTest extends TestCase
{
public function testInvalidEmailIsInvalid()
{
$validation = new NoRFCWarningsValidation();
$this->assertFalse($validation->isValid('non-email-string', new EmailLexer()));
$this->assertInstanceOf(NoDomainPart::class, $validation->getError());
}
public function testEmailWithWarningsIsInvalid()
{
$validation = new NoRFCWarningsValidation();
$this->assertFalse($validation->isValid(str_repeat('x', 254).'@example.com', new EmailLexer())); // too long email
$this->assertInstanceOf(RFCWarnings::class, $validation->getError());
}
public function testEmailWithoutWarningsIsValid()
{
$validation = new NoRFCWarningsValidation();
$this->assertTrue($validation->isValid('example@example.com', new EmailLexer()));
$this->assertNull($validation->getError());
}
}
@@ -1,6 +1,6 @@
<?php
namespace Egulias\Tests\EmailValidator\Validation;
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Validation\RFCValidation;
@@ -35,8 +35,9 @@ use Egulias\EmailValidator\Warning\LabelTooLong;
use Egulias\EmailValidator\Warning\LocalTooLong;
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
use Egulias\EmailValidator\Warning\QuotedString;
use PHPUnit\Framework\TestCase;
class RFCValidationTest extends \PHPUnit_Framework_TestCase
class RFCValidationTest extends TestCase
{
/**
* @var RFCValidation
@@ -47,7 +48,7 @@ class RFCValidationTest extends \PHPUnit_Framework_TestCase
* @var EmailLexer
*/
protected $lexer;
protected function setUp()
{
$this->validator = new RFCValidation();
@@ -103,10 +104,10 @@ class RFCValidationTest extends \PHPUnit_Framework_TestCase
public function testInvalidUTF8Email()
{
$email = "\x80\x81\x82@\x83\x84\x85.\x86\x87\x88";
$email = "\x80\x81\x82@\x83\x84\x85.\x86\x87\x88";
$this->assertFalse($this->validator->isValid($email, $this->lexer));
}
/**
* @dataProvider getInvalidEmails
*/
@@ -165,6 +166,9 @@ class RFCValidationTest extends \PHPUnit_Framework_TestCase
['test@email>'],
['test@email<'],
['test@email{'],
['test@ '],
['test@example.com []'],
["test@example.com'"],
];
}
@@ -209,6 +213,7 @@ class RFCValidationTest extends \PHPUnit_Framework_TestCase
[new CRNoLF(), "example@exa\rmple.co.uk"],
[new CRNoLF(), "example@[\r]"],
[new CRNoLF(), "exam\rple@example.co.uk"],
[new ExpectingATEXT(), "test@example.com'"],
];
}
@@ -219,16 +224,16 @@ class RFCValidationTest extends \PHPUnit_Framework_TestCase
{
$this->assertTrue($this->validator->isValid($email, $this->lexer));
$warnings = $this->validator->getWarnings();
$this->assertTrue(
count($warnings) === count($expectedWarnings),
$this->assertCount(
count($warnings), $expectedWarnings,
"Expected: " . implode(",", $expectedWarnings) . " and got " . implode(",", $warnings)
);
foreach ($warnings as $warning) {
$this->assertTrue(isset($expectedWarnings[$warning->code()]));
$this->assertArrayHasKey($warning->code(), $expectedWarnings);
}
}
public function getInvalidEmailsWithWarnings()
{
return [
@@ -263,6 +268,10 @@ class RFCValidationTest extends \PHPUnit_Framework_TestCase
[LocalTooLong::CODE,],
'too_long_localpart_too_long_localpart_too_long_localpart_too_long_localpart@invalid.example.com'
],
[
[LocalTooLong::CODE],
'too_long_localpart_too_long_localpart_123_too_long_localpart_too_long_localpart@example.com'
],
[
[LabelTooLong::CODE,],
'example@toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart.co.uk'
@@ -1,11 +1,12 @@
<?php
namespace Egulias\Tests\EmailValidator\Validation;
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Validation\SpoofCheckValidation;
use PHPUnit\Framework\TestCase;
class SpoofCheckValidationTest extends \PHPUnit_Framework_TestCase
class SpoofCheckValidationTest extends TestCase
{
/**
* @dataProvider validUTF8EmailsProvider
@@ -14,17 +15,17 @@ class SpoofCheckValidationTest extends \PHPUnit_Framework_TestCase
{
$this->markTestSkipped("Skipped for Travis CI since it is failing on this test for unknown reasons.");
$validation = new SpoofCheckValidation();
$this->assertTrue($validation->isValid($email, new EmailLexer()));
}
public function testEmailWithSpoofsIsInvalid()
{
$validation = new SpoofCheckValidation();
$this->assertFalse($validation->isValid("Кириллица"."latin漢字"."ひらがな"."カタカナ", new EmailLexer()));
}
public function validUTF8EmailsProvider()
{
return [