Completed
Pull Request — master (#12)
by Frederik
12:11
created

HeaderValue::__toString()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
ccs 6
cts 6
cp 1
cc 4
eloc 11
nc 4
nop 0
crap 4
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Header;
5
6
/**
7
 * Class HeaderValue
8
 * @package Genkgo\Mail\Header
9
 */
10
final class HeaderValue
11
{
12
    /**
13
     *
14
     */
15
    private CONST PARSE_START = 1;
16
    /**
17
     *
18
     */
19
    private CONST PARSE_QUOTE = 2;
20
    /**
21
     * @var string
22
     */
23
    private $value;
24
    /**
25
     * @var array
26
     */
27
    private $parameters = [];
28
    /**
29
     * @var array
30
     */
31
    private $parametersForceNewLine = [];
32
    /**
33
     * @var bool
34
     */
35
    private $needsEncoding = true;
36
37 89
    /**
38
     * HeaderValue constructor.
39 89
     * @param string $value
40
     */
41 86
    public function __construct(string $value)
42 86
    {
43
        $this->assertValid($value);
44
45
        $this->value = $value;
46
    }
47
48 24
    /**
49
     * @param HeaderValueParameter $parameter
50 24
     * @param bool $forceNewLine
51 24
     * @return HeaderValue
52 24
     */
53
    public function withParameter(HeaderValueParameter $parameter, bool $forceNewLine = false): HeaderValue
54
    {
55
        $clone = clone $this;
56
        $clone->parameters[$parameter->getName()] = $parameter;
57
        $clone->parametersForceNewLine[$parameter->getName()] = $forceNewLine;
58 13
        return $clone;
59
    }
60 13
61
    /**
62
     * @return string
63
     */
64
    public function getRaw(): string
65
    {
66 69
        return $this->value;
67
    }
68 69
69
    /**
70 69
     * @return string
71 62
     */
72
    public function __toString(): string
73
    {
74 16
        $value = $this->value;
75
76
        if ($this->needsEncoding) {
77
            $value = (new OptimalEncodedHeaderValue($value));
78
        }
79
80 89
        $parameters = [];
81
82 89
        foreach ($this->parameters as $name => $parameter) {
83 3
            $parameters[] = sprintf(
84
                ';%s%s',
85 86
                empty($this->parametersForceNewLine[$name]) ? ' ' : "\r\n ",
86
                (string) $parameter
87
            );
88
        }
89
90
        return implode('', array_merge([$value], $parameters));
91 89
    }
92
93 89
    /**
94
     * @param string $value
95 89
     */
96 89
    private function assertValid(string $value): void
97 89
    {
98 1
        if ($this->validate($value) === false) {
99
            throw new \InvalidArgumentException('Invalid header value ' . $value);
100 89
        }
101 5
    }
102
103
    /**
104 5
     * @param $value
105 5
     * @return bool
106 5
     */
107 2
    private function validate(string $value): bool
108
    {
109
        $total = strlen($value);
110 3
111
        for ($i = 0; $i < $total; $i += 1) {
112
            $ord = ord($value[$i]);
113
            if ($ord === 10) {
114 86
                return false;
115
            }
116
            if ($ord === 13) {
117
                if ($i + 2 >= $total) {
118
                    return false;
119
                }
120
                $lf = ord($value[$i + 1]);
121 7
                $sp = ord($value[$i + 2]);
122
                if ($lf !== 10 || ! in_array($sp, [9, 32], true)) {
123 7
                    return false;
124 7
                }
125
                // skip over the LF following this
126
                $i += 2;
127 2
            }
128
        }
129
130
        return true;
131
    }
132
133
    /**
134 16
     * @param string $name
135
     * @return HeaderValueParameter
136 16
     */
137 16
    public function getParameter(string $name): HeaderValueParameter
138 16
    {
139
        if (isset($this->parameters[$name])) {
140
            return $this->parameters[$name];
141
        }
142
143
        throw new \UnexpectedValueException('No parameter with name ' . $name);
144
    }
145 3
146
    /**
147 3
     * @param string $value
148
     * @return HeaderValue
149 3
     */
150
    public static function fromEncodedString(string $value): HeaderValue
151 3
    {
152 3
        $headerValue = new self($value);
153 3
        $headerValue->needsEncoding = false;
154 3
        return $headerValue;
155 3
    }
156
157 3
    /**
158 3
     * @param string $headerValueAsString
159
     * @return HeaderValue
160 3
     */
161
    public static function fromString(string $headerValueAsString): HeaderValue
162 3
    {
163
        $values = [];
164 3
165
        $headerValueAsString = trim($headerValueAsString);
166
167
        $length = strlen($headerValueAsString) - 1;
168
        $n = -1;
169 3
        $state = self::PARSE_START;
170
        $escapeNext = false;
171
        $sequence = '';
172
173
        while ($n < $length) {
174
            $n++;
175 3
176 2
            $char = $headerValueAsString[$n];
177 2
178
            $sequence .= $char;
179
180 2
            if ($char === '\\') {
181
                $escapeNext = true;
182 3
                continue;
183 2
            }
184
185
            if ($escapeNext) {
186 3
                $escapeNext = false;
187 3
                continue;
188 3
            }
189 3
190
            switch ($state) {
191 3
                case self::PARSE_QUOTE:
192
                    if ($char === '"') {
193
                        $state = self::PARSE_START;
194
                    }
195 3
196
                    break;
197 3
                default:
198
                    if ($char === '"') {
199 3
                        $state = self::PARSE_QUOTE;
200 3
                    }
201 3
202 3
                    if ($char === ';') {
203
                        $values[] = trim(substr($sequence, 0, -1));
204
                        $sequence = '';
205 3
                        $state = self::PARSE_START;
206 3
                    }
207
                    break;
208
            }
209
        }
210
211
        $values[] = trim($sequence);
212
213
        $headerValue = new self($values[0]);
214
215
        $parameters = [];
216
        foreach (array_slice($values, 1) as $parameterString) {
217
            $parameter = HeaderValueParameter::fromString($parameterString);
218
            $parameters[$parameter->getName()] = $parameter;
219
        }
220
221
        $headerValue->parameters = $parameters;
222
        return $headerValue;
223
    }
224
}