Passed
Pull Request — master (#31)
by Anatoly
39:30
created

Header::getIterator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-message
10
 */
11
12
namespace Sunrise\Http\Message;
13
14
/**
15
 * Import classes
16
 */
17
use Sunrise\Http\Message\Exception\InvalidHeaderException;
18
use DateTime;
19
use DateTimeImmutable;
20
use DateTimeInterface;
21
use DateTimeZone;
22
use Traversable;
23
24
/**
25
 * Import functions
26
 */
27
use function gettype;
28
use function is_int;
29
use function is_string;
30
use function preg_match;
31
use function sprintf;
32
33
/**
34
 * HTTP Header Field
35
 */
36
abstract class Header implements HeaderInterface
37
{
38
39
    /**
40
     * Regular Expression for a token validation
41
     *
42
     * @link https://tools.ietf.org/html/rfc7230#section-3.2
43
     *
44
     * @var string
45
     */
46
    public const RFC7230_VALID_TOKEN = '/^[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+$/';
47
48
    /**
49
     * Regular Expression for a field value validation
50
     *
51
     * @link https://tools.ietf.org/html/rfc7230#section-3.2
52
     *
53
     * @var string
54
     */
55
    public const RFC7230_VALID_FIELD_VALUE = '/^[\x09\x20-\x7E\x80-\xFF]*$/';
56
57
    /**
58
     * Regular Expression for a quoted string validation
59
     *
60
     * @link https://tools.ietf.org/html/rfc7230#section-3.2
61
     *
62
     * @var string
63
     */
64
    public const RFC7230_VALID_QUOTED_STRING = '/^[\x09\x20\x21\x23-\x5B\x5D-\x7E\x80-\xFF]*$/';
65
66
    /**
67
     * Date and time format
68
     *
69
     * @link https://www.rfc-editor.org/rfc/rfc822#section-5
70
     *
71
     * @var string
72
     */
73
    public const RFC822_DATE_TIME_FORMAT = 'D, d M y H:i:s O';
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 37
    final public function getIterator(): Traversable
79
    {
80 37
        yield $this->getFieldName();
81 37
        yield $this->getFieldValue();
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 37
    final public function __toString(): string
88
    {
89 37
        return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue());
90
    }
91
92
    /**
93
     * Checks if the given string is a token
94
     *
95
     * @param string $token
96
     *
97
     * @return bool
98
     */
99 12
    final protected function isToken(string $token): bool
100
    {
101 12
        return preg_match(self::RFC7230_VALID_TOKEN, $token) === 1;
102
    }
103
104
    /**
105
     * Validates the given token(s)
106
     *
107
     * @param string ...$tokens
108
     *
109
     * @return void
110
     *
111
     * @throws InvalidHeaderException
112
     *         If one of the tokens isn't valid.
113
     */
114 121
    final protected function validateToken(string ...$tokens): void
115
    {
116 121
        $this->validateValueByRegex(self::RFC7230_VALID_TOKEN, ...$tokens);
117
    }
118
119
    /**
120
     * Validates the given field value(s)
121
     *
122
     * @param string ...$fieldValues
123
     *
124
     * @return void
125
     *
126
     * @throws InvalidHeaderException
127
     *         If one of the field values isn't valid.
128
     */
129
    final protected function validateFieldValue(string ...$fieldValues): void
130
    {
131
        $this->validateValueByRegex(self::RFC7230_VALID_FIELD_VALUE, ...$fieldValues);
132
    }
133
134
    /**
135
     * Validates the given quoted string(s)
136
     *
137
     * @param string ...$quotedStrings
138
     *
139
     * @return void
140
     *
141
     * @throws InvalidHeaderException
142
     *         If one of the quoted strings isn't valid.
143
     */
144 25
    final protected function validateQuotedString(string ...$quotedStrings): void
145
    {
146 25
        $this->validateValueByRegex(self::RFC7230_VALID_QUOTED_STRING, ...$quotedStrings);
147
    }
148
149
    /**
150
     * Validates and normalizes the given parameters
151
     *
152
     * @param array<array-key, mixed> $parameters
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
153
     *
154
     * @return array<string, string>
155
     *         The normalized parameters.
156
     *
157
     * @throws InvalidHeaderException
158
     *         If one of the parameters isn't valid.
159
     */
160 84
    final protected function validateParameters(array $parameters): array
161
    {
162 84
        return $this->validateParametersByRegex(
163 84
            $parameters,
164 84
            self::RFC7230_VALID_TOKEN,
165 84
            self::RFC7230_VALID_QUOTED_STRING
166 84
        );
167
    }
168
169
    /**
170
     * Validates the given value(s) by the given regular expression
171
     *
172
     * @param string $regex
173
     * @param string ...$values
174
     *
175
     * @return void
176
     *
177
     * @throws InvalidHeaderException
178
     *         If one of the values isn't valid.
179
     */
180 173
    final protected function validateValueByRegex(string $regex, string ...$values): void
181
    {
182 173
        foreach ($values as $value) {
183 173
            if (!preg_match($regex, $value)) {
184 50
                throw new InvalidHeaderException(sprintf(
185 50
                    'The value "%2$s" for the header "%1$s" is not valid',
186 50
                    $this->getFieldName(),
187 50
                    $value
188 50
                ));
189
            }
190
        }
191
    }
192
193
    /**
194
     * Validates and normalizes the given parameters by the given regular expressions
195
     *
196
     * @param array<array-key, mixed> $parameters
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
197
     * @param string $nameRegex
198
     * @param string $valueRegex
199
     *
200
     * @return array<string, string>
201
     *         The normalized parameters.
202
     *
203
     * @throws InvalidHeaderException
204
     *         If one of the parameters isn't valid.
205
     */
206 110
    final protected function validateParametersByRegex(array $parameters, string $nameRegex, string $valueRegex): array
207
    {
208 110
        foreach ($parameters as $name => &$value) {
209 86
            if (!is_string($name) || !preg_match($nameRegex, $name)) {
210 16
                throw new InvalidHeaderException(sprintf(
211 16
                    'The parameter name "%2$s" for the header "%1$s" is not valid',
212 16
                    $this->getFieldName(),
213 16
                    (is_string($name) ? $name : ('<' . gettype($name) . '>'))
214 16
                ));
215
            }
216
217
            // e.g. Cache-Control: max-age=31536000
218 70
            if (is_int($value)) {
219 16
                $value = (string) $value;
220
            }
221
222 70
            if (!is_string($value) || !preg_match($valueRegex, $value)) {
223 16
                throw new InvalidHeaderException(sprintf(
224 16
                    'The parameter value "%2$s" for the header "%1$s" is not valid',
225 16
                    $this->getFieldName(),
226 16
                    (is_string($value) ? $value : ('<' . gettype($value) . '>'))
227 16
                ));
228
            }
229
        }
230
231
        /** @var array<string, string> $parameters */
232
233 78
        return $parameters;
234
    }
235
236
    /**
237
     * Formats the given date-time object
238
     *
239
     * @link https://tools.ietf.org/html/rfc7230#section-3.2
240
     *
241
     * @param DateTimeInterface $dateTime
242
     *
243
     * @return string
244
     */
245 31
    final protected function formatDateTime(DateTimeInterface $dateTime): string
246
    {
247 31
        if ($dateTime instanceof DateTime) {
248 24
            return (clone $dateTime)
249 24
                ->setTimezone(new DateTimeZone('GMT'))
250 24
                ->format(self::RFC822_DATE_TIME_FORMAT);
251
        }
252
253 7
        return $dateTime
254 7
            ->setTimezone(new DateTimeZone('GMT'))
255 7
            ->format(self::RFC822_DATE_TIME_FORMAT);
256
    }
257
}
258