Passed
Pull Request — master (#250)
by
unknown
02:31
created

DNSCheckValidation::checkDns()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Egulias\EmailValidator\Validation;
4
5
use Egulias\EmailValidator\EmailLexer;
6
use Egulias\EmailValidator\Exception\InvalidEmail;
7
use Egulias\EmailValidator\Exception\LocalOrReservedDomain;
8
use Egulias\EmailValidator\Exception\DomainAcceptsNoMail;
9
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
10
use Egulias\EmailValidator\Exception\NoDNSRecord;
11
12
class DNSCheckValidation implements EmailValidation
13
{
14
    /**
15
     * @var array
16
     */
17
    private $warnings = [];
18
19
    /**
20
     * @var InvalidEmail|null
21
     */
22
    private $error;
23
24
    /**
25
     * @var array
26
     */
27
    private $mxRecords = [];
28
29
30 23
    public function __construct()
31
    {
32 23
        if (!function_exists('idn_to_ascii')) {
33
            throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
34
        }
35 23
    }
36
37 23
    public function isValid($email, EmailLexer $emailLexer)
38
    {
39
        // use the input to check DNS if we cannot extract something similar to a domain
40 23
        $host = $email;
41
42
        // Arguable pattern to extract the domain. Not aiming to validate the domain nor the email
43 23
        if (false !== $lastAtPos = strrpos($email, '@')) {
44 11
            $host = substr($email, $lastAtPos + 1);
45 11
        }
46
47
        // Get the domain parts
48 23
        $hostParts = explode('.', $host);
49
50
        // Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
51
        // mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
52
        $reservedTopLevelDnsNames = [
53
            // Reserved Top Level DNS Names
54 23
            'test',
55 23
            'example',
56 23
            'invalid',
57 23
            'localhost',
58
59
            // mDNS
60 23
            'local',
61
62
            // Private DNS Namespaces
63 23
            'intranet',
64 23
            'internal',
65 23
            'private',
66 23
            'corp',
67 23
            'home',
68 23
            'lan',
69 23
        ];
70
71 23
        $isLocalDomain = count($hostParts) <= 1;
72 23
        $isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], $reservedTopLevelDnsNames);
73
74
        // Exclude reserved top level DNS names
75 23
        if ($isLocalDomain || $isReservedTopLevel) {
76 11
            $this->error = new LocalOrReservedDomain();
77 11
            return false;
78
        }
79
80 12
        return $this->checkDns($host);
81
    }
82
83 13
    public function getError()
84
    {
85 13
        return $this->error;
86
    }
87
88
    public function getWarnings()
89
    {
90
        return $this->warnings;
91
    }
92
93
    /**
94
     * @param string $host
95
     *
96
     * @return bool
97
     */
98 12
    protected function checkDns($host)
99
    {
100 12
        $variant = INTL_IDNA_VARIANT_2003;
101 12
        if (defined('INTL_IDNA_VARIANT_UTS46')) {
102 12
            $variant = INTL_IDNA_VARIANT_UTS46;
103 12
        }
104
105 12
        $host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.';
106
107 12
        return $this->validateDnsRecords($host);
108
    }
109
110
111
    /**
112
     * Validate the DNS records
113
     *
114
     * @param array $dnsRecords A set of DNS records in the format returned by dns_get_record.
0 ignored issues
show
Bug introduced by
There is no parameter named $dnsRecords. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
115
     *
116
     * @return bool True on success.
117
     */
118 12
    private function validateDnsRecords($host)
119
    {
120
        // Get all MX, A and AAAA DNS records for host
121 12
        $dnsRecords = dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA);
122
123
124
        // No MX, A or AAAA DNS records
125 12
        if (empty($dnsRecords)) {
126 2
            $this->error = new NoDNSRecord();
127 2
            return false;
128
        }
129
130
        // For each DNS record
131 10
        foreach ($dnsRecords as $dnsRecord) {
132 10
            if (!$this->validateMXRecord($dnsRecord)) {
133 1
                return false;
134
            }
135 10
        }
136
137
        // No MX records (fallback to A or AAAA records)
138 9
        if (empty($this->mxRecords)) {
139
            $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
140
        }
141
142 9
        return true;
143
    }
144
145
    /**
146
     * Validate an MX record
147
     *
148
     * @param array $dnsRecord A DNS record.
149
     *
150
     * @return bool True if valid.
151
     */
152 10
    private function validateMxRecord($dnsRecord)
153
    {
154 10
        if ($dnsRecord['type'] != 'MX') {
155 10
            return true;
156
        }
157
158
        // "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505)
159 10
        if (empty($dnsRecord['target']) || $dnsRecord['target'] == '.') {
160 1
            $this->error = new DomainAcceptsNoMail();
161 1
            return false;
162
        }
163
164 9
        $this->mxRecords[] = $dnsRecord;
165
166 9
        return true;
167
    }
168
}
169