DomainNameValidator::hasValidLabelBoundaries()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EmailValidator\Validator\Domain;
6
7
/**
8
 * Validates domain names according to RFC 5322 standards
9
 */
10
class DomainNameValidator
11
{
12
    private const MAX_DOMAIN_LABEL_LENGTH = 63;
13
    private const MAX_DOMAIN_LENGTH = 255;
14
15
    /**
16
     * Validates a domain name
17
     *
18
     * @param string $domain The domain name to validate
19
     * @return bool True if the domain name is valid
20
     */
21
    public function validate(string $domain): bool
22
    {
23
        // Check for empty domain
24
        if ($domain === '') {
25
            return false;
26
        }
27
28
        // Check total length
29
        if (!$this->validateLength($domain)) {
30
            return false;
31
        }
32
33
        // Split into labels
34
        $labels = explode('.', $domain);
35
36
        // Must have at least two labels
37
        if (count($labels) < 2) {
38
            return false;
39
        }
40
41
        // Validate each label
42
        foreach ($labels as $label) {
43
            if (!$this->validateLabel($label)) {
44
                return false;
45
            }
46
        }
47
48
        return true;
49
    }
50
51
    /**
52
     * Validates the length of a domain
53
     *
54
     * @param string $domain The domain to validate
55
     * @return bool True if the length is valid
56
     */
57
    private function validateLength(string $domain): bool
58
    {
59
        return strlen($domain) <= self::MAX_DOMAIN_LENGTH;
60
    }
61
62
    /**
63
     * Validates a single domain label
64
     *
65
     * @param string $label The domain label to validate
66
     * @return bool True if the domain label is valid
67
     */
68
    private function validateLabel(string $label): bool
69
    {
70
        // Check length
71
        if (!$this->validateLabelLength($label)) {
72
            return false;
73
        }
74
75
        // Handle IDN labels (starting with 'xn--')
76
        if (substr($label, 0, 4) === 'xn--') {
77
            return $this->validateIDNLabel($label);
78
        }
79
80
        // Single character labels are allowed if they're alphanumeric
81
        if (strlen($label) === 1) {
82
            return ctype_alnum($label);
83
        }
84
85
        // Must start and end with alphanumeric
86
        if (!$this->hasValidLabelBoundaries($label)) {
87
            return false;
88
        }
89
90
        // Check for valid characters and format
91
        if (!$this->hasValidLabelFormat($label)) {
92
            return false;
93
        }
94
95
        // Check for consecutive hyphens
96
        return !$this->hasConsecutiveHyphens($label);
97
    }
98
99
    /**
100
     * Validates the length of a domain label
101
     *
102
     * @param string $label The domain label to validate
103
     * @return bool True if the length is valid
104
     */
105
    private function validateLabelLength(string $label): bool
106
    {
107
        return strlen($label) <= self::MAX_DOMAIN_LABEL_LENGTH && $label !== '';
108
    }
109
110
    /**
111
     * Validates an IDN (Internationalized Domain Name) label
112
     *
113
     * @param string $label The IDN label to validate
114
     * @return bool True if the IDN label is valid
115
     */
116
    private function validateIDNLabel(string $label): bool
117
    {
118
        // Must be at least 5 characters (xn-- plus at least one character)
119
        if (strlen($label) < 5) {
120
            return false;
121
        }
122
123
        // Rest of the label must be alphanumeric or hyphen
124
        $rest = substr($label, 4);
125
        return (bool)preg_match('/^[a-zA-Z0-9-]+$/', $rest);
126
    }
127
128
    /**
129
     * Checks if a domain label has valid start and end characters
130
     *
131
     * @param string $label The domain label to validate
132
     * @return bool True if the boundaries are valid
133
     */
134
    private function hasValidLabelBoundaries(string $label): bool
135
    {
136
        return ctype_alnum($label[0]) && ctype_alnum(substr($label, -1));
137
    }
138
139
    /**
140
     * Checks if a domain label has valid format
141
     *
142
     * @param string $label The domain label to validate
143
     * @return bool True if the format is valid
144
     */
145
    private function hasValidLabelFormat(string $label): bool
146
    {
147
        return (bool)preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$/', $label);
148
    }
149
150
    /**
151
     * Checks if a domain label has consecutive hyphens
152
     *
153
     * @param string $label The domain label to validate
154
     * @return bool True if the label has consecutive hyphens
155
     */
156
    private function hasConsecutiveHyphens(string $label): bool
157
    {
158
        return strpos($label, '--') !== false;
159
    }
160
}