Completed
Push — master ( cbbea8...8bbcb3 )
by Frederik
03:15
created

Address   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 197
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 2
dl 0
loc 197
ccs 79
cts 79
cp 1
rs 9
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A getAddress() 0 4 1
A getName() 0 4 1
A equals() 0 4 2
B __toString() 0 17 5
F fromString() 0 102 24
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail;
5
6
use Genkgo\Mail\Header\OptimalEncodedHeaderValue;
7
8
/**
9
 * Class Address
10
 * @package Genkgo\Mail
11
 */
12
final class Address
13
{
14
    /**
15
     *
16
     */
17
    private CONST PARSE_POSITION_START = 1;
18
    /**
19
     *
20
     */
21
    private CONST PARSE_POSITION_QUOTE = 2;
22
    /**
23
     *
24
     */
25
    private CONST PARSE_STATE_EMAIL = 1;
26
    /**
27
     *
28
     */
29
    private CONST PARSE_STATE_TAGGED_EMAIL = 2;
30
31
    /**
32
     * @var EmailAddress
33
     */
34
    private $address;
35
36
    /**
37
     * @var string
38
     */
39
    private $name;
40
41
    /**
42
     * To constructor.
43
     * @param EmailAddress $address
44
     * @param string $name
45
     */
46 70
    public function __construct(EmailAddress $address, string $name = '')
47
    {
48 70
        if (preg_match('/\v/', $name) !== 0) {
49 1
            throw new \InvalidArgumentException('Cannot use vertical white space within name of email address');
50
        }
51
52 69
        $this->address = $address;
53 69
        $this->name = $name;
54 69
    }
55
56
    /**
57
     * @return EmailAddress
58
     */
59 26
    public function getAddress(): EmailAddress
60
    {
61 26
        return $this->address;
62
    }
63
64
    /**
65
     * @return string
66
     */
67 19
    public function getName(): string
68
    {
69 19
        return $this->name;
70
    }
71
72
    /**
73
     * @param Address $address
74
     * @return bool
75
     */
76 3
    public function equals(Address $address): bool
77
    {
78 3
        return $this->address->equals($address->address) && $this->name === $address->name;
79
    }
80
81
    /**
82
     * @return string
83
     */
84 40
    public function __toString(): string
85
    {
86 40
        if ($this->name === '') {
87 12
            return (string)$this->address;
88
        }
89
90 29
        $encodedName = (string) OptimalEncodedHeaderValue::forPhrase($this->name);
91 29
        if ($encodedName === $this->name) {
92 26
            $encodedName = addcslashes($encodedName, "\0..\37\177\\\"");
93
94 26
            if ($encodedName !== $this->name || preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $this->name) === 1) {
95 2
                $encodedName = sprintf('"%s"', $encodedName);
96
            }
97
        }
98
99 29
        return sprintf('%s <%s>', $encodedName, $this->address->getPunyCode());
100
    }
101
102
    /**
103
     * @param string $addressAsString
104
     * @return Address
105
     */
106 38
    public static function fromString(string $addressAsString)
107
    {
108 38
        $addressAsString = trim($addressAsString);
109
110 38
        if ($addressAsString === '') {
111 1
            throw new \InvalidArgumentException('Address cannot be empty');
112
        }
113
114 37
        $sequence = '';
115 37
        $length = strlen($addressAsString) - 1;
116 37
        $n = -1;
117 37
        $state = self::PARSE_STATE_EMAIL;
118 37
        $position = self::PARSE_POSITION_START;
119 37
        $escapeNext = false;
120 37
        $name = '';
121 37
        $email = '';
122 37
        $nameQuoted = false;
123
124 37
        while ($n < $length) {
125 37
            $n++;
126
127 37
            $char = $addressAsString[$n];
128
129 37
            if ($char === '\\') {
130 5
                $escapeNext = true;
131 5
                continue;
132
            }
133
134 37
            $sequence .= $char;
135
136 37
            if ($escapeNext) {
137 5
                $escapeNext = false;
138 5
                continue;
139
            }
140
141
            switch ($position) {
142 37
                case self::PARSE_POSITION_QUOTE:
143 19
                    if ($char === '"') {
144 17
                        $position = self::PARSE_POSITION_START;
145
                    }
146
147 19
                    break;
148
                default:
149 37
                    if ($char === '"') {
150 19
                        $position = self::PARSE_POSITION_QUOTE;
151
                    }
152
153 37
                    if ($char === '"' && $state === self::PARSE_STATE_EMAIL) {
154 18
                        $nameQuoted = true;
155
                    }
156 37
                    break;
157
            }
158
159
            switch ($state) {
160 37
                case self::PARSE_STATE_TAGGED_EMAIL:
161 24
                    if ($position !== self::PARSE_POSITION_QUOTE && $char === '>') {
162 23
                        $state = self::PARSE_STATE_EMAIL;
163 23
                        $email = substr($sequence, 0, -1);
164
                    }
165
166 24
                    break;
167
                default:
168 37
                    if ($email !== '') {
169 1
                        throw new \InvalidArgumentException('Invalid characters used after <>');
170
                    }
171
172 37
                    if ($position !== self::PARSE_POSITION_QUOTE && $char === '<') {
173 24
                        $state = self::PARSE_STATE_TAGGED_EMAIL;
174 24
                        $name = trim(substr($sequence, 0, -1));
175 24
                        $sequence = '';
176
                    }
177 37
                    break;
178
            }
179
        }
180
181 36
        if ($position === self::PARSE_POSITION_QUOTE) {
182 2
            throw new \InvalidArgumentException('Address uses starting quotes but no ending quotes');
183
        }
184
185 34
        if ($state === self::PARSE_STATE_TAGGED_EMAIL) {
186 1
            throw new \InvalidArgumentException('Address uses starting tag (<) but no ending tag (>)');
187
        }
188
189 33
        if ($name === '' && $email === '') {
190 11
            return new self(new EmailAddress($sequence));
191
        }
192
193 22
        if ($nameQuoted && $name[0] !== '"') {
194 1
            throw new \InvalidArgumentException('Invalid characters before "');
195
        }
196
197 21
        if ($nameQuoted) {
198 10
            $name = substr($name, 1, -1);
199
        }
200
201 21
        $name = @iconv_mime_decode($name);
202 21
        if ($name === false) {
203 1
            throw new \InvalidArgumentException('Failed to mime decode the name');
204
        }
205
206 20
        return new self(new EmailAddress($email), $name);
207
    }
208
}