Passed
Push — master ( 0f05d2...660106 )
by Alexander
08:58
created

Email::enableIDN()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 4
cts 5
cp 0.8
crap 3.072
rs 10
1
<?php
2
3
namespace Yiisoft\Validator\Rule;
4
5
use Yiisoft\Validator\DataSetInterface;
6
use Yiisoft\Validator\Result;
7
use Yiisoft\Validator\Rule;
8
9
/**
10
 * EmailValidator validates that the attribute value is a valid email address.
11
 */
12
class Email extends Rule
13
{
14
    /**
15
     * @var string the regular expression used to validateValue the attribute value.
16
     * @see http://www.regular-expressions.info/email.html
17
     */
18
    private string $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
19
    /**
20
     * @var string the regular expression used to validateValue email addresses with the name part.
21
     * This property is used only when [[allowName]] is true.
22
     * @see allowName
23
     */
24
    private string $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
25
    /**
26
     * @var bool whether to allow name in the email address (e.g. "John Smith <[email protected]>"). Defaults to false.
27
     * @see fullPattern
28
     */
29
    private bool $allowName = false;
30
    /**
31
     * @var bool whether to check whether the email's domain exists and has either an A or MX record.
32
     * Be aware that this check can fail due to temporary DNS problems even if the email address is
33
     * valid and an email would be deliverable. Defaults to false.
34
     */
35
    private bool $checkDNS = false;
36
    /**
37
     * @var bool whether validation process should take into account IDN (internationalized domain
38
     * names). Defaults to false meaning that validation of emails containing IDN will always fail.
39
     * Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
40
     * otherwise an exception would be thrown.
41
     */
42
    private bool $enableIDN = false;
43
44
45
    private string $message = '{attribute} is not a valid email address.';
46
47 36
    protected function validateValue($value, DataSetInterface $dataSet = null): Result
48
    {
49 36
        $result = new Result();
50
51 36
        if (!is_string($value)) {
52 1
            $valid = false;
53 36
        } elseif (!preg_match('/^(?P<name>(?:"?([^"]*)"?\s)?)(?:\s+)?(?:(?P<open><?)((?P<local>.+)@(?P<domain>[^>]+))(?P<close>>?))$/i', $value, $matches)) {
54 2
            $valid = false;
55
        } else {
56 36
            if ($this->enableIDN) {
57 35
                $matches['local'] = $this->idnToAscii($matches['local']);
58 35
                $matches['domain'] = $this->idnToAscii($matches['domain']);
59 35
                $value = $matches['name'] . $matches['open'] . $matches['local'] . '@' . $matches['domain'] . $matches['close'];
60
            }
61
62 36
            if (strlen($matches['local']) > 64) {
63
                // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1
64
                // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
65 1
                $valid = false;
66 36
            } elseif (strlen($matches['local'] . '@' . $matches['domain']) > 254) {
67
                // There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands
68
                // of 254 characters. Since addresses that do not fit in those fields are not normally useful, the
69
                // upper limit on address lengths should normally be considered to be 254.
70
                //
71
                // Dominic Sayers, RFC 3696 erratum 1690
72
                // http://www.rfc-editor.org/errata_search.php?eid=1690
73 1
                $valid = false;
74
            } else {
75 36
                $valid = preg_match($this->pattern, $value) || ($this->allowName && preg_match($this->fullPattern, $value));
76 36
                if ($valid && $this->checkDNS) {
77
                    $valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A');
78
                }
79
            }
80
        }
81
82
83 36
        if ($valid === false) {
84 36
            $result->addError($this->formatMessage($this->message));
85
        }
86
87 36
        return $result;
88
    }
89
90 35
    private function idnToAscii($idn)
91
    {
92 35
        return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
93
    }
94
95
    /**
96
     * @param bool $allowName
97
     *
98
     * @return Email
99
     */
100 2
    public function allowName(bool $allowName): self
101
    {
102 2
        $this->allowName = $allowName;
103
104 2
        return $this;
105
    }
106
107
    /**
108
     * @param bool $checkDNS
109
     *
110
     * @return Email
111
     */
112
    public function checkDNS(bool $checkDNS): self
113
    {
114
        $this->checkDNS = $checkDNS;
115
116
        return $this;
117
    }
118
119
    /**
120
     * @param bool $enableIDN
121
     *
122
     * @return Email
123
     */
124 35
    public function enableIDN(bool $enableIDN): self
125
    {
126 35
        if ($enableIDN && !function_exists('idn_to_ascii')) {
127
            throw new \RuntimeException('In order to use IDN validation intl extension must be installed and enabled.');
128
        }
129
130 35
        $this->enableIDN = $enableIDN;
131
132 35
        return $this;
133
    }
134
135
    public function message(string $message): self
136
    {
137
        $this->message = $message;
138
139
        return $this;
140
    }
141
}
142