Passed
Pull Request — master (#27)
by Anatoly
04:06
created

Header::validateParametersByRegex()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 9

Importance

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