mirror of
https://github.com/egulias/EmailValidator.git
synced 2026-09-01 21:19:25 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f88dcf4b14 | |||
| a5ed8d58ed | |||
| 312b65911f | |||
| d51b279b5f | |||
| 9aab2ceed6 | |||
| 007bfd424e | |||
| 5cc04719d0 | |||
| c70449e7b1 | |||
| 36425ab1f9 | |||
| 4c7033b422 | |||
| 037594add3 | |||
| 4cccb8186b | |||
| ee0db30118 | |||
| c81f18a3ef | |||
| 95979e2ad6 | |||
| 62c3b73c58 | |||
| 8e526a57c1 |
+3
-2
@@ -3,11 +3,12 @@ imports:
|
||||
|
||||
build:
|
||||
environment:
|
||||
php: '7.2.13'
|
||||
php: '7.4'
|
||||
tests:
|
||||
override:
|
||||
-
|
||||
command: 'vendor/bin/phpunit --coverage-clover=clover.xml --exclude-group slow'
|
||||
command: 'vendor/bin/phpunit --coverage-clover=clover.xml --exclude-group flaky'
|
||||
coverage:
|
||||
file: 'clover.xml'
|
||||
format: 'clover'
|
||||
- php-scrutinizer-run
|
||||
|
||||
+2
-15
@@ -2,28 +2,15 @@ language: php
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 7.2
|
||||
env:
|
||||
- psalm=yes
|
||||
dist: bionic
|
||||
- php: 7.3
|
||||
dist: bionic
|
||||
env:
|
||||
- psalm=yes
|
||||
- php: 7.4
|
||||
dist: bionic
|
||||
env:
|
||||
- psalm=yes
|
||||
dist: bionic
|
||||
- php: 8.0
|
||||
env:
|
||||
- psalm=yes
|
||||
dist: bionic
|
||||
#ppc64le support code
|
||||
- php: 7.2
|
||||
arch: ppc64le
|
||||
env:
|
||||
- psalm=yes
|
||||
dist: bionic
|
||||
- php: 7.4
|
||||
arch: ppc64le
|
||||
env:
|
||||
@@ -38,7 +25,7 @@ before_script:
|
||||
- mkdir -p build/logs
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml --exclude-group slow
|
||||
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml --exclude-group flaky
|
||||
- if [ "$psalm" = "yes" ]; then vendor/bin/psalm; fi
|
||||
|
||||
after_script:
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
When contributing to this repository make sure to follow the Pull request process below.
|
||||
When contributing to this repository make sure to follow the Pull request process below.
|
||||
Reduce to the minimum 3rd party dependencies.
|
||||
|
||||
Please note we have a [code of conduct](#Code of Conduct), please follow it in all your interactions with the project.
|
||||
@@ -14,7 +14,7 @@ When doing a PR to v2 remember that you also have to do the PR port to v3, or te
|
||||
3. Describe the changes you are proposing
|
||||
1. If adding an extra validation state the benefits of adding it and the problem is solving
|
||||
2. Document in the readme, by adding it to the list
|
||||
4. Provide appropiate tests for the code you are submitting: aim to keep the existing coverage percentage.
|
||||
4. Provide appropriate tests for the code you are submitting: aim to keep the existing coverage percentage.
|
||||
5. Add your Twitter handle (if you have) so we can thank you there.
|
||||
|
||||
## License
|
||||
@@ -139,11 +139,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||
at [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# EmailValidator
|
||||
|
||||
[](https://travis-ci.org/egulias/EmailValidator)
|
||||
[](https://app.travis-ci.com/github/egulias/EmailValidator)
|
||||
[](https://scrutinizer-ci.com/g/egulias/EmailValidator/?branch=3.x)
|
||||
[](https://scrutinizer-ci.com/g/egulias/EmailValidator/?branch=3.x)
|
||||
[](https://insight.symfony.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6)
|
||||
|
||||
A library for validating emails against several RFC.
|
||||
|
||||
@@ -20,7 +19,7 @@ This library aims to support RFCs:
|
||||
|
||||
## Supported versions
|
||||
|
||||
**Curent major version with full support is v3**
|
||||
**Current major version with full support is v3**
|
||||
|
||||
| Version | Released | EOL | Only critical bug fixes | Full |
|
||||
| ---- | :----: | :----: | :----: | :----: |
|
||||
@@ -33,7 +32,7 @@ This library aims to support RFCs:
|
||||
|
||||
* PHP 7.2
|
||||
* [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)
|
||||
* [Spoofchecking](/src/Validation/Extra/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)
|
||||
|
||||
**Note**: `PHP version upgrades will happen to accomodate to the pace of major frameworks. Minor versions bumps will go via minor versions of this library (i.e: PHP7.3 -> v3.x+1). Major versions will go with major versions of the library`
|
||||
|
||||
@@ -47,7 +46,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).
|
||||
`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
|
||||
```php
|
||||
@@ -65,11 +64,12 @@ $validator->isValid("example@example.com", new RFCValidation()); //true
|
||||
|
||||
1. [RFCValidation](/src/Validation/RFCValidation.php): Standard RFC-like email validation.
|
||||
2. [NoRFCWarningsValidation](/src/Validation/NoRFCWarningsValidation.php): RFC-like validation that will fail when warnings* are found.
|
||||
3. [DNSCheckValidation](/src/Validation/DNSCheckValidation.php): Will check if there are DNS records that signal that the server accepts emails. This does not entails that the email exists.
|
||||
3. [DNSCheckValidation](/src/Validation/DNSCheckValidation.php): Will check if there are DNS records that signal that the server accepts emails. This does not entail that the email exists.
|
||||
5. [MultipleValidationWithAnd](/src/Validation/MultipleValidationWithAnd.php): It is a validation that operates over other validations performing a logical and (&&) over the result of each validation.
|
||||
6. [Your own validation](#how-to-extend): You can extend the library behaviour by implementing your own validations.
|
||||
6. [MessageIDValidation](/src/Validation/MessageIDValidation.php): Follows [RFC2822 for message-id](https://tools.ietf.org/html/rfc2822#section-3.6.4) to validate that field, that has some differences in the domain part.
|
||||
7. [Your own validation](#how-to-extend): You can extend the library behaviour by implementing your own validations.
|
||||
|
||||
*warnings: Warnings are deviations from the RFC that in a broader interpretation are acceptded.
|
||||
*warnings: Warnings are deviations from the RFC that in a broader interpretation are accepted.
|
||||
|
||||
```php
|
||||
<?php
|
||||
@@ -84,7 +84,7 @@ $multipleValidations = new MultipleValidationWithAnd([
|
||||
new RFCValidation(),
|
||||
new DNSCheckValidation()
|
||||
]);
|
||||
//ietf.org has MX records signaling a server with email capabilites
|
||||
//ietf.org has MX records signaling a server with email capabilities
|
||||
$validator->isValid("example@ietf.org", $multipleValidations); //true
|
||||
```
|
||||
|
||||
|
||||
Generated
+1287
-698
File diff suppressed because it is too large
Load Diff
+16
-30
@@ -1,31 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
executionOrder="defects"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="EmailValidator Test Suite">
|
||||
<directory>./tests/EmailValidator</directory>
|
||||
<exclude>./vendor/</exclude>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">./src/Result/Reason</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" executionOrder="defects" processIsolation="false" stopOnFailure="false">
|
||||
<coverage>
|
||||
<include>
|
||||
<directory>./src/</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory suffix=".php">./src/Result/Reason</directory>
|
||||
</exclude>
|
||||
</coverage>
|
||||
<testsuites>
|
||||
<testsuite name="EmailValidator Test Suite">
|
||||
<directory>./tests/EmailValidator</directory>
|
||||
<exclude>./vendor/</exclude>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
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"
|
||||
|
||||
+79
-100
@@ -61,7 +61,7 @@ class EmailLexer extends AbstractLexer
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $charValue = array(
|
||||
protected $charValue = [
|
||||
'{' => self::S_OPENCURLYBRACES,
|
||||
'}' => self::S_CLOSECURLYBRACES,
|
||||
'(' => self::S_OPENPARENTHESIS,
|
||||
@@ -105,11 +105,29 @@ class EmailLexer extends AbstractLexer
|
||||
'?' => self::QUESTIONMARK,
|
||||
'#' => self::NUMBER_SIGN,
|
||||
'¡' => self::INVERT_EXCLAMATION,
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu";
|
||||
|
||||
const VALID_UTF8_REGEX = '/\p{Cc}+/u';
|
||||
|
||||
const CATCHABLE_PATTERNS = [
|
||||
'[a-zA-Z]+[46]?', //ASCII and domain literal
|
||||
'[^\x00-\x7F]', //UTF-8
|
||||
'[0-9]+',
|
||||
'\r\n',
|
||||
'::',
|
||||
'\s+?',
|
||||
'.',
|
||||
];
|
||||
|
||||
const NON_CATCHABLE_PATTERNS = [
|
||||
'[\xA0-\xff]+',
|
||||
];
|
||||
|
||||
const MODIFIERS = 'iu';
|
||||
|
||||
/** @var bool */
|
||||
protected $hasInvalidTokens = false;
|
||||
|
||||
/**
|
||||
@@ -124,6 +142,7 @@ class EmailLexer extends AbstractLexer
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
* @psalm-var array{value:string, type:null|int, position:int}
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
*/
|
||||
@@ -132,27 +151,21 @@ class EmailLexer extends AbstractLexer
|
||||
/**
|
||||
* The next token in the input.
|
||||
*
|
||||
* @var array|null
|
||||
* @var array{position: int, type: int|null|string, value: int|string}|null
|
||||
*/
|
||||
public $lookahead;
|
||||
|
||||
/**
|
||||
* @psalm-var array{value:'', type:null, position:0}
|
||||
*/
|
||||
/** @psalm-var array{value:'', type:null, position:0} */
|
||||
private static $nullToken = [
|
||||
'value' => '',
|
||||
'type' => null,
|
||||
'position' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
private $accumulator = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
/** @var bool */
|
||||
private $hasToRecord = false;
|
||||
|
||||
public function __construct()
|
||||
@@ -161,24 +174,13 @@ class EmailLexer extends AbstractLexer
|
||||
$this->lookahead = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function reset()
|
||||
public function reset() : void
|
||||
{
|
||||
$this->hasInvalidTokens = false;
|
||||
parent::reset();
|
||||
$this->previous = $this->token = self::$nullToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasInvalidTokens()
|
||||
{
|
||||
return $this->hasInvalidTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type
|
||||
* @throws \UnexpectedValueException
|
||||
@@ -186,7 +188,7 @@ class EmailLexer extends AbstractLexer
|
||||
*
|
||||
* @psalm-suppress InvalidScalarArgument
|
||||
*/
|
||||
public function find($type)
|
||||
public function find($type) : bool
|
||||
{
|
||||
$search = clone $this;
|
||||
$search->skipUntil($type);
|
||||
@@ -197,30 +199,24 @@ class EmailLexer extends AbstractLexer
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* getPrevious
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPrevious()
|
||||
{
|
||||
return $this->previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* moveNext
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function moveNext()
|
||||
public function moveNext() : bool
|
||||
{
|
||||
if ($this->hasToRecord && $this->previous === self::$nullToken) {
|
||||
$this->accumulator .= $this->token['value'];
|
||||
}
|
||||
|
||||
$this->previous = $this->token;
|
||||
|
||||
if($this->lookahead === null) {
|
||||
$this->lookahead = self::$nullToken;
|
||||
}
|
||||
|
||||
$hasNext = parent::moveNext();
|
||||
$this->token = $this->token ?: self::$nullToken;
|
||||
|
||||
if ($this->hasToRecord) {
|
||||
$this->accumulator .= $this->token['value'];
|
||||
@@ -229,36 +225,6 @@ class EmailLexer extends AbstractLexer
|
||||
return $hasNext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexical catchable patterns.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getCatchablePatterns()
|
||||
{
|
||||
return array(
|
||||
'[a-zA-Z]+[46]?', //ASCII and domain literal
|
||||
'[^\x00-\x7F]', //UTF-8
|
||||
'[0-9]+',
|
||||
'\r\n',
|
||||
'::',
|
||||
'\s+?',
|
||||
'.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexical non-catchable patterns.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getNonCatchablePatterns()
|
||||
{
|
||||
return [
|
||||
'[\xA0-\xff]+',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve token type. Also processes the token value if necessary.
|
||||
*
|
||||
@@ -271,7 +237,7 @@ class EmailLexer extends AbstractLexer
|
||||
$encoded = $value;
|
||||
|
||||
if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
|
||||
$encoded = utf8_encode($value);
|
||||
$encoded = mb_convert_encoding($value, 'UTF-8', 'Windows-1252');
|
||||
}
|
||||
|
||||
if ($this->isValid($encoded)) {
|
||||
@@ -291,51 +257,64 @@ class EmailLexer extends AbstractLexer
|
||||
return self::GENERIC;
|
||||
}
|
||||
|
||||
protected function isInvalidChar(string $value) : bool
|
||||
{
|
||||
if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isValid(string $value) : bool
|
||||
{
|
||||
if (isset($this->charValue[$value])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return isset($this->charValue[$value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
protected function isNullType($value)
|
||||
protected function isNullType(string $value) : bool
|
||||
{
|
||||
if ($value === "\0") {
|
||||
return true;
|
||||
}
|
||||
return $value === "\0";
|
||||
}
|
||||
|
||||
return false;
|
||||
protected function isInvalidChar(string $value) : bool
|
||||
{
|
||||
return !preg_match(self::INVALID_CHARS_REGEX, $value);
|
||||
}
|
||||
|
||||
protected function isUTF8Invalid(string $value) : bool
|
||||
{
|
||||
if (preg_match('/\p{Cc}+/u', $value)) {
|
||||
return true;
|
||||
}
|
||||
return preg_match(self::VALID_UTF8_REGEX, $value) !== false;
|
||||
}
|
||||
|
||||
return false;
|
||||
public function hasInvalidTokens() : bool
|
||||
{
|
||||
return $this->hasInvalidTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* getPrevious
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getModifiers()
|
||||
public function getPrevious() : array
|
||||
{
|
||||
return 'iu';
|
||||
return $this->previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexical catchable patterns.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getCatchablePatterns() : array
|
||||
{
|
||||
return self::CATCHABLE_PATTERNS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexical non-catchable patterns.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getNonCatchablePatterns() : array
|
||||
{
|
||||
return self::NON_CATCHABLE_PATTERNS;
|
||||
}
|
||||
|
||||
protected function getModifiers() : string
|
||||
{
|
||||
return self::MODIFIERS;
|
||||
}
|
||||
|
||||
public function getAccumulatedValues() : string
|
||||
|
||||
+23
-74
@@ -9,18 +9,12 @@ use Egulias\EmailValidator\Parser\DomainPart;
|
||||
use Egulias\EmailValidator\Result\ValidEmail;
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Warning\EmailTooLong;
|
||||
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||
use Egulias\EmailValidator\Result\Reason\NoLocalPart;
|
||||
|
||||
class EmailParser
|
||||
class EmailParser extends Parser
|
||||
{
|
||||
const EMAIL_MAX_LENGTH = 254;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $warnings = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -30,52 +24,39 @@ class EmailParser
|
||||
* @var string
|
||||
*/
|
||||
protected $localPart = '';
|
||||
/**
|
||||
* @var EmailLexer
|
||||
*/
|
||||
protected $lexer;
|
||||
|
||||
public function __construct(EmailLexer $lexer)
|
||||
{
|
||||
$this->lexer = $lexer;
|
||||
}
|
||||
|
||||
public function parse(string $str) : Result
|
||||
{
|
||||
$this->lexer->setInput($str);
|
||||
|
||||
if (!$this->hasAtToken()) {
|
||||
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
|
||||
}
|
||||
|
||||
$localPartResult = $this->processLocalPart();
|
||||
|
||||
if ($localPartResult->isInvalid()) {
|
||||
return $localPartResult;
|
||||
}
|
||||
|
||||
$domainPartResult = $this->processDomainPart();
|
||||
|
||||
if ($domainPartResult->isInvalid()) {
|
||||
return $domainPartResult;
|
||||
}
|
||||
|
||||
if ($this->lexer->hasInvalidTokens()) {
|
||||
return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->token["value"]);
|
||||
}
|
||||
$result = parent::parse($str);
|
||||
|
||||
$this->addLongEmailWarning($this->localPart, $this->domainPart);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function preLeftParsing(): Result
|
||||
{
|
||||
if (!$this->hasAtToken()) {
|
||||
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
|
||||
}
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
protected function parseLeftFromAt(): Result
|
||||
{
|
||||
return $this->processLocalPart();
|
||||
}
|
||||
|
||||
protected function parseRightFromAt(): Result
|
||||
{
|
||||
return $this->processDomainPart();
|
||||
}
|
||||
|
||||
private function processLocalPart() : Result
|
||||
{
|
||||
$this->lexer->startRecording();
|
||||
$localPartParser = new LocalPart($this->lexer);
|
||||
$localPartResult = $localPartParser->parse();
|
||||
$this->lexer->stopRecording();
|
||||
$this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@');
|
||||
$this->localPart = $localPartParser->localPart();
|
||||
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);
|
||||
|
||||
return $localPartResult;
|
||||
@@ -83,28 +64,14 @@ class EmailParser
|
||||
|
||||
private function processDomainPart() : Result
|
||||
{
|
||||
$this->lexer->clearRecorded();
|
||||
$this->lexer->startRecording();
|
||||
$domainPartParser = new DomainPart($this->lexer);
|
||||
$domainPartResult = $domainPartParser->parse();
|
||||
$this->lexer->stopRecording();
|
||||
$this->domainPart = $this->lexer->getAccumulatedValues();
|
||||
$this->domainPart = $domainPartParser->domainPart();
|
||||
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);
|
||||
|
||||
return $domainPartResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Warning\Warning[]
|
||||
*/
|
||||
public function getWarnings() : array
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDomainPart() : string
|
||||
{
|
||||
return $this->domainPart;
|
||||
@@ -115,25 +82,7 @@ class EmailParser
|
||||
return $this->localPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasAtToken() : bool
|
||||
{
|
||||
$this->lexer->moveNext();
|
||||
$this->lexer->moveNext();
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $localPart
|
||||
* @param string $parsedDomainPart
|
||||
*/
|
||||
protected function addLongEmailWarning($localPart, $parsedDomainPart) : void
|
||||
private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void
|
||||
{
|
||||
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
|
||||
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator;
|
||||
|
||||
use Egulias\EmailValidator\Parser;
|
||||
use Egulias\EmailValidator\Result\Result;
|
||||
use Egulias\EmailValidator\Parser\IDLeftPart;
|
||||
use Egulias\EmailValidator\Parser\IDRightPart;
|
||||
use Egulias\EmailValidator\Result\ValidEmail;
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Warning\EmailTooLong;
|
||||
use Egulias\EmailValidator\Result\Reason\NoLocalPart;
|
||||
|
||||
class MessageIDParser extends Parser
|
||||
{
|
||||
|
||||
const EMAILID_MAX_LENGTH = 254;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $idLeft = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $idRight = '';
|
||||
|
||||
public function parse(string $str) : Result
|
||||
{
|
||||
$result = parent::parse($str);
|
||||
|
||||
$this->addLongEmailWarning($this->idLeft, $this->idRight);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function preLeftParsing(): Result
|
||||
{
|
||||
if (!$this->hasAtToken()) {
|
||||
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
|
||||
}
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
protected function parseLeftFromAt(): Result
|
||||
{
|
||||
return $this->processIDLeft();
|
||||
}
|
||||
|
||||
protected function parseRightFromAt(): Result
|
||||
{
|
||||
return $this->processIDRight();
|
||||
}
|
||||
|
||||
private function processIDLeft() : Result
|
||||
{
|
||||
$localPartParser = new IDLeftPart($this->lexer);
|
||||
$localPartResult = $localPartParser->parse();
|
||||
$this->idLeft = $localPartParser->localPart();
|
||||
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);
|
||||
|
||||
return $localPartResult;
|
||||
}
|
||||
|
||||
private function processIDRight() : Result
|
||||
{
|
||||
$domainPartParser = new IDRightPart($this->lexer);
|
||||
$domainPartResult = $domainPartParser->parse();
|
||||
$this->idRight = $domainPartParser->domainPart();
|
||||
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);
|
||||
|
||||
return $domainPartResult;
|
||||
}
|
||||
|
||||
public function getLeftPart() : string
|
||||
{
|
||||
return $this->idLeft;
|
||||
}
|
||||
|
||||
public function getRightPart() : string
|
||||
{
|
||||
return $this->idRight;
|
||||
}
|
||||
|
||||
private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void
|
||||
{
|
||||
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAILID_MAX_LENGTH) {
|
||||
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator;
|
||||
|
||||
use Egulias\EmailValidator\Result\Result;
|
||||
use Egulias\EmailValidator\Result\ValidEmail;
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||
|
||||
abstract class Parser
|
||||
{
|
||||
/**
|
||||
* @var Warning\Warning[]
|
||||
*/
|
||||
protected $warnings = [];
|
||||
|
||||
/**
|
||||
* @var EmailLexer
|
||||
*/
|
||||
protected $lexer;
|
||||
|
||||
/**
|
||||
* id-left "@" id-right
|
||||
*/
|
||||
abstract protected function parseRightFromAt() : Result;
|
||||
abstract protected function parseLeftFromAt() : Result;
|
||||
abstract protected function preLeftParsing() : Result;
|
||||
|
||||
|
||||
public function __construct(EmailLexer $lexer)
|
||||
{
|
||||
$this->lexer = $lexer;
|
||||
}
|
||||
|
||||
public function parse(string $str) : Result
|
||||
{
|
||||
$this->lexer->setInput($str);
|
||||
|
||||
if ($this->lexer->hasInvalidTokens()) {
|
||||
return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->token["value"]);
|
||||
}
|
||||
|
||||
$preParsingResult = $this->preLeftParsing();
|
||||
if ($preParsingResult->isInvalid()) {
|
||||
return $preParsingResult;
|
||||
}
|
||||
|
||||
$localPartResult = $this->parseLeftFromAt();
|
||||
|
||||
if ($localPartResult->isInvalid()) {
|
||||
return $localPartResult;
|
||||
}
|
||||
|
||||
$domainPartResult = $this->parseRightFromAt();
|
||||
|
||||
if ($domainPartResult->isInvalid()) {
|
||||
return $domainPartResult;
|
||||
}
|
||||
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Warning\Warning[]
|
||||
*/
|
||||
public function getWarnings() : array
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
protected function hasAtToken() : bool
|
||||
{
|
||||
$this->lexer->moveNext();
|
||||
$this->lexer->moveNext();
|
||||
|
||||
return $this->lexer->token['type'] !== EmailLexer::S_AT;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use Egulias\EmailValidator\Result\Reason\UnclosedComment;
|
||||
use Egulias\EmailValidator\Result\Reason\UnOpenedComment;
|
||||
use Egulias\EmailValidator\Warning\Comment as WarningComment;
|
||||
|
||||
class Comment extends Parser
|
||||
class Comment extends PartParser
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
|
||||
@@ -20,8 +20,17 @@ use Egulias\EmailValidator\Result\Reason\ExpectingDTEXT;
|
||||
use Egulias\EmailValidator\Result\Reason\UnusualElements;
|
||||
use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral;
|
||||
|
||||
class DomainLiteral extends Parser
|
||||
class DomainLiteral extends PartParser
|
||||
{
|
||||
const IPV4_REGEX = '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
|
||||
|
||||
const OBSOLETE_WARNINGS = [
|
||||
EmailLexer::INVALID,
|
||||
EmailLexer::C_DEL,
|
||||
EmailLexer::S_LF,
|
||||
EmailLexer::S_BACKSLASH
|
||||
];
|
||||
|
||||
public function parse() : Result
|
||||
{
|
||||
$this->addTagWarnings();
|
||||
@@ -41,7 +50,7 @@ class DomainLiteral extends Parser
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextTokenAny(
|
||||
array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
|
||||
array(EmailLexer::S_HTAB, EmailLexer::S_SP, EmailLexer::CRLF)
|
||||
)) {
|
||||
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
||||
$this->parseFWS();
|
||||
@@ -138,11 +147,8 @@ class DomainLiteral extends Parser
|
||||
|
||||
public function convertIPv4ToIPv6(string $addressLiteralIPv4) : string
|
||||
{
|
||||
$matchesIP = array();
|
||||
$IPv4Match = preg_match(
|
||||
'/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
|
||||
$addressLiteralIPv4,
|
||||
$matchesIP);
|
||||
$matchesIP = [];
|
||||
$IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteralIPv4, $matchesIP);
|
||||
|
||||
// Extract IPv4 part from the end of the address-literal (if there is one)
|
||||
if ($IPv4Match > 0) {
|
||||
@@ -164,11 +170,8 @@ class DomainLiteral extends Parser
|
||||
*/
|
||||
protected function checkIPV4Tag($addressLiteral) : bool
|
||||
{
|
||||
$matchesIP = array();
|
||||
$IPv4Match = preg_match(
|
||||
'/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
|
||||
$addressLiteral,
|
||||
$matchesIP);
|
||||
$matchesIP = [];
|
||||
$IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteral, $matchesIP);
|
||||
|
||||
// Extract IPv4 part from the end of the address-literal (if there is one)
|
||||
|
||||
@@ -186,11 +189,7 @@ class DomainLiteral extends Parser
|
||||
|
||||
private function addObsoleteWarnings() : void
|
||||
{
|
||||
if ($this->lexer->token['type'] === EmailLexer::INVALID ||
|
||||
$this->lexer->token['type'] === EmailLexer::C_DEL ||
|
||||
$this->lexer->token['type'] === EmailLexer::S_LF ||
|
||||
$this->lexer->token['type'] === EmailLexer::S_BACKSLASH
|
||||
) {
|
||||
if(in_array($this->lexer->token['type'], self::OBSOLETE_WARNINGS)) {
|
||||
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
|
||||
}
|
||||
}
|
||||
|
||||
+36
-34
@@ -22,7 +22,7 @@ use Egulias\EmailValidator\Parser\CommentStrategy\DomainComment;
|
||||
use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose;
|
||||
use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
|
||||
|
||||
class DomainPart extends Parser
|
||||
class DomainPart extends PartParser
|
||||
{
|
||||
const DOMAIN_MAX_LENGTH = 253;
|
||||
const LABEL_MAX_LENGTH = 63;
|
||||
@@ -39,6 +39,9 @@ class DomainPart extends Parser
|
||||
|
||||
public function parse() : Result
|
||||
{
|
||||
$this->lexer->clearRecorded();
|
||||
$this->lexer->startRecording();
|
||||
|
||||
$this->lexer->moveNext();
|
||||
|
||||
$domainChecks = $this->performDomainStartChecks();
|
||||
@@ -49,20 +52,22 @@ class DomainPart extends Parser
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
|
||||
return new InvalidEmail(new ConsecutiveAt(), $this->lexer->token['value']);
|
||||
}
|
||||
$domain = $this->doParseDomainPart();
|
||||
if ($domain->isInvalid()) {
|
||||
return $domain;
|
||||
}
|
||||
|
||||
$length = strlen($this->domainPart);
|
||||
$result = $this->doParseDomainPart();
|
||||
if ($result->isInvalid()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$end = $this->checkEndOfDomain();
|
||||
if ($end->isInvalid()) {
|
||||
return $end;
|
||||
}
|
||||
|
||||
$this->lexer->stopRecording();
|
||||
$this->domainPart = $this->lexer->getAccumulatedValues();
|
||||
|
||||
$length = strlen($this->domainPart);
|
||||
if ($length > self::DOMAIN_MAX_LENGTH) {
|
||||
//$this->warnings[DomainTooLong::CODE] = new DomainTooLong();
|
||||
return new InvalidEmail(new DomainTooLong(), $this->lexer->token['value']);
|
||||
}
|
||||
|
||||
@@ -128,14 +133,6 @@ class DomainPart extends Parser
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDomainPart()
|
||||
{
|
||||
return $this->domainPart;
|
||||
}
|
||||
|
||||
protected function parseComments(): Result
|
||||
{
|
||||
$commentParser = new Comment($this->lexer, new DomainComment());
|
||||
@@ -229,7 +226,6 @@ class DomainPart extends Parser
|
||||
*/
|
||||
protected function parseDomainLiteral() : Result
|
||||
{
|
||||
|
||||
try {
|
||||
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
|
||||
} catch (\RuntimeException $e) {
|
||||
@@ -242,22 +238,8 @@ class DomainPart extends Parser
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvalidEmail|ValidEmail
|
||||
*/
|
||||
protected function checkDomainPartExceptions(array $prev, bool $hasComments) : Result
|
||||
{
|
||||
$validDomainTokens = array(
|
||||
EmailLexer::GENERIC => true,
|
||||
EmailLexer::S_HYPHEN => true,
|
||||
EmailLexer::S_DOT => true,
|
||||
);
|
||||
|
||||
if ($hasComments) {
|
||||
$validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true;
|
||||
$validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true;
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET && $prev['type'] !== EmailLexer::S_AT) {
|
||||
return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->token['value']);
|
||||
}
|
||||
@@ -271,6 +253,22 @@ class DomainPart extends Parser
|
||||
return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->token['value']);
|
||||
}
|
||||
|
||||
return $this->validateTokens($hasComments);
|
||||
}
|
||||
|
||||
protected function validateTokens(bool $hasComments) : Result
|
||||
{
|
||||
$validDomainTokens = array(
|
||||
EmailLexer::GENERIC => true,
|
||||
EmailLexer::S_HYPHEN => true,
|
||||
EmailLexer::S_DOT => true,
|
||||
);
|
||||
|
||||
if ($hasComments) {
|
||||
$validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true;
|
||||
$validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true;
|
||||
}
|
||||
|
||||
if (!isset($validDomainTokens[$this->lexer->token['type']])) {
|
||||
return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);
|
||||
}
|
||||
@@ -278,14 +276,13 @@ class DomainPart extends Parser
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
|
||||
private function checkLabelLength(bool $isEndOfDomain = false) : Result
|
||||
{
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_DOT || $isEndOfDomain) {
|
||||
if ($this->isLabelTooLong($this->label)) {
|
||||
$this->label = '';
|
||||
return new InvalidEmail(new LabelTooLong(), $this->lexer->token['value']);
|
||||
}
|
||||
$this->label = '';
|
||||
}
|
||||
$this->label .= $this->lexer->token['value'];
|
||||
return new ValidEmail();
|
||||
@@ -295,7 +292,7 @@ class DomainPart extends Parser
|
||||
private function isLabelTooLong(string $label) : bool
|
||||
{
|
||||
if (preg_match('/[^\x00-\x7F]/', $label)) {
|
||||
idn_to_ascii(utf8_decode($label), IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
|
||||
idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
|
||||
return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
|
||||
}
|
||||
return strlen($label) > self::LABEL_MAX_LENGTH;
|
||||
@@ -307,4 +304,9 @@ class DomainPart extends Parser
|
||||
$this->warnings[TLD::CODE] = new TLD();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function domainPart() : string
|
||||
{
|
||||
return $this->domainPart;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||
use Egulias\EmailValidator\Result\Reason\UnclosedQuotedString;
|
||||
use Egulias\EmailValidator\Result\Result;
|
||||
|
||||
class DoubleQuote extends Parser
|
||||
class DoubleQuote extends PartParser
|
||||
{
|
||||
public function parse() : Result
|
||||
{
|
||||
@@ -19,18 +19,19 @@ class DoubleQuote extends Parser
|
||||
$validQuotedString = $this->checkDQUOTE();
|
||||
if($validQuotedString->isInvalid()) return $validQuotedString;
|
||||
|
||||
$special = array(
|
||||
$special = [
|
||||
EmailLexer::S_CR => true,
|
||||
EmailLexer::S_HTAB => true,
|
||||
EmailLexer::S_LF => true
|
||||
);
|
||||
];
|
||||
|
||||
$invalid = array(
|
||||
$invalid = [
|
||||
EmailLexer::C_NUL => true,
|
||||
EmailLexer::S_HTAB => true,
|
||||
EmailLexer::S_CR => true,
|
||||
EmailLexer::S_LF => true
|
||||
);
|
||||
];
|
||||
|
||||
$setSpecialsWarning = true;
|
||||
|
||||
$this->lexer->moveNext();
|
||||
|
||||
@@ -13,8 +13,16 @@ use Egulias\EmailValidator\Result\Reason\ExpectingCTEXT;
|
||||
use Egulias\EmailValidator\Result\Result;
|
||||
use Egulias\EmailValidator\Result\ValidEmail;
|
||||
|
||||
class FoldingWhiteSpace extends Parser
|
||||
class FoldingWhiteSpace extends PartParser
|
||||
{
|
||||
const FWS_TYPES = [
|
||||
EmailLexer::S_SP,
|
||||
EmailLexer::S_HTAB,
|
||||
EmailLexer::S_CR,
|
||||
EmailLexer::S_LF,
|
||||
EmailLexer::CRLF
|
||||
];
|
||||
|
||||
public function parse() : Result
|
||||
{
|
||||
if (!$this->isFWS()) {
|
||||
@@ -23,7 +31,10 @@ class FoldingWhiteSpace extends Parser
|
||||
|
||||
$previous = $this->lexer->getPrevious();
|
||||
|
||||
$this->checkCRLFInFWS();
|
||||
$resultCRLF = $this->checkCRLFInFWS();
|
||||
if ($resultCRLF->isInvalid()) {
|
||||
return $resultCRLF;
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_CR) {
|
||||
return new InvalidEmail(new CRNoLF(), $this->lexer->token['value']);
|
||||
@@ -46,10 +57,7 @@ class FoldingWhiteSpace extends Parser
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvalidEmail|ValidEmail|null
|
||||
*/
|
||||
protected function checkCRLFInFWS()
|
||||
protected function checkCRLFInFWS() : Result
|
||||
{
|
||||
if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
|
||||
return new ValidEmail();
|
||||
@@ -63,21 +71,16 @@ class FoldingWhiteSpace extends Parser
|
||||
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
|
||||
return new InvalidEmail(new CRLFAtTheEnd(), $this->lexer->token['value']);
|
||||
}
|
||||
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFWS()
|
||||
protected function isFWS() : bool
|
||||
{
|
||||
if ($this->escaped()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->lexer->token['type'] === EmailLexer::S_SP ||
|
||||
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
|
||||
$this->lexer->token['type'] === EmailLexer::S_CR ||
|
||||
$this->lexer->token['type'] === EmailLexer::S_LF ||
|
||||
$this->lexer->token['type'] === EmailLexer::CRLF;
|
||||
return in_array($this->lexer->token['type'], self::FWS_TYPES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Parser;
|
||||
|
||||
use Egulias\EmailValidator\Result\Result;
|
||||
use Egulias\EmailValidator\Parser\LocalPart;
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Result\Reason\CommentsInIDRight;
|
||||
|
||||
class IDLeftPart extends LocalPart
|
||||
{
|
||||
protected function parseComments(): Result
|
||||
{
|
||||
return new InvalidEmail(new CommentsInIDRight(), $this->lexer->token['value']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Parser;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Result\Result;
|
||||
use Egulias\EmailValidator\Result\ValidEmail;
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||
|
||||
class IDRightPart extends DomainPart
|
||||
{
|
||||
protected function validateTokens(bool $hasComments) : Result
|
||||
{
|
||||
$invalidDomainTokens = [
|
||||
EmailLexer::S_DQUOTE => true,
|
||||
EmailLexer::S_SQUOTE => true,
|
||||
EmailLexer::S_BACKTICK => true,
|
||||
EmailLexer::S_SEMICOLON => true,
|
||||
EmailLexer::S_GREATERTHAN => true,
|
||||
EmailLexer::S_LOWERTHAN => true,
|
||||
];
|
||||
|
||||
if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
|
||||
return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);
|
||||
}
|
||||
return new ValidEmail();
|
||||
}
|
||||
}
|
||||
+35
-19
@@ -13,27 +13,28 @@ use Egulias\EmailValidator\Result\Reason\ConsecutiveDot;
|
||||
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||
use Egulias\EmailValidator\Parser\CommentStrategy\LocalComment;
|
||||
|
||||
class LocalPart extends Parser
|
||||
class LocalPart extends PartParser
|
||||
{
|
||||
const INVALID_TOKENS = [
|
||||
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
|
||||
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
|
||||
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
|
||||
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
|
||||
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
|
||||
EmailLexer::S_COLON => EmailLexer::S_COLON,
|
||||
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
|
||||
EmailLexer::INVALID => EmailLexer::INVALID
|
||||
];
|
||||
|
||||
/**
|
||||
* Invalid lexer tokens for local part
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
private $invalidTokens = array(
|
||||
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
|
||||
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
|
||||
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
|
||||
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
|
||||
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
|
||||
EmailLexer::S_COLON => EmailLexer::S_COLON,
|
||||
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
|
||||
EmailLexer::INVALID => EmailLexer::INVALID
|
||||
);
|
||||
private $localPart = '';
|
||||
|
||||
|
||||
public function parse() : Result
|
||||
{
|
||||
$totalLength = 0;
|
||||
$this->lexer->startRecording();
|
||||
|
||||
while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) {
|
||||
if ($this->hasDotAtStart()) {
|
||||
@@ -74,8 +75,9 @@ class LocalPart extends Parser
|
||||
return $resultEscaping;
|
||||
}
|
||||
|
||||
if (isset($this->invalidTokens[$this->lexer->token['type']])) {
|
||||
return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']);
|
||||
$resultToken = $this->validateTokens(false);
|
||||
if ($resultToken->isInvalid()) {
|
||||
return $resultToken;
|
||||
}
|
||||
|
||||
$resultFWS = $this->parseLocalFWS();
|
||||
@@ -83,17 +85,31 @@ class LocalPart extends Parser
|
||||
return $resultFWS;
|
||||
}
|
||||
|
||||
$totalLength += strlen($this->lexer->token['value']);
|
||||
$this->lexer->moveNext();
|
||||
}
|
||||
|
||||
if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) {
|
||||
$this->lexer->stopRecording();
|
||||
$this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@');
|
||||
if (strlen($this->localPart) > LocalTooLong::LOCAL_PART_LENGTH) {
|
||||
$this->warnings[LocalTooLong::CODE] = new LocalTooLong();
|
||||
}
|
||||
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
protected function validateTokens(bool $hasComments) : Result
|
||||
{
|
||||
if (isset(self::INVALID_TOKENS[$this->lexer->token['type']])) {
|
||||
return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']);
|
||||
}
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
public function localPart() : string
|
||||
{
|
||||
return $this->localPart;
|
||||
}
|
||||
|
||||
private function parseLocalFWS() : Result
|
||||
{
|
||||
$foldingWS = new FoldingWhiteSpace($this->lexer);
|
||||
@@ -118,7 +134,7 @@ class LocalPart extends Parser
|
||||
return $parseAgain;
|
||||
}
|
||||
|
||||
private function parseComments(): Result
|
||||
protected function parseComments(): Result
|
||||
{
|
||||
$commentParser = new Comment($this->lexer, new LocalComment());
|
||||
$result = $commentParser->parse();
|
||||
|
||||
@@ -8,7 +8,7 @@ use Egulias\EmailValidator\Result\Reason\ConsecutiveDot;
|
||||
use Egulias\EmailValidator\Result\Result;
|
||||
use Egulias\EmailValidator\Result\ValidEmail;
|
||||
|
||||
abstract class Parser
|
||||
abstract class PartParser
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
@@ -25,6 +25,8 @@ abstract class Parser
|
||||
$this->lexer = $lexer;
|
||||
}
|
||||
|
||||
abstract public function parse() : Result;
|
||||
|
||||
/**
|
||||
* @return \Egulias\EmailValidator\Warning\Warning[]
|
||||
*/
|
||||
@@ -33,8 +35,6 @@ abstract class Parser
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
abstract public function parse() : Result;
|
||||
|
||||
protected function parseFWS() : Result
|
||||
{
|
||||
$foldingWS = new FoldingWhiteSpace($this->lexer);
|
||||
@@ -52,9 +52,6 @@ abstract class Parser
|
||||
return new ValidEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function escaped() : bool
|
||||
{
|
||||
$previous = $this->lexer->getPrevious();
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Egulias\EmailValidator\Result;
|
||||
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Result\Reason\EmptyReason;
|
||||
use Egulias\EmailValidator\Result\Reason\Reason;
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,9 @@ class MultipleErrors extends InvalidEmail
|
||||
|
||||
public function reason() : Reason
|
||||
{
|
||||
return $this->reasons[0];
|
||||
return 0 !== count($this->reasons)
|
||||
? current($this->reasons)
|
||||
: new EmptyReason();
|
||||
}
|
||||
|
||||
public function description() : string
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Result\Reason;
|
||||
|
||||
class CommentsInIDRight implements Reason
|
||||
{
|
||||
public function code() : int
|
||||
{
|
||||
return 400;
|
||||
}
|
||||
|
||||
public function description() : string
|
||||
{
|
||||
return 'Comments are not allowed in IDRight for message-id';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Result\Reason;
|
||||
|
||||
class EmptyReason implements Reason
|
||||
{
|
||||
public function code() : int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function description() : string
|
||||
{
|
||||
return 'Empty reason';
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,6 @@ class UnOpenedComment implements Reason
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Missing openning comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2';
|
||||
return 'Missing opening comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2';
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\Validation\DNSGetRecordWrapper;
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail;
|
||||
@@ -17,6 +18,29 @@ class DNSCheckValidation implements EmailValidation
|
||||
*/
|
||||
protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
const RESERVED_DNS_TOP_LEVEL_NAMES = [
|
||||
// Reserved Top Level DNS Names
|
||||
'test',
|
||||
'example',
|
||||
'invalid',
|
||||
'localhost',
|
||||
|
||||
// mDNS
|
||||
'local',
|
||||
|
||||
// Private DNS Namespaces
|
||||
'intranet',
|
||||
'internal',
|
||||
'private',
|
||||
'corp',
|
||||
'home',
|
||||
'lan',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@@ -32,12 +56,22 @@ class DNSCheckValidation implements EmailValidation
|
||||
*/
|
||||
private $mxRecords = [];
|
||||
|
||||
/**
|
||||
* @var DNSGetRecordWrapper
|
||||
*/
|
||||
private $dnsGetRecord;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(DNSGetRecordWrapper $dnsGetRecord = null)
|
||||
{
|
||||
if (!function_exists('idn_to_ascii')) {
|
||||
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
|
||||
}
|
||||
|
||||
if ($dnsGetRecord == null) {
|
||||
$dnsGetRecord = new DNSGetRecordWrapper();
|
||||
}
|
||||
|
||||
$this->dnsGetRecord = $dnsGetRecord;
|
||||
}
|
||||
|
||||
public function isValid(string $email, EmailLexer $emailLexer) : bool
|
||||
@@ -53,29 +87,8 @@ class DNSCheckValidation implements EmailValidation
|
||||
// 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);
|
||||
$isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], self::RESERVED_DNS_TOP_LEVEL_NAMES, true);
|
||||
|
||||
// Exclude reserved top level DNS names
|
||||
if ($isLocalDomain || $isReservedTopLevel) {
|
||||
@@ -120,27 +133,17 @@ class DNSCheckValidation implements EmailValidation
|
||||
*/
|
||||
private function validateDnsRecords($host) : bool
|
||||
{
|
||||
// A workaround to fix https://bugs.php.net/bug.php?id=73149
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
set_error_handler(
|
||||
static function (int $errorLevel, string $errorMessage): ?bool {
|
||||
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
|
||||
}
|
||||
);
|
||||
$dnsRecordsResult = $this->dnsGetRecord->getRecords($host, static::DNS_RECORD_TYPES_TO_CHECK);
|
||||
|
||||
try {
|
||||
// Get all MX, A and AAAA DNS records for host
|
||||
$dnsRecords = dns_get_record($host, static::DNS_RECORD_TYPES_TO_CHECK);
|
||||
} catch (\RuntimeException $exception) {
|
||||
if ($dnsRecordsResult->withError()) {
|
||||
$this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$dnsRecords = $dnsRecordsResult->getRecords();
|
||||
|
||||
// No MX, A or AAAA DNS records
|
||||
if ($dnsRecords === [] || $dnsRecords === false) {
|
||||
if ($dnsRecords === []) {
|
||||
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
|
||||
return false;
|
||||
}
|
||||
@@ -167,6 +170,11 @@ class DNSCheckValidation implements EmailValidation
|
||||
*/
|
||||
private function validateMxRecord($dnsRecord) : bool
|
||||
{
|
||||
if (!isset($dnsRecord['type'])) {
|
||||
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($dnsRecord['type'] !== 'MX') {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
class DNSGetRecordWrapper
|
||||
{
|
||||
/**
|
||||
* @param string $host
|
||||
* @param int $type
|
||||
*/
|
||||
public function getRecords(string $host, int $type) : DNSRecords
|
||||
{
|
||||
// A workaround to fix https://bugs.php.net/bug.php?id=73149
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
set_error_handler(
|
||||
static function (int $errorLevel, string $errorMessage): ?bool {
|
||||
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
|
||||
}
|
||||
);
|
||||
try {
|
||||
// Get all MX, A and AAAA DNS records for host
|
||||
return new DNSRecords(dns_get_record($host, $type));
|
||||
} catch (\RuntimeException $exception) {
|
||||
return new DNSRecords([], true);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
class DNSRecords
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array $records
|
||||
*/
|
||||
private $records = [];
|
||||
|
||||
/**
|
||||
* @var bool $error
|
||||
*/
|
||||
private $error = false;
|
||||
|
||||
public function __construct(array $records, bool $error = false)
|
||||
{
|
||||
$this->records = $records;
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
public function getRecords() : array
|
||||
{
|
||||
return $this->records;
|
||||
}
|
||||
|
||||
public function withError() : bool
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@ interface EmailValidation
|
||||
/**
|
||||
* Returns the validation error.
|
||||
*
|
||||
* @return InvalidEmail
|
||||
* @return InvalidEmail|null
|
||||
*/
|
||||
public function getError() : ?InvalidEmail;
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\MessageIDParser;
|
||||
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||
use Egulias\EmailValidator\Result\Reason\ExceptionFound;
|
||||
|
||||
class MessageIDValidation implements EmailValidation
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $warnings = [];
|
||||
|
||||
/**
|
||||
* @var ?InvalidEmail
|
||||
*/
|
||||
private $error;
|
||||
|
||||
public function isValid(string $email, EmailLexer $emailLexer): bool
|
||||
{
|
||||
$parser = new MessageIDParser($emailLexer);
|
||||
try {
|
||||
$result = $parser->parse($email);
|
||||
$this->warnings = $parser->getWarnings();
|
||||
if ($result->isInvalid()) {
|
||||
/** @psalm-suppress PropertyTypeCoercion */
|
||||
$this->error = $result;
|
||||
return false;
|
||||
}
|
||||
} catch (\Exception $invalid) {
|
||||
$this->error = new InvalidEmail(new ExceptionFound($invalid), '');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getWarnings(): array
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
public function getError(): ?InvalidEmail
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use Egulias\EmailValidator\Result\MultipleErrors;
|
||||
class MultipleValidationWithAnd implements EmailValidation
|
||||
{
|
||||
/**
|
||||
* If one of validations fails, the remaining validations will be skept.
|
||||
* If one of validations fails, the remaining validations will be skipped.
|
||||
* This means MultipleErrors will only contain a single error, the first found.
|
||||
*/
|
||||
const STOP_ON_ERROR = 0;
|
||||
|
||||
@@ -42,6 +42,6 @@ abstract class Warning
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->message() . " rfc: " . $this->rfcNumber . "interal code: " . static::CODE;
|
||||
return $this->message() . " rfc: " . $this->rfcNumber . "internal code: " . static::CODE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
namespace Egulias\EmailValidator\Tests\EmailValidator\Reason;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Egulias\EmailValidator\Result\MultipleErrors;
|
||||
use Egulias\EmailValidator\Result\Reason\EmptyReason;
|
||||
use Egulias\EmailValidator\Tests\EmailValidator\Dummy\AnotherDummyReason;
|
||||
use Egulias\EmailValidator\Tests\EmailValidator\Dummy\DummyReason;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MultipleErrorsTest extends TestCase
|
||||
{
|
||||
@@ -35,4 +36,31 @@ class MultipleErrorsTest extends TestCase
|
||||
$this->assertEquals($expectedReason, $multiError->description());
|
||||
$this->assertEquals($error1, $multiError->reason());
|
||||
}
|
||||
|
||||
public function testRetrieveFirstReasonWithReasonCodeEqualsZero(): void
|
||||
{
|
||||
$error1 = new DummyReason();
|
||||
|
||||
$multiError = new MultipleErrors();
|
||||
$multiError->addReason($error1);
|
||||
|
||||
$this->assertEquals($error1, $multiError->reason());
|
||||
}
|
||||
|
||||
public function testRetrieveFirstReasonWithReasonCodeDistinctToZero(): void
|
||||
{
|
||||
$error1 = new AnotherDummyReason();
|
||||
|
||||
$multiError = new MultipleErrors();
|
||||
$multiError->addReason($error1);
|
||||
|
||||
$this->assertEquals($error1, $multiError->reason());
|
||||
}
|
||||
|
||||
public function testRetrieveFirstReasonWithNoReasonAdded()
|
||||
{
|
||||
$emptyReason = new EmptyReason();
|
||||
$multiError = new MultipleErrors();
|
||||
$this->assertEquals($emptyReason, $multiError->reason());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ use Egulias\EmailValidator\Result\Reason\LocalOrReservedDomain;
|
||||
use Egulias\EmailValidator\Result\Reason\NoDNSRecord;
|
||||
use Egulias\EmailValidator\Result\Reason\UnableToGetDNSRecord;
|
||||
use Egulias\EmailValidator\Validation\DNSCheckValidation;
|
||||
use Egulias\EmailValidator\Validation\DNSGetRecordWrapper;
|
||||
use Egulias\EmailValidator\Validation\DNSRecords;
|
||||
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -109,20 +111,42 @@ class DNSCheckValidationTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @group slow
|
||||
* @group flaky
|
||||
*/
|
||||
public function testUnableToGetDNSRecord()
|
||||
{
|
||||
error_reporting(\E_ALL);
|
||||
|
||||
// UnableToGetDNSRecord raises on network errors (e.g. timeout) that we can‘t emulate in tests (for sure),
|
||||
// but we can try to get timeout error by trying to fetch all DNS records
|
||||
$validation = new class extends DNSCheckValidation {
|
||||
protected const DNS_RECORD_TYPES_TO_CHECK = \DNS_ALL;
|
||||
// but we can simulate with the wrapper helper
|
||||
|
||||
$wrapper = new class extends DNSGetRecordWrapper {
|
||||
public function getRecords(string $host, int $type) : DNSRecords
|
||||
{
|
||||
return new DNSRecords([], true);
|
||||
}
|
||||
};
|
||||
|
||||
$validation = new DNSCheckValidation($wrapper);
|
||||
$expectedError = new InvalidEmail(new UnableToGetDNSRecord(), '');
|
||||
|
||||
$validation->isValid('example@invalid.example.com', new EmailLexer());
|
||||
$this->assertEquals($expectedError, $validation->getError());
|
||||
}
|
||||
}
|
||||
|
||||
public function testMissingTypeKey()
|
||||
{
|
||||
$wrapper = new class extends DNSGetRecordWrapper {
|
||||
public function getRecords(string $host, int $type): DNSRecords
|
||||
{
|
||||
return new DNSRecords(['host' => 'test']);
|
||||
}
|
||||
};
|
||||
|
||||
$validation = new DNSCheckValidation($wrapper);
|
||||
$expectedError = new InvalidEmail(new NoDNSRecord(), '');
|
||||
|
||||
$validation->isValid('example@invalid.example.com', new EmailLexer());
|
||||
$this->assertEquals($expectedError, $validation->getError());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Validation\MessageIDValidation;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MessageIDValidationTest extends TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @dataProvider validMessageIDs
|
||||
*/
|
||||
public function testValidMessageIDs(string $messageID)
|
||||
{
|
||||
$validator = new MessageIDValidation();
|
||||
|
||||
$this->assertTrue($validator->isValid($messageID, new EmailLexer()));
|
||||
}
|
||||
|
||||
public function validMessageIDs() : array
|
||||
{
|
||||
return [
|
||||
['a@b.c+&%$.d'],
|
||||
['a.b+&%$.c@d'],
|
||||
['a@ä'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidMessageIDs
|
||||
*/
|
||||
public function testInvalidMessageIDs(string $messageID)
|
||||
{
|
||||
$validator = new MessageIDValidation();
|
||||
|
||||
$this->assertFalse($validator->isValid($messageID, new EmailLexer()));
|
||||
}
|
||||
|
||||
public function invalidMessageIDs() : array
|
||||
{
|
||||
return [
|
||||
['example'],
|
||||
['example@with space'],
|
||||
['example@iana.'],
|
||||
['example@ia\na.'],
|
||||
/**
|
||||
* RFC 2822, section 3.6.4, Page 25
|
||||
* Since the msg-id has
|
||||
* a similar syntax to angle-addr (identical except that comments and
|
||||
* folding white space are not allowed), a good method is to put the
|
||||
* domain name (or a domain literal IP address) of the host on which the
|
||||
* message identifier was created on the right hand side of the "@", and
|
||||
* put a combination of the current absolute date and time along with
|
||||
* some other currently unique (perhaps sequential) identifier available
|
||||
* on the system (for example, a process id number) on the left hand
|
||||
* side.
|
||||
*/
|
||||
['example(comment)@example.com'],
|
||||
["\r\nFWS@example.com"]
|
||||
];
|
||||
}
|
||||
|
||||
public function testInvalidMessageIDsWithError()
|
||||
{
|
||||
$this->markTestIncomplete("missing error check");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ class RFCValidationDomainPartTest extends TestCase
|
||||
['validipv4@[127.0.0.0]'],
|
||||
['validipv4@127.0.0.0'],
|
||||
['withhyphen@domain-exam.com'],
|
||||
['valid_long_domain@71846jnrsoj91yfhc18rkbrf90ue3onl8y46js38kae8inz0t1.5a-xdycuau.na49.le.example.com']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user