Compare commits

...

20 Commits

Author SHA1 Message Date
Eduardo Gulias Davis b115554301 Fix Readme style 2024-12-27 01:36:43 +01:00
Eduardo Gulias Davis c67e8e655f Fix Readme style 2024-12-27 01:27:39 +01:00
Eduardo Gulias Davis 2647beedf3 Removed stale changelog 2024-12-27 01:17:25 +01:00
Eduardo Gulias Davis e25ccf7e74 Add depandabot 2024-12-27 00:36:05 +01:00
Eduardo Gulias Davis d379dc240b Update readme 2024-12-27 00:31:08 +01:00
Eduardo Gulias Davis 621d46003b Fix github action (#396)
* Fix github action

* PHP8.4 is too new

* Revert log storage
2024-12-27 00:28:29 +01:00
chris 87db43e4ff docs: fix build status in README.md (#392) 2024-10-16 23:10:38 +02:00
chris c118cc7666 ci: fix deprecation for github actions (#391) 2024-10-08 19:20:25 +02:00
Arnt Gulbrandsen b4a1a0e5a6 Regularise a test. (#389)
The validEmailsProvider contained n-1 valid email addresses and 1 domain;
this replaces the domain with two valid email addresses in that domain, so
that all entries match the name.
2024-09-15 22:03:00 +02:00
Danny van der Sluijs 92c1ab0cc1 Update GitHub Actions to V4 (#383)
* Update tests.yml using actions/checkout@v4

* Update static-analysis.yml usingactions/checkout@v4
2024-05-01 15:20:12 +02:00
Daniel 8775776563 chore: replace scalar with more precise type specification (#369)
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
2023-10-22 23:56:40 +02:00
Andrew Ivchenkov 87b8b8506e Used the most strict psalm level and fixed all psalm errors (#377) 2023-10-15 23:39:26 +02:00
Christian Rishøj ebaaf5be6c perform AAAA check separately (fixes #301) (#376) 2023-10-06 08:47:41 +02:00
chris d11195e117 chore: add tests for php 8.3 (#373)
authored-by: Christopher Georg <christopher.georg@sr-travel.de>
2023-08-28 18:30:37 +02:00
chris 36233f5918 chore: update phpunit 9 = 10 and psalm 4 = 5 (#368)
Co-authored-by: Christopher Georg <christopher.georg@sr-travel.de>
2023-07-28 20:33:18 +02:00
Márk Magyar 27be0e7157 code refactor (#364)
* use spread syntax instead of array_merge()

* use type cast instead of function cast

* removed redundant returns

* removed redundant phpdoc - type is already inferred

* removed invalid phpdoc - type is already inferred

The PHPDoc return type hint was incomplete, it should have been `InvalidEmail|null`, however, it can be removed altogether as the return type is already inferred from the code.

* changed warnEscaping()'s return value from bool to void

This is not a breaking change as the method is private,  and it's only used at one place, where the return value was not used anyway.

* made private class property local

`private $parser` was used in only one place, hence it can be local, there is no reason to put it into the class' scope.

* removed unnecessary type casting

Concatenation already casts `static::CODE` from `int` to `string`, no reason to do it explicitly.

* removed redundant initializers - constructor overwrites them immediately

* removed redundant else block

* simplified if-else statement

* wrapped if body in brackets to comply with PSR12

* fixed README formatting

- fixed numbering at the `Available validations` section
- fixed overall formatting

* Revert "removed redundant phpdoc - type is already inferred"

This reverts commit 68a9ae20bd.

* don't wrap long lines

* make properties typed

Also using constructor property promotion, see more info about it [here](https://php.watch/versions/8.0/constructor-property-promotion).
2023-06-07 16:47:52 +02:00
Eduardo Gulias Davis 97c28cd611 Badges and Scrutinezr config (#366)
* Codacy badges and removing Scrutinizr config
2023-05-23 22:25:38 +02:00
Eduardo Gulias Davis 178d148438 Codacy GitHub action (#365)
* README for v4

* badges update

* license year

* Adding codacy reporter action

* plain text secret for testing

* using bash

* downloading artifact

* all in one

* path to coverage

* removing extra job and adding sending coverage report only once
2023-05-23 17:55:24 +02:00
Grégoire Pineau c4f65a68ee Fixed typo in CHANGELOG.md (#363) 2023-05-22 16:53:31 +02:00
KergeKacsa 3a85486b70 DNS record check now passes if email address has no top-level domain (#355) 2023-01-14 15:17:03 +01:00
40 changed files with 220 additions and 252 deletions
-1
View File
@@ -3,5 +3,4 @@
/.* export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
/psalm.baseline.xml export-ignore
/README.md export-ignore
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
+14 -6
View File
@@ -1,4 +1,4 @@
name: tests
name: build
on:
push:
@@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: true
matrix:
php: ['8.1', '8.2']
php: ['8.1', '8.2', '8.3']
deps: [highest]
include:
@@ -27,7 +27,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -38,7 +38,7 @@ jobs:
coverage: xdebug
- name: Install dependencies
uses: ramsey/composer-install@v2
uses: ramsey/composer-install@v3
with:
dependency-versions: ${{ matrix.deps }}
@@ -48,8 +48,16 @@ jobs:
- name: Execute tests
run: vendor/bin/phpunit --coverage-clover build/logs/clover.xml --exclude-group flaky
- name: Coverage
if: ${{ matrix.php == '8.1' && matrix.deps == 'lowest' }}
shell: bash
env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
run: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r build/logs/clover.xml
- name: Store artifacts
uses: actions/upload-artifact@v3
if: ${{ matrix.php == '8.1' && matrix.deps == 'lowest' }}
uses: actions/upload-artifact@v4
with:
name: logs
name: clover.xml
path: build/logs
-13
View File
@@ -1,13 +0,0 @@
imports:
- php
build:
environment:
php: "8.1"
tests:
override:
- command: "vendor/bin/phpunit --coverage-clover=clover.xml --exclude-group flaky"
coverage:
file: "build/logs/clover.xml"
format: "clover"
- php-scrutinizer-run
-33
View File
@@ -1,33 +0,0 @@
# EmailValidator v3 Changelog
## New Features
* Access to local part and domain part from EmailParser
* Validations outside of the scope of the RFC will be considered "extra" validations, thus opening the door for adding new; will live in their own folder "extra" (as requested in #248, #195, #183).
## Breaking changes
* PHP version upgraded to match Symfony's (as of 12/2020).
* DNSCheckValidation now fails for missing MX records. While the RFC argues that the existence of only A records to be valid, starting in v3 they will be considered invalid.
* Emails domain part are now intenteded to be RFC 1035 compliant, rendering previous valid emails (e.g example@examp&) invalid.
## PHP versions upgrade policy
PHP version upgrade requirement will happen via MINOR (3.x) version upgrades of the library, following the adoption level by major frameworks.
## Changes
* #235
* #215
* #130
* #258
* #188
* #181
* #217
* #214
* #249
* #236
* #257
* #210
## Thanks
To contributors, be it with PRs, reporting issues or supporting otherwise.
+69 -47
View File
@@ -1,43 +1,48 @@
# EmailValidator
[![Build Status](https://github.com/egulias/EmailValidator/workflows/tests/badge.svg?branch=4.x)](https://github.com/egulias/EmailValidator/actions)
[![Code Quality](https://scrutinizer-ci.com/g/egulias/EmailValidator/badges/quality-score.png?b=3.x)](https://scrutinizer-ci.com/g/egulias/EmailValidator/?branch=4.x)
[![Test Coverage](https://scrutinizer-ci.com/g/egulias/EmailValidator/badges/coverage.png?b=3.x)](https://scrutinizer-ci.com/g/egulias/EmailValidator/?branch=4.x)
[![Build Status](https://github.com/egulias/EmailValidator/actions/workflows/tests.yml/badge.svg)](https://github.com/egulias/EmailValidator/actions/workflows/tests.yml)
[![Quality Badge](https://app.codacy.com/project/badge/Grade/55d44898c7e44ebdb4e457523563ad63)](https://app.codacy.com/gh/egulias/EmailValidator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[![Test Coverage](https://app.codacy.com/project/badge/Coverage/55d44898c7e44ebdb4e457523563ad63)](https://app.codacy.com/gh/egulias/EmailValidator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
![Latest Release](https://img.shields.io/github/v/release/egulias/EmailValidator)
A library for validating emails against several RFC.
## Supported RFCs ##
## Supported RFCs
This library aims to support RFCs:
* [5321](https://tools.ietf.org/html/rfc5321),
* [5322](https://tools.ietf.org/html/rfc5322),
* [6530](https://tools.ietf.org/html/rfc6530),
* [6531](https://tools.ietf.org/html/rfc6531),
* [5321](https://tools.ietf.org/html/rfc5321),
* [5322](https://tools.ietf.org/html/rfc5322),
* [6530](https://tools.ietf.org/html/rfc6530),
* [6531](https://tools.ietf.org/html/rfc6531),
* [6532](https://tools.ietf.org/html/rfc6532),
* [1035](https://tools.ietf.org/html/rfc1035)
## Supported versions
**Current major version with full support is v3**
| Version | Released | EOL | Only critical bug fixes | Full |
|:-------:|:----------:|:---:|:-----------------------:|:----:|
|**v4.x** |**2023/01/07** | - | **X** |**X** |
| v3.x | 2020/12/29 | YES | | |
| v2.1.x | 2016/05/16 | YES | | |
| v1.2 | 2013/19/05 | YES | | |
| Version | Released | EOL | Only critical bug fixes | Full |
| :-----: | :--------: | :---: | :---------------------: | :---: |
| v4.x | 2023/01/07 | - | X | X |
| v3.x | 2020/12/29 | - | X | |
| v2.1.x | 2016/05/16 | YES | | |
| v1.2 | 2013/19/05 | YES | | |
## Requirements
* PHP 8.1
* [Composer](https://getcomposer.org) is required for installation
* [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)
## Requirements ##
**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`
* PHP 8.1
* [Composer](https://getcomposer.org) is required for installation
* [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`
## Installation ##
## Installation
Run the command below to install via Composer
@@ -45,11 +50,14 @@ Run the command below to install via Composer
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).
`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
<?php
@@ -60,17 +68,22 @@ $validator = new EmailValidator();
$validator->isValid("example@example.com", new RFCValidation()); //true
```
### Available validations ###
### Available validations
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 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. [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.
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 entail that the email exists.
4. [MultipleValidationWithAnd](/src/Validation/MultipleValidationWithAnd.php):
It is a validation that operates over other validations performing a logical and (&&) over the result of each validation.
5. [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.
6. [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 accepted.
*warnings: Warnings are deviations from the RFC that in a broader interpretation
are accepted.
```php
<?php
@@ -89,30 +102,39 @@ $multipleValidations = new MultipleValidationWithAnd([
$validator->isValid("example@ietf.org", $multipleValidations); //true
```
#### Additional validations ####
Validations not present in the RFCs
#### Additional validations
1. [SpoofCheckValidation](/src/Validation/Extra/SpoofCheckValidation.php): Will check for multi-utf-8 chars that can signal an erroneous email name.
Validations not present in the RFCs
1. [SpoofCheckValidation](/src/Validation/Extra/SpoofCheckValidation.php):
Will check for multi-utf-8 chars that can signal an erroneous email name.
### How to extend ###
### How to extend
It's easy! You just need to implement [EmailValidation](/src/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.
## Contributing ##
## Contributing
Please follow the [Contribution guide](CONTRIBUTING.md). Is short and simple and will help a lot.
Please follow the [Contribution guide](CONTRIBUTING.md).
Is short and simple and will help a lot.
## Other Contributors ##
## Other Contributors
(You can find current contributors [here](https://github.com/egulias/EmailValidator/graphs/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](https://github.com/ricardclau): Performance against PHP built-in filter_var (v2 and earlier)
* 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
* Ricard Clau [@ricardclau](https://github.com/ricardclau):
Performance against PHP built-in filter_var (v2 and earlier)
* 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.
Released under the MIT License attached with this code.
+2 -2
View File
@@ -18,8 +18,8 @@
"symfony/polyfill-intl-idn": "^1.26"
},
"require-dev": {
"phpunit/phpunit": "^9.5.27",
"vimeo/psalm": "^4.30"
"phpunit/phpunit": "^10.2",
"vimeo/psalm": "^5.12"
},
"suggest": {
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+19 -9
View File
@@ -1,17 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<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" convertDeprecationsToExceptions="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>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
executionOrder="defects"
cacheDirectory=".phpunit.cache"
>
<coverage/>
<testsuites>
<testsuite name="EmailValidator Test Suite">
<directory>./tests/EmailValidator</directory>
<exclude>./vendor/</exclude>
</testsuite>
</testsuites>
<source>
<include>
<directory>./src/</directory>
</include>
<exclude>
<directory suffix=".php">./src/Result/Reason</directory>
</exclude>
</source>
</phpunit>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.4.0@62db5d4f6a7ae0a20f7cc5a4952d730272fc0863">
<file src="src/Parser/DomainPart.php">
<RedundantConditionGivenDocblockType occurrences="1">
<code>null !== $this-&gt;lexer-&gt;token['type']</code>
</RedundantConditionGivenDocblockType>
</file>
</files>
+5 -10
View File
@@ -3,7 +3,11 @@
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"
errorLevel="1"
findUnusedCode="false"
findUnusedPsalmSuppress="true"
disableSuppressAll="true"
findUnusedVariablesAndParams="true"
>
<projectFiles>
<directory name="src" />
@@ -11,13 +15,4 @@
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<DeprecatedMethod>
<errorLevel type="suppress">
<!-- This issue needs to be resolved before upgrading to Lexer 3 -->
<referencedMethod name="Doctrine\Common\Lexer\Token::offsetGet"/>
</errorLevel>
</DeprecatedMethod>
</issueHandlers>
</psalm>
+1 -2
View File
@@ -61,7 +61,7 @@ class EmailLexer extends AbstractLexer
/**
* US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
*
* @var array
* @var array<string, int>
*/
protected $charValue = [
'{' => self::S_OPENCURLYBRACES,
@@ -177,7 +177,6 @@ class EmailLexer extends AbstractLexer
* @throws \UnexpectedValueException
* @return boolean
*
* @psalm-suppress InvalidScalarArgument
*/
public function find($type): bool
{
+2 -2
View File
@@ -56,7 +56,7 @@ class EmailParser extends Parser
$localPartParser = new LocalPart($this->lexer);
$localPartResult = $localPartParser->parse();
$this->localPart = $localPartParser->localPart();
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);
$this->warnings = [...$localPartParser->getWarnings(), ...$this->warnings];
return $localPartResult;
}
@@ -66,7 +66,7 @@ class EmailParser extends Parser
$domainPartParser = new DomainPart($this->lexer);
$domainPartResult = $domainPartParser->parse();
$this->domainPart = $domainPartParser->domainPart();
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);
$this->warnings = [...$domainPartParser->getWarnings(), ...$this->warnings];
return $domainPartResult;
}
+2 -2
View File
@@ -57,7 +57,7 @@ class MessageIDParser extends Parser
$localPartParser = new IDLeftPart($this->lexer);
$localPartResult = $localPartParser->parse();
$this->idLeft = $localPartParser->localPart();
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);
$this->warnings = [...$localPartParser->getWarnings(), ...$this->warnings];
return $localPartResult;
}
@@ -67,7 +67,7 @@ class MessageIDParser extends Parser
$domainPartParser = new IDRightPart($this->lexer);
$domainPartResult = $domainPartParser->parse();
$this->idRight = $domainPartParser->domainPart();
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);
$this->warnings = [...$domainPartParser->getWarnings(), ...$this->warnings];
return $domainPartResult;
}
+5 -6
View File
@@ -66,29 +66,28 @@ class Comment extends PartParser
$finalValidations = $this->commentStrategy->endOfLoopValidations($this->lexer);
$this->warnings = array_merge($this->warnings, $this->commentStrategy->getWarnings());
$this->warnings = [...$this->warnings, ...$this->commentStrategy->getWarnings()];
return $finalValidations;
}
/**
* @return bool
* @return void
*/
private function warnEscaping(): bool
private function warnEscaping(): void
{
//Backslash found
if (!$this->lexer->current->isA(EmailLexer::S_BACKSLASH)) {
return false;
return;
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
return false;
return;
}
$this->warnings[QuotedPart::CODE] =
new QuotedPart($this->lexer->getPrevious()->type, $this->lexer->current->type);
return true;
}
private function noClosingParenthesis(): bool
+1 -5
View File
@@ -12,11 +12,7 @@ class DomainComment implements CommentStrategy
{
public function exitCondition(EmailLexer $lexer, int $openedParenthesis): bool
{
if (($openedParenthesis === 0 && $lexer->isNextToken(EmailLexer::S_DOT))) { // || !$internalLexer->moveNext()) {
return false;
}
return true;
return !($openedParenthesis === 0 && $lexer->isNextToken(EmailLexer::S_DOT));
}
public function endOfLoopValidations(EmailLexer $lexer): Result
+2 -1
View File
@@ -8,11 +8,12 @@ use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Warning\CFWSNearAt;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
use Egulias\EmailValidator\Warning\Warning;
class LocalComment implements CommentStrategy
{
/**
* @var array
* @var array<int, Warning>
*/
private $warnings = [];
+2 -2
View File
@@ -82,10 +82,10 @@ class DomainLiteral extends PartParser
if (!$isAddressLiteralIPv4) {
return new ValidEmail();
} else {
$addressLiteral = $this->convertIPv4ToIPv6($addressLiteral);
}
$addressLiteral = $this->convertIPv4ToIPv6($addressLiteral);
if (!$IPv6TAG) {
$this->warnings[WarningDomainLiteral::CODE] = new WarningDomainLiteral();
return new ValidEmail();
+7 -6
View File
@@ -137,7 +137,7 @@ class DomainPart extends PartParser
{
$commentParser = new Comment($this->lexer, new DomainComment());
$result = $commentParser->parse();
$this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
$this->warnings = [...$this->warnings, ...$commentParser->getWarnings()];
return $result;
}
@@ -213,9 +213,9 @@ class DomainPart extends PartParser
return new ValidEmail();
}
/**
/**
* @param Token<int, string> $token
*
*
* @return Result
*/
private function checkNotAllowedChars(Token $token): Result
@@ -240,14 +240,14 @@ class DomainPart extends PartParser
$domainLiteralParser = new DomainLiteralParser($this->lexer);
$result = $domainLiteralParser->parse();
$this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings());
$this->warnings = [...$this->warnings, ...$domainLiteralParser->getWarnings()];
return $result;
}
/**
* @param Token<int, string> $prev
* @param bool $hasComments
*
*
* @return Result
*/
protected function checkDomainPartExceptions(Token $prev, bool $hasComments): Result
@@ -307,6 +307,7 @@ class DomainPart extends PartParser
{
if (preg_match('/[^\x00-\x7F]/', $label)) {
idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
/** @psalm-var array{errors: int, ...} $idnaInfo */
return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
}
return strlen($label) > self::LABEL_MAX_LENGTH;
@@ -323,4 +324,4 @@ class DomainPart extends PartParser
{
return $this->domainPart;
}
}
}
+6 -2
View File
@@ -17,7 +17,9 @@ class DoubleQuote extends PartParser
{
$validQuotedString = $this->checkDQUOTE();
if ($validQuotedString->isInvalid()) return $validQuotedString;
if ($validQuotedString->isInvalid()) {
return $validQuotedString;
}
$special = [
EmailLexer::S_CR => true,
@@ -56,7 +58,9 @@ class DoubleQuote extends PartParser
if ($prev->isA(EmailLexer::S_BACKSLASH)) {
$validQuotedString = $this->checkDQUOTE();
if ($validQuotedString->isInvalid()) return $validQuotedString;
if ($validQuotedString->isInvalid()) {
return $validQuotedString;
}
}
if (!$this->lexer->isNextToken(EmailLexer::S_AT) && !$prev->isA(EmailLexer::S_BACKSLASH)) {
+4 -10
View File
@@ -118,7 +118,7 @@ class LocalPart extends PartParser
$foldingWS = new FoldingWhiteSpace($this->lexer);
$resultFWS = $foldingWS->parse();
if ($resultFWS->isValid()) {
$this->warnings = array_merge($this->warnings, $foldingWS->getWarnings());
$this->warnings = [...$this->warnings, ...$foldingWS->getWarnings()];
}
return $resultFWS;
}
@@ -132,7 +132,7 @@ class LocalPart extends PartParser
{
$dquoteParser = new DoubleQuote($this->lexer);
$parseAgain = $dquoteParser->parse();
$this->warnings = array_merge($this->warnings, $dquoteParser->getWarnings());
$this->warnings = [...$this->warnings, ...$dquoteParser->getWarnings()];
return $parseAgain;
}
@@ -141,10 +141,8 @@ class LocalPart extends PartParser
{
$commentParser = new Comment($this->lexer, new LocalComment());
$result = $commentParser->parse();
$this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
if ($result->isInvalid()) {
return $result;
}
$this->warnings = [...$this->warnings, ...$commentParser->getWarnings()];
return $result;
}
@@ -159,10 +157,6 @@ class LocalPart extends PartParser
return new InvalidEmail(new ExpectingATEXT('Found ATOM after escaping'), $this->lexer->current->value);
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
return new ValidEmail();
}
return new ValidEmail();
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ abstract class PartParser
{
$foldingWS = new FoldingWhiteSpace($this->lexer);
$resultFWS = $foldingWS->parse();
$this->warnings = array_merge($this->warnings, $foldingWS->getWarnings());
$this->warnings = [...$this->warnings, ...$foldingWS->getWarnings()];
return $resultFWS;
}
+1 -1
View File
@@ -7,7 +7,7 @@ class UnusualElements implements Reason
/**
* @var string $element
*/
private $element = '';
private $element;
public function __construct(string $element)
{
+21 -7
View File
@@ -13,10 +13,6 @@ use Egulias\EmailValidator\Warning\Warning;
class DNSCheckValidation implements EmailValidation
{
/**
* @var int
*/
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),
@@ -123,9 +119,20 @@ class DNSCheckValidation implements EmailValidation
{
$variant = INTL_IDNA_VARIANT_UTS46;
$host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.';
$host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.');
return $this->validateDnsRecords($host);
$hostParts = explode('.', $host);
$host = array_pop($hostParts);
while (count($hostParts) > 0) {
$host = array_pop($hostParts) . '.' . $host;
if ($this->validateDnsRecords($host)) {
return true;
}
}
return false;
}
@@ -138,7 +145,7 @@ class DNSCheckValidation implements EmailValidation
*/
private function validateDnsRecords($host): bool
{
$dnsRecordsResult = $this->dnsGetRecord->getRecords($host, static::DNS_RECORD_TYPES_TO_CHECK);
$dnsRecordsResult = $this->dnsGetRecord->getRecords($host, DNS_A + DNS_MX);
if ($dnsRecordsResult->withError()) {
$this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
@@ -147,6 +154,13 @@ class DNSCheckValidation implements EmailValidation
$dnsRecords = $dnsRecordsResult->getRecords();
// Combined check for A+MX+AAAA can fail with SERVFAIL, even in the presence of valid A/MX records
$aaaaRecordsResult = $this->dnsGetRecord->getRecords($host, DNS_AAAA);
if (! $aaaaRecordsResult->withError()) {
$dnsRecords = array_merge($dnsRecords, $aaaaRecordsResult->getRecords());
}
// No MX, A or AAAA DNS records
if ($dnsRecords === []) {
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
+1 -2
View File
@@ -7,13 +7,12 @@ class DNSGetRecordWrapper
/**
* @param string $host
* @param int $type
*
*
* @return DNSRecords
*/
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): never {
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
+3 -16
View File
@@ -4,29 +4,16 @@ namespace Egulias\EmailValidator\Validation;
class DNSRecords
{
/**
* @var array $records
*/
private $records = [];
/**
* @var bool $error
*/
private $error = false;
/**
* @param array $records
* @param list<array<array-key, mixed>> $records
* @param bool $error
*/
public function __construct(array $records, bool $error = false)
public function __construct(private readonly array $records, private readonly bool $error = false)
{
$this->records = $records;
$this->error = $error;
}
/**
* @return array
* @return list<array<array-key, mixed>>
*/
public function getRecords(): array
{
@@ -22,9 +22,6 @@ class SpoofCheckValidation implements EmailValidation
}
}
/**
* @psalm-suppress InvalidArgument
*/
public function isValid(string $email, EmailLexer $emailLexer) : bool
{
$checker = new Spoofchecker();
@@ -37,9 +34,6 @@ class SpoofCheckValidation implements EmailValidation
return $this->error === null;
}
/**
* @return InvalidEmail
*/
public function getError() : ?InvalidEmail
{
return $this->error;
+2 -15
View File
@@ -22,11 +22,6 @@ class MultipleValidationWithAnd implements EmailValidation
*/
public const ALLOW_ALL_ERRORS = 1;
/**
* @var EmailValidation[]
*/
private $validations = [];
/**
* @var Warning[]
*/
@@ -37,23 +32,15 @@ class MultipleValidationWithAnd implements EmailValidation
*/
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)
public function __construct(private readonly array $validations, private readonly int $mode = self::ALLOW_ALL_ERRORS)
{
if (count($validations) == 0) {
throw new EmptyValidationList();
}
$this->validations = $validations;
$this->mode = $mode;
}
/**
@@ -66,7 +53,7 @@ class MultipleValidationWithAnd implements EmailValidation
$emailLexer->reset();
$validationResult = $validation->isValid($email, $emailLexer);
$result = $result && $validationResult;
$this->warnings = array_merge($this->warnings, $validation->getWarnings());
$this->warnings = [...$this->warnings, ...$validation->getWarnings()];
if (!$validationResult) {
$this->processError($validation);
}
+3 -8
View File
@@ -10,11 +10,6 @@ use Egulias\EmailValidator\Warning\Warning;
class RFCValidation implements EmailValidation
{
/**
* @var EmailParser|null
*/
private $parser;
/**
* @var Warning[]
*/
@@ -27,10 +22,10 @@ class RFCValidation implements EmailValidation
public function isValid(string $email, EmailLexer $emailLexer): bool
{
$this->parser = new EmailParser($emailLexer);
$parser = new EmailParser($emailLexer);
try {
$result = $this->parser->parse($email);
$this->warnings = $this->parser->getWarnings();
$result = $parser->parse($email);
$this->warnings = $parser->getWarnings();
if ($result->isInvalid()) {
/** @psalm-suppress PropertyTypeCoercion */
$this->error = $result;
+12 -2
View File
@@ -2,16 +2,26 @@
namespace Egulias\EmailValidator\Warning;
use UnitEnum;
class QuotedPart extends Warning
{
public const CODE = 36;
/**
* @param scalar|null $prevToken
* @param scalar|null $postToken
* @param UnitEnum|string|int|null $prevToken
* @param UnitEnum|string|int|null $postToken
*/
public function __construct($prevToken, $postToken)
{
if ($prevToken instanceof UnitEnum) {
$prevToken = $prevToken->name;
}
if ($postToken instanceof UnitEnum) {
$postToken = $postToken->name;
}
$this->message = "Deprecated Quoted String found between $prevToken and $postToken";
}
}
+2 -2
View File
@@ -7,8 +7,8 @@ class QuotedString extends Warning
public const CODE = 11;
/**
* @param scalar $prevToken
* @param scalar $postToken
* @param string|int $prevToken
* @param string|int $postToken
*/
public function __construct($prevToken, $postToken)
{
+1 -1
View File
@@ -48,6 +48,6 @@ abstract class Warning
*/
public function __toString(): string
{
return $this->message() . " rfc: " . $this->rfcNumber . "internal code: " . strval(static::CODE);
return $this->message() . " rfc: " . $this->rfcNumber . "internal code: " . static::CODE;
}
}
+4 -4
View File
@@ -51,11 +51,11 @@ class EmailLexerTest extends TestCase
$this->assertEquals(EmailLexer::INVALID, $lexer->current->type);
}
public function invalidUTF8CharsProvider()
public static function invalidUTF8CharsProvider()
{
$chars = array();
for ($i = 0; $i < 0x100; ++$i) {
$c = $this->utf8Chr($i);
$c = self::utf8Chr($i);
if (preg_match('/(?=\p{Cc})(?=[^\t\n\n\r])/u', $c) && !preg_match('/\x{0000}/u', $c)) {
$chars[] = array($c);
}
@@ -64,7 +64,7 @@ class EmailLexerTest extends TestCase
return $chars;
}
protected function utf8Chr($code_point)
protected static function utf8Chr($code_point)
{
if ($code_point < 0 || 0x10FFFF < $code_point || (0xD800 <= $code_point && $code_point <= 0xDFFF)) {
@@ -123,7 +123,7 @@ class EmailLexerTest extends TestCase
$this->assertTrue($lexer->find(EmailLexer::S_HTAB));
}
public function getTokens()
public static function getTokens()
{
return array(
array("foo", EmailLexer::GENERIC),
+1 -1
View File
@@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase;
class EmailParserTest extends TestCase
{
public function emailPartsProvider()
public static function emailPartsProvider()
{
return [
['test@foo.com', 'test', 'foo.com'],
@@ -16,11 +16,12 @@ use PHPUnit\Framework\TestCase;
class DNSCheckValidationTest extends TestCase
{
public function validEmailsProvider()
public static function validEmailsProvider()
{
return [
// dot-atom
['Abc@ietf.org'],
['Abc@fake.ietf.org'],
['ABC@ietf.org'],
['Abc.123@ietf.org'],
['user+mailbox/department=shipping@ietf.org'],
@@ -31,12 +32,13 @@ class DNSCheckValidationTest extends TestCase
['"Fred\ Bloggs"@ietf.org'],
['"Joe.\\Blow"@ietf.org'],
// unicide
['ñandu.cl'],
// unicode
['info@ñandu.cl'],
['ñandu@ñandu.cl'],
];
}
public function localOrReservedEmailsProvider()
public static function localOrReservedEmailsProvider()
{
return [
// Reserved Top Level DNS Names
@@ -149,4 +151,4 @@ class DNSCheckValidationTest extends TestCase
$validation->isValid('example@invalid.example.com', new EmailLexer());
$this->assertEquals($expectedError, $validation->getError());
}
}
}
@@ -25,7 +25,7 @@ class SpoofCheckValidationTest extends TestCase
$this->assertFalse($validation->isValid("Кириллица"."latin漢字"."ひらがな"."カタカナ", new EmailLexer()));
}
public function validUTF8EmailsProvider()
public static function validUTF8EmailsProvider()
{
return [
// Cyrillic
@@ -19,7 +19,7 @@ class MessageIDValidationTest extends TestCase
$this->assertTrue($validator->isValid($messageID, new EmailLexer()));
}
public function validMessageIDs() : array
public static function validMessageIDs() : array
{
return [
['a@b.c+&%$.d'],
@@ -38,7 +38,7 @@ class MessageIDValidationTest extends TestCase
$this->assertFalse($validator->isValid($messageID, new EmailLexer()));
}
public function invalidMessageIDs() : array
public static function invalidMessageIDs() : array
{
return [
['example'],
@@ -38,7 +38,7 @@ class NoRFCWarningsValidationTest extends TestCase
$this->assertNull($validation->getError());
}
public function getValidEmailsWithoutWarnings()
public static function getValidEmailsWithoutWarnings()
{
return [
['example@example.com',],
@@ -62,7 +62,7 @@ class RFCValidationDomainPartTest extends TestCase
$this->assertTrue($this->validator->isValid($email, $this->lexer));
}
public function getValidEmails()
public static function getValidEmails()
{
return array(
['fabien@symfony.com'],
@@ -88,7 +88,7 @@ class RFCValidationDomainPartTest extends TestCase
$this->assertFalse($this->validator->isValid($email, $this->lexer));
}
public function getInvalidEmails()
public static function getInvalidEmails()
{
return [
['test@example.com test'],
@@ -161,7 +161,7 @@ class RFCValidationDomainPartTest extends TestCase
$this->assertEquals($error, $this->validator->getError());
}
public function getInvalidEmailsWithErrors()
public static function getInvalidEmailsWithErrors()
{
return [
[new InvalidEmail(new NoDomainPart(), ''), 'example@'],
@@ -203,7 +203,7 @@ class RFCValidationDomainPartTest extends TestCase
}
}
public function getValidEmailsWithWarnings()
public static function getValidEmailsWithWarnings()
{
return [
//Check if this is actually possible
@@ -232,7 +232,7 @@ class RFCValidationDomainPartTest extends TestCase
];
}
public function invalidUTF16Chars()
public static function invalidUTF16Chars()
{
return [
['example@symƒony.com'],
@@ -54,7 +54,7 @@ class RFCValidationTest extends TestCase
$this->assertTrue($this->validator->isValid($email, $this->lexer));
}
public function getValidEmails()
public static function getValidEmails()
{
return array(
['â@iana.org'],
@@ -91,7 +91,7 @@ class RFCValidationTest extends TestCase
$this->assertEquals($expectedWarnings, $this->validator->getWarnings());
}
public function getValidEmailsWithWarnings()
public static function getValidEmailsWithWarnings()
{
return [
['a5aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@example.com', [new LocalTooLong()]],
@@ -120,7 +120,7 @@ class RFCValidationTest extends TestCase
$this->assertFalse($this->validator->isValid($email, $this->lexer));
}
public function getInvalidEmails()
public static function getInvalidEmails()
{
return [
['user name@example.com'],
@@ -164,7 +164,7 @@ class RFCValidationTest extends TestCase
$this->assertEquals($error, $this->validator->getError());
}
public function getInvalidEmailsWithErrors()
public static function getInvalidEmailsWithErrors()
{
return [
[new InvalidEmail(new NoLocalPart(), "@"), '@example.co.uk'],