QuotedStringValidator::validateContent()   B
last analyzed

Complexity

Conditions 9
Paths 7

Size

Total Lines 39
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 19
c 1
b 0
f 0
dl 0
loc 39
rs 8.0555
cc 9
nc 7
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EmailValidator\Validator\LocalPart;
6
7
/**
8
 * Validates quoted string format of local parts in email addresses
9
 */
10
class QuotedStringValidator
11
{
12
    /**
13
     * Characters that must be escaped in quoted strings
14
     */
15
    private const MUST_ESCAPE = ['"', '\\'];
16
17
    /**
18
     * Validates a quoted string local part
19
     *
20
     * @param string $localPart The quoted string to validate
21
     * @return bool True if the quoted string is valid
22
     */
23
    public function validate(string $localPart): bool
24
    {
25
        // Must start and end with quotes
26
        if (!$this->hasValidQuotes($localPart)) {
27
            return false;
28
        }
29
30
        // Remove outer quotes for content validation
31
        $content = substr($localPart, 1, -1);
32
33
        // Empty quoted strings are valid
34
        if ($content === '') {
35
            return true;
36
        }
37
38
        return $this->validateContent($content);
39
    }
40
41
    /**
42
     * Checks if a quoted string has valid opening and closing quotes
43
     *
44
     * @param string $localPart The quoted string to validate
45
     * @return bool True if the quotes are valid
46
     */
47
    private function hasValidQuotes(string $localPart): bool
48
    {
49
        return substr($localPart, 0, 1) === '"' && substr($localPart, -1) === '"';
50
    }
51
52
    /**
53
     * Validates the content of a quoted string
54
     *
55
     * @param string $content The content to validate (without outer quotes)
56
     * @return bool True if the content is valid
57
     */
58
    private function validateContent(string $content): bool
59
    {
60
        $length = strlen($content);
61
        $i = 0;
62
63
        while ($i < $length) {
64
            $char = $content[$i];
65
            $charCode = ord($char);
66
67
            // Handle backslash escapes
68
            if ($char === '\\') {
69
                // Can't end with a lone backslash
70
                if ($i === $length - 1) {
71
                    return false;
72
                }
73
                // Next character must be either a quote or backslash
74
                $nextChar = $content[$i + 1];
75
                if (!in_array($nextChar, self::MUST_ESCAPE, true)) {
76
                    return false;
77
                }
78
                // Skip the escaped character
79
                $i += 2;
80
                continue;
81
            }
82
83
            // Non-printable characters are never allowed (except tab)
84
            if (($charCode < 32 || $charCode > 126) && $charCode !== 9) {
85
                return false;
86
            }
87
88
            // Unescaped quotes are not allowed
89
            if ($char === '"') {
90
                return false;
91
            }
92
93
            $i++;
94
        }
95
96
        return true;
97
    }
98
}