• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / httpful / 5593279780

pending completion
5593279780

push

github

voku
Merge branch 'master' of ssh://github.com/voku/httpful

* 'master' of ssh://github.com/voku/httpful:
  Fix CS again
  Fix CS
  Fix typos
  Fix curly braces in strings

12 of 12 new or added lines in 3 files covered. (100.0%)

1653 of 2504 relevant lines covered (66.01%)

80.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.67
/src/Httpful/Headers.php
1
<?php
2

3
/** @noinspection MagicMethodsValidityInspection */
4
/** @noinspection PhpMissingParentConstructorInspection */
5

6
declare(strict_types=1);
7

8
namespace Httpful;
9

10
use Httpful\Exception\ResponseHeaderException;
11

12
class Headers implements \ArrayAccess, \Countable, \Iterator
13
{
14
    /**
15
     * @var mixed[] data storage with lowercase keys
16
     *
17
     * @see offsetSet()
18
     * @see offsetExists()
19
     * @see offsetUnset()
20
     * @see offsetGet()
21
     * @see count()
22
     * @see current()
23
     * @see next()
24
     * @see key()
25
     */
26
    private $data = [];
27

28
    /**
29
     * @var string[] case-sensitive keys
30
     *
31
     * @see offsetSet()
32
     * @see offsetUnset()
33
     * @see key()
34
     */
35
    private $keys = [];
36

37
    /**
38
     * Allow creating either an empty Array, or convert an existing Array to a
39
     * Case-Insensitive Array.  (Caution: Data may be lost when converting Case-
40
     * Sensitive Arrays to Case-Insensitive Arrays)
41
     *
42
     * @param mixed[] $initial (optional) Existing Array to convert
43
     */
44
    public function __construct(array $initial = null)
45
    {
46
        if ($initial !== null) {
576✔
47
            foreach ($initial as $key => $value) {
360✔
48
                if (!\is_array($value)) {
356✔
49
                    $value = [$value];
32✔
50
                }
51

52
                $this->forceSet($key, $value);
356✔
53
            }
54
        }
55
    }
144✔
56

57
    /**
58
     * @see https://secure.php.net/manual/en/countable.count.php
59
     *
60
     * @return int the number of elements stored in the array
61
     */
62
    #[\ReturnTypeWillChange]
63
    public function count()
64
    {
65
        return (int) \count($this->data);
4✔
66
    }
67

68
    /**
69
     * @see https://secure.php.net/manual/en/iterator.current.php
70
     *
71
     * @return mixed data at the current position
72
     */
73
    #[\ReturnTypeWillChange]
74
    public function current()
75
    {
76
        return \current($this->data);
276✔
77
    }
78

79
    /**
80
     * @see https://secure.php.net/manual/en/iterator.key.php
81
     *
82
     * @return mixed case-sensitive key at current position
83
     */
84
    #[\ReturnTypeWillChange]
85
    public function key()
86
    {
87
        $key = \key($this->data);
276✔
88

89
        return $this->keys[$key] ?? $key;
276✔
90
    }
91

92
    /**
93
     * @see https://secure.php.net/manual/en/iterator.next.php
94
     *
95
     * @return void
96
     */
97
    #[\ReturnTypeWillChange]
98
    public function next()
99
    {
100
        \next($this->data);
276✔
101
    }
69✔
102

103
    /**
104
     * @see https://secure.php.net/manual/en/iterator.rewind.php
105
     *
106
     * @return void
107
     */
108
    #[\ReturnTypeWillChange]
109
    public function rewind()
110
    {
111
        \reset($this->data);
340✔
112
    }
85✔
113

114
    /**
115
     * @see https://secure.php.net/manual/en/iterator.valid.php
116
     *
117
     * @return bool if the current position is valid
118
     */
119
    #[\ReturnTypeWillChange]
120
    public function valid()
121
    {
122
        return \key($this->data) !== null;
340✔
123
    }
124

125
    /**
126
     * @param string $offset the offset to store the data at (case-insensitive)
127
     * @param mixed  $value  the data to store at the specified offset
128
     *
129
     * @return void
130
     */
131
    public function forceSet($offset, $value)
132
    {
133
        $value = $this->_validateAndTrimHeader($offset, $value);
376✔
134

135
        $this->offsetSetForce($offset, $value);
376✔
136
    }
94✔
137

138
    /**
139
     * @param string $offset
140
     *
141
     * @return void
142
     */
143
    public function forceUnset($offset)
144
    {
145
        $this->offsetUnsetForce($offset);
268✔
146
    }
67✔
147

148
    /**
149
     * @param string $string
150
     *
151
     * @return Headers
152
     */
153
    public static function fromString($string): self
154
    {
155
        // init
156
        $parsed_headers = [];
188✔
157

158
        $headers = \preg_split("/[\r\n]+/", $string, -1, \PREG_SPLIT_NO_EMPTY);
188✔
159
        if ($headers === false) {
188✔
160
            return new self($parsed_headers);
×
161
        }
162

163
        $headersCount = \count($headers);
188✔
164
        for ($i = 1; $i < $headersCount; ++$i) {
188✔
165
            $header = $headers[$i];
184✔
166

167
            if (\strpos($header, ':') === false) {
184✔
168
                continue;
28✔
169
            }
170

171
            list($key, $raw_value) = \explode(':', $header, 2);
184✔
172
            $key = \trim($key);
184✔
173
            $value = \trim($raw_value);
184✔
174

175
            if (\array_key_exists($key, $parsed_headers)) {
184✔
176
                $parsed_headers[$key][] = $value;
36✔
177
            } else {
178
                $parsed_headers[$key][] = $value;
184✔
179
            }
180
        }
181

182
        return new self($parsed_headers);
188✔
183
    }
184

185
    /**
186
     * Checks if the offset exists in data storage. The index is looked up with
187
     * the lowercase version of the provided offset.
188
     *
189
     * @see https://secure.php.net/manual/en/arrayaccess.offsetexists.php
190
     *
191
     * @param string $offset Offset to check
192
     *
193
     * @return bool if the offset exists
194
     */
195
    #[\ReturnTypeWillChange]
196
    public function offsetExists($offset)
197
    {
198
        return (bool) \array_key_exists(\strtolower($offset), $this->data);
416✔
199
    }
200

201
    /**
202
     * Return the stored data at the provided offset. The offset is converted to
203
     * lowercase and the lookup is done on the data store directly.
204
     *
205
     * @see https://secure.php.net/manual/en/arrayaccess.offsetget.php
206
     *
207
     * @param string $offset offset to lookup
208
     *
209
     * @return mixed the data stored at the offset
210
     */
211
    #[\ReturnTypeWillChange]
212
    public function offsetGet($offset)
213
    {
214
        $offsetLower = \strtolower($offset);
260✔
215

216
        return $this->data[$offsetLower] ?? null;
260✔
217
    }
218

219
    /**
220
     * @param string $offset
221
     * @param string $value
222
     *
223
     * @throws ResponseHeaderException
224
     *
225
     * @return void
226
     */
227
    #[\ReturnTypeWillChange]
228
    public function offsetSet($offset, $value)
229
    {
230
        throw new ResponseHeaderException('Headers are read-only.');
×
231
    }
232

233
    /**
234
     * @param string $offset
235
     *
236
     * @throws ResponseHeaderException
237
     *
238
     * @return void
239
     */
240
    #[\ReturnTypeWillChange]
241
    public function offsetUnset($offset)
242
    {
243
        throw new ResponseHeaderException('Headers are read-only.');
×
244
    }
245

246
    /**
247
     * @return array
248
     */
249
    public function toArray(): array
250
    {
251
        // init
252
        $return = [];
332✔
253

254
        $that = clone $this;
332✔
255

256
        foreach ($that as $key => $value) {
332✔
257
            if (\is_array($value)) {
120✔
258
                foreach ($value as $keyInner => $valueInner) {
120✔
259
                    $value[$keyInner] = \trim($valueInner, " \t");
120✔
260
                }
261
            }
262

263
            $return[$key] = $value;
120✔
264
        }
265

266
        return $return;
332✔
267
    }
268

269
    /**
270
     * Make sure the header complies with RFC 7230.
271
     *
272
     * Header names must be a non-empty string consisting of token characters.
273
     *
274
     * Header values must be strings consisting of visible characters with all optional
275
     * leading and trailing whitespace stripped. This method will always strip such
276
     * optional whitespace. Note that the method does not allow folding whitespace within
277
     * the values as this was deprecated for almost all instances by the RFC.
278
     *
279
     * header-field = field-name ":" OWS field-value OWS
280
     * field-name   = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
281
     *              / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
282
     * OWS          = *( SP / HTAB )
283
     * field-value  = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
284
     *
285
     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
286
     *
287
     * @param mixed $header
288
     * @param mixed $values
289
     *
290
     * @return string[]
291
     */
292
    private function _validateAndTrimHeader($header, $values): array
293
    {
294
        if (
295
            !\is_string($header)
376✔
296
            ||
297
            \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $header) !== 1
376✔
298
        ) {
299
            throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string: ' . \print_r($header, true));
4✔
300
        }
301

302
        if (!\is_array($values)) {
376✔
303
            // This is simple, just one value.
304
            if (
305
                (!\is_numeric($values) && !\is_string($values))
184✔
306
                ||
307
                \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values) !== 1
184✔
308
            ) {
309
                throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings: ' . \print_r($header, true));
×
310
            }
311

312
            return [\trim((string) $values, " \t")];
184✔
313
        }
314

315
        if (empty($values)) {
368✔
316
            throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.');
×
317
        }
318

319
        // Assert Non empty array
320
        $returnValues = [];
368✔
321
        foreach ($values as $v) {
368✔
322
            if (
323
                (!\is_numeric($v) && !\is_string($v))
368✔
324
                ||
325
                \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $v) !== 1
368✔
326
            ) {
327
                throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings: ' . \print_r($v, true));
×
328
            }
329

330
            $returnValues[] = \trim((string) $v, " \t");
368✔
331
        }
332

333
        return $returnValues;
368✔
334
    }
335

336
    /**
337
     * Set data at a specified offset. Converts the offset to lowercase, and
338
     * stores the case-sensitive offset and the data at the lowercase indexes in
339
     * $this->keys and @this->data.
340
     *
341
     * @see https://secure.php.net/manual/en/arrayaccess.offsetset.php
342
     *
343
     * @param string|null $offset the offset to store the data at (case-insensitive)
344
     * @param mixed       $value  the data to store at the specified offset
345
     *
346
     * @return void
347
     */
348
    private function offsetSetForce($offset, $value)
349
    {
350
        if ($offset === null) {
376✔
351
            $this->data[] = $value;
×
352
        } else {
353
            $offsetlower = \strtolower($offset);
376✔
354
            $this->data[$offsetlower] = $value;
376✔
355
            $this->keys[$offsetlower] = $offset;
376✔
356
        }
357
    }
94✔
358

359
    /**
360
     * Unsets the specified offset. Converts the provided offset to lowercase,
361
     * and unsets the case-sensitive key, as well as the stored data.
362
     *
363
     * @see https://secure.php.net/manual/en/arrayaccess.offsetunset.php
364
     *
365
     * @param string $offset the offset to unset
366
     *
367
     * @return void
368
     */
369
    private function offsetUnsetForce($offset)
370
    {
371
        $offsetLower = \strtolower($offset);
268✔
372

373
        unset($this->data[$offsetLower], $this->keys[$offsetLower]);
268✔
374
    }
67✔
375
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc