Use a safe way to execute dns_get_record (v3) (#286)

* #256 Bypass a PHP warning generated by @dns_get_record

The idea is to create a custom error handler for dns_get_record(), call dns_get_record and then restore the original error handler.
This way we can catch PHP warnings (e.g. "Warning: dns_get_record(): DNS Query failed" or "dns_get_record(): A temporary server error occurred." [ErrorException])

* #256 Make `UnableToGetDNSRecord` reason more testable

* #256 Suppress psalm’s false-positive error

see https://github.com/vimeo/psalm/issues/5134#issuecomment-782937791

* #256 Speed up tests: start from previously failed

* #256 Suppress psalm’s false-positive error

We don’t have a control over AbstractLexer and can’t specify array shape in this parent class
This commit is contained in:
Alies Lapatsin
2021-02-28 17:27:57 +03:00
committed by GitHub
parent 04cad1a5ec
commit 451b438902
7 changed files with 75 additions and 12 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ build:
tests:
override:
-
command: 'vendor/bin/phpunit --coverage-clover=clover.xml'
command: 'vendor/bin/phpunit --coverage-clover=clover.xml --exclude-group slow'
coverage:
file: 'clover.xml'
format: 'clover'
+1 -1
View File
@@ -38,7 +38,7 @@ before_script:
- mkdir -p build/logs
script:
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml --exclude-group slow
- if [ "$psalm" = "yes" ]; then vendor/bin/psalm; fi
after_script:
+5 -2
View File
@@ -1,14 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
<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"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="EmailValidator Test Suite">
+1
View File
@@ -125,6 +125,7 @@ class EmailLexer extends AbstractLexer
* @var array
*
* @psalm-var array{value:string, type:null|int, position:int}
* @psalm-suppress NonInvariantDocblockPropertyType
*/
public $token;
@@ -0,0 +1,19 @@
<?php
namespace Egulias\EmailValidator\Result\Reason;
/**
* Used on SERVFAIL, TIMEOUT or other runtime and network errors
*/
class UnableToGetDNSRecord extends NoDNSRecord
{
public function code() : int
{
return 3;
}
public function description() : string
{
return 'Unable to get DNS records for the host';
}
}
+25 -4
View File
@@ -5,12 +5,18 @@ namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
use Egulias\EmailValidator\Result\Reason\LocalOrReservedDomain;
use Egulias\EmailValidator\Result\Reason\NoDNSRecord as ReasonNoDNSRecord;
use Egulias\EmailValidator\Result\Reason\UnableToGetDNSRecord;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
class DNSCheckValidation implements EmailValidation
{
/**
* @var int
*/
protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA;
/**
* @var array
*/
@@ -114,12 +120,27 @@ class DNSCheckValidation implements EmailValidation
*/
private function validateDnsRecords($host) : bool
{
// Get all MX, A and AAAA DNS records for host
$dnsRecords = @dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA);
// 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
$dnsRecords = dns_get_record($host, static::DNS_RECORD_TYPES_TO_CHECK);
} catch (\RuntimeException $exception) {
$this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
return false;
} finally {
restore_error_handler();
}
// No MX, A or AAAA DNS records
if (empty($dnsRecords)) {
if ($dnsRecords === [] || $dnsRecords === false) {
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
return false;
}
@@ -2,14 +2,15 @@
namespace Egulias\EmailValidator\Tests\EmailValidator\Validation;
use PHPUnit\Framework\TestCase;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
use Egulias\EmailValidator\Validation\DNSCheckValidation;
use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail;
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\Warning\NoDNSMXRecord;
use PHPUnit\Framework\TestCase;
class DNSCheckValidationTest extends TestCase
{
@@ -92,7 +93,7 @@ class DNSCheckValidationTest extends TestCase
public function testDNSWarnings()
{
$this->markTestSkipped('Need to found a domain with AAAA redords and no MX that fails later in the validations');
$this->markTestSkipped('Need to found a domain with AAAA records and no MX that fails later in the validations');
$validation = new DNSCheckValidation();
$expectedWarnings = [NoDNSMXRecord::CODE => new NoDNSMXRecord()];
$validation->isValid("example@invalid.example.com", new EmailLexer());
@@ -106,4 +107,22 @@ class DNSCheckValidationTest extends TestCase
$validation->isValid("example@invalid.example.com", new EmailLexer());
$this->assertEquals($expectedError, $validation->getError());
}
/**
* @group slow
*/
public function testUnableToGetDNSRecord()
{
error_reporting(\E_ALL);
// UnableToGetDNSRecord raises on network errors (e.g. timeout) that we cant 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;
};
$expectedError = new InvalidEmail(new UnableToGetDNSRecord(), '');
$validation->isValid('example@invalid.example.com', new EmailLexer());
$this->assertEquals($expectedError, $validation->getError());
}
}