Passed
Pull Request — master (#250)
by
unknown
02:34
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
        // Exclude reserved top level DNS names
72 23
        if (count($hostParts) <= 1 || in_array($hostParts[(count($hostParts) - 1)], $reservedTopLevelDnsNames)) {
73 11
            $this->error = new LocalOrReservedDomain();
74 11
            return false;
75
        }
76
77 12
        return $this->checkDns($host);
78
    }
79
80 13
    public function getError()
81
    {
82 13
        return $this->error;
83
    }
84
85
    public function getWarnings()
86
    {
87
        return $this->warnings;
88
    }
89
90
    /**
91
     * @param string $host
92
     *
93
     * @return bool
94
     */
95 12
    protected function checkDns($host)
96
    {
97 12
        $variant = INTL_IDNA_VARIANT_2003;
98 12
        if (defined('INTL_IDNA_VARIANT_UTS46')) {
99 12
            $variant = INTL_IDNA_VARIANT_UTS46;
100 12
        }
101
102 12
        $host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.';
103
104 12
        return $this->validateDnsRecords($host);
105
    }
106
107
108
    /**
109
     * Validate the DNS records
110
     *
111
     * @param $dnsRecords A set of DNS records in the format returned by dns_get_record.
112
     *
113
     * @return bool True on success.
114
     */
115 12
    private function validateDnsRecords($host)
116
    {
117
        // Get all MX, A and AAAA DNS records for host
118 12
        $dnsRecords = dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA);
119
120
121
        // No MX, A or AAAA DNS records
122 12
        if (empty($dnsRecords)) {
123 2
            $this->error = new NoDNSRecord();
124 2
            return false;
125
        }
126
127
        // For each DNS record
128 10
        foreach ($dnsRecords as $dnsRecord) {
129 10
            if (!$this->validateMXRecord($dnsRecord)) {
130 1
                return false;
131
            }
132 10
        }
133
134
        // No MX records (fallback to A or AAAA records)
135 9
        if (empty($this->mxRecords)) {
136
            $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
137
        }
138
139 9
        return true;
140
    }
141
142
    /**
143
     * Validate an MX record
144
     *
145
     * @param $dnsRecord A DNS record.
146
     */
147 10
    private function validateMxRecord($dnsRecord)
148
    {
149 10
        if ($dnsRecord['type'] != 'MX') {
150 10
            return true;
151
        }
152
153
        // "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505)
154 10
        if (empty($dnsRecord['target']) || $dnsRecord['target'] == '.') {
155 1
            $this->error = new DomainAcceptsNoMail();
156 1
            return false;
157
        }
158
159 9
        $this->mxRecords[] = $dnsRecord;
160
161 9
        return true;
162
    }
163
}
164