Compare commits

...

8 Commits

Author SHA1 Message Date
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
9 changed files with 201 additions and 37 deletions
+18 -11
View File
@@ -1,7 +1,12 @@
# EmailValidator
[![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)
=============================
## Suported RFCs ##
[![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.
@@ -9,7 +14,7 @@ RFC 5321, 5322, 6530, 6531, 6532.
## Requirements ##
* [Composer](https://getcomposer.org) is required for installation
* [Spoofchecking](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/SpoofCheckValidation.php) and [DNSCheckValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/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)
* [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 ##
@@ -20,6 +25,7 @@ composer require egulias/email-validator
```
## 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
@@ -36,11 +42,11 @@ $validator->isValid("example@example.com", new RFCValidation()); //true
### Available validations ###
1. [RFCValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/RFCValidation.php)
2. [NoRFCWarningsValidation](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. [MultipleValidationWithAnd](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)
`MultipleValidationWithAnd`
@@ -65,10 +71,11 @@ $validator->isValid("example@example.com", $multipleValidations); //true
### How to extend ###
It's easy! You just need to implement [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 ##
(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:
@@ -78,5 +85,5 @@ As this is a port from another library and work, here are other people related t
* Dominic Sayers [@dominicsayers](https://github.com/dominicsayers): The original isemail function
## License ##
Released under the MIT License attached with this code.
Released under the MIT License attached with this code.
+2 -2
View File
@@ -12,14 +12,14 @@
>
<testsuites>
<testsuite name="EmailValidator Test Suite">
<directory>./Tests/EmailValidator</directory>
<directory>./tests/EmailValidator</directory>
<exclude>./vendor/</exclude>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./EmailValidator/</directory>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>
+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)';
}
+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)';
}
+1 -1
View File
@@ -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";
}
+3
View File
@@ -198,6 +198,9 @@ class DomainPart extends Parser
$domain .= $this->lexer->token['value'];
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_SP) {
throw new CharNotAllowed();
}
} while (null !== $this->lexer->token['type']);
return $domain;
+103 -14
View File
@@ -4,6 +4,8 @@ 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;
@@ -19,6 +21,12 @@ class DNSCheckValidation implements EmailValidation
*/
private $error;
/**
* @var array
*/
private $mxRecords = [];
public function __construct()
{
if (!function_exists('idn_to_ascii')) {
@@ -36,7 +44,40 @@ class DNSCheckValidation implements EmailValidation
$host = substr($email, $lastAtPos + 1);
}
return $this->checkDNS($host);
// 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()
@@ -54,24 +95,72 @@ class DNSCheckValidation implements EmailValidation
*
* @return bool
*/
protected function checkDNS($host)
protected function checkDns($host)
{
$variant = INTL_IDNA_VARIANT_2003;
if ( defined('INTL_IDNA_VARIANT_UTS46') ) {
$variant = INTL_IDNA_VARIANT_UTS46;
}
$variant = INTL_IDNA_VARIANT_UTS46;
$host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.';
$Aresult = true;
$MXresult = checkdnsrr($host, 'MX');
return $this->validateDnsRecords($host);
}
if (!$MXresult) {
$this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
$Aresult = checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA');
if (!$Aresult) {
$this->error = new NoDNSRecord();
/**
* 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) || !$dnsRecords) {
$this->error = new NoDNSRecord();
return false;
}
// For each DNS record
foreach ($dnsRecords as $dnsRecord) {
if (!$this->validateMXRecord($dnsRecord)) {
return false;
}
}
return $MXresult || $Aresult;
// 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;
}
}
@@ -4,6 +4,8 @@ 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;
@@ -14,22 +16,44 @@ class DNSCheckValidationTest extends TestCase
{
return [
// dot-atom
['Abc@example.com'],
['ABC@EXAMPLE.COM'],
['Abc.123@example.com'],
['user+mailbox/department=shipping@example.com'],
['!#$%&\'*+-/=?^_`.{|}~@example.com'],
['Abc@ietf.org'],
['ABC@ietf.org'],
['Abc.123@ietf.org'],
['user+mailbox/department=shipping@ietf.org'],
['!#$%&\'*+-/=?^_`.{|}~@ietf.org'],
// quoted string
['"Abc@def"@example.com'],
['"Fred\ Bloggs"@example.com'],
['"Joe.\\Blow"@example.com'],
['"Abc@def"@ietf.org'],
['"Fred\ Bloggs"@ietf.org'],
['"Joe.\\Blow"@ietf.org'],
// unicide
// 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
*/
@@ -45,6 +69,27 @@ class DNSCheckValidationTest extends TestCase
$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();
@@ -52,6 +97,7 @@ class DNSCheckValidationTest extends TestCase
$validation->isValid("example@invalid.example.com", new EmailLexer());
$this->assertEquals($expectedWarnings, $validation->getWarnings());
}
*/
public function testNoDNSError()
{
@@ -167,6 +167,7 @@ class RFCValidationTest extends TestCase
['test@email<'],
['test@email{'],
['test@ '],
['test@example.com []'],
];
}