Completed
Pull Request — master (#18)
by Frederik
01:37
created

HeaderValue   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 94.19%

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 2
dl 0
loc 215
ccs 81
cts 86
cp 0.9419
rs 10
c 0
b 0
f 0

9 Methods

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