Completed
Push — master ( 05dea0...95aa89 )
by Frederik
03:28
created

Address::toReadableString()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 7
cts 7
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 3
nop 0
crap 4
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 88
    public function __construct(EmailAddress $address, string $name = '')
47
    {
48 88
        if (preg_match('/\v/', $name) !== 0) {
49 1
            throw new \InvalidArgumentException('Cannot use vertical white space within name of email address');
50
        }
51
52 87
        $this->address = $address;
53 87
        $this->name = $name;
54 87
    }
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 41
    public function __toString(): string
85
    {
86 41
        if ($this->name === '') {
87 12
            return (string)$this->address;
88
        }
89
90 30
        $encodedName = (string) OptimalEncodedHeaderValue::forPhrase($this->name);
91 30
        if ($encodedName === $this->name) {
92 27
            $encodedName = addcslashes($encodedName, "\0..\37\177\\\"");
93
94 27
            if ($encodedName !== $this->name || preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $this->name) === 1) {
95 2
                $encodedName = sprintf('"%s"', $encodedName);
96
            }
97
        }
98
99 30
        return sprintf('%s <%s>', $encodedName, $this->address->getPunyCode());
100
    }
101
102
    /**
103
     * @return string
104
     */
105 17
    public function toReadableString(): string
106
    {
107 17
        if ($this->name === '') {
108 17
            return (string)$this->address;
109
        }
110
111 17
        $encodedName = $this->name;
112 17
        if ($encodedName !== $this->name || preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $this->name) === 1) {
113 17
            $encodedName = sprintf('"%s"', $encodedName);
114
        }
115
116 17
        return sprintf('%s <%s>', $encodedName, $this->address->getAddress());
117
    }
118
119
    /**
120
     * @param string $addressAsString
121
     * @return Address
122
     */
123 38
    public static function fromString(string $addressAsString)
124
    {
125 38
        $addressAsString = trim($addressAsString);
126
127 38
        if ($addressAsString === '') {
128 1
            throw new \InvalidArgumentException('Address cannot be empty');
129
        }
130
131 37
        $sequence = '';
132 37
        $length = strlen($addressAsString) - 1;
133 37
        $n = -1;
134 37
        $state = self::PARSE_STATE_EMAIL;
135 37
        $position = self::PARSE_POSITION_START;
136 37
        $escapeNext = false;
137 37
        $name = '';
138 37
        $email = '';
139 37
        $nameQuoted = false;
140
141 37
        while ($n < $length) {
142 37
            $n++;
143
144 37
            $char = $addressAsString[$n];
145
146 37
            if ($char === '\\') {
147 5
                $escapeNext = true;
148 5
                continue;
149
            }
150
151 37
            $sequence .= $char;
152
153 37
            if ($escapeNext) {
154 5
                $escapeNext = false;
155 5
                continue;
156
            }
157
158
            switch ($position) {
159 37
                case self::PARSE_POSITION_QUOTE:
160 19
                    if ($char === '"') {
161 17
                        $position = self::PARSE_POSITION_START;
162
                    }
163
164 19
                    break;
165
                default:
166 37
                    if ($char === '"') {
167 19
                        $position = self::PARSE_POSITION_QUOTE;
168
                    }
169
170 37
                    if ($char === '"' && $state === self::PARSE_STATE_EMAIL) {
171 18
                        $nameQuoted = true;
172
                    }
173 37
                    break;
174
            }
175
176
            switch ($state) {
177 37
                case self::PARSE_STATE_TAGGED_EMAIL:
178 24
                    if ($position !== self::PARSE_POSITION_QUOTE && $char === '>') {
179 23
                        $state = self::PARSE_STATE_EMAIL;
180 23
                        $email = substr($sequence, 0, -1);
181
                    }
182
183 24
                    break;
184
                default:
185 37
                    if ($email !== '') {
186 1
                        throw new \InvalidArgumentException('Invalid characters used after <>');
187
                    }
188
189 37
                    if ($position !== self::PARSE_POSITION_QUOTE && $char === '<') {
190 24
                        $state = self::PARSE_STATE_TAGGED_EMAIL;
191 24
                        $name = trim(substr($sequence, 0, -1));
192 24
                        $sequence = '';
193
                    }
194 37
                    break;
195
            }
196
        }
197
198 36
        if ($position === self::PARSE_POSITION_QUOTE) {
199 2
            throw new \InvalidArgumentException('Address uses starting quotes but no ending quotes');
200
        }
201
202 34
        if ($state === self::PARSE_STATE_TAGGED_EMAIL) {
203 1
            throw new \InvalidArgumentException('Address uses starting tag (<) but no ending tag (>)');
204
        }
205
206 33
        if ($name === '' && $email === '') {
207 11
            return new self(new EmailAddress($sequence));
208
        }
209
210 22
        if ($nameQuoted && $name[0] !== '"') {
211 1
            throw new \InvalidArgumentException('Invalid characters before "');
212
        }
213
214 21
        if ($nameQuoted) {
215 10
            $name = substr($name, 1, -1);
216
        }
217
218 21
        $name = @iconv_mime_decode($name);
219 21
        if ($name === false) {
220 1
            throw new \InvalidArgumentException('Failed to mime decode the name');
221
        }
222
223 20
        return new self(new EmailAddress($email), $name);
224
    }
225
}