Message::withoutHeader()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace PTS\Psr7;
5
6
use InvalidArgumentException;
7
use Psr\Http\Message\MessageInterface;
8
use Psr\Http\Message\StreamInterface;
9
use function is_numeric;
10
use function is_string;
11
use function preg_match;
12
use function trim;
13
14
class Message implements MessageInterface
15
{
16
17
    /**
18
     * RFC-2616, RFC-7230 - case-insensitive; HTTP2 pack convert all header to lowercase
19
     *
20
     * @var array - all header name will be convert to lowercase
21
     */
22
    protected array $headers = [];
23
    protected string $protocol = '1.1';
24
    protected ?StreamInterface $stream = null;
25
26 5
    public function getProtocolVersion(): string
27
    {
28 5
        return $this->protocol;
29
    }
30
31 3
    public function withProtocolVersion($version): self
32
    {
33 3
        if ($this->protocol !== $version) {
34 1
            $this->protocol = $version;
35
        }
36
37 3
        return $this;
38
    }
39
40 25
    public function getHeaders(): array
41
    {
42 25
        return $this->headers;
43
    }
44
45 42
    public function hasHeader($name): bool
46
    {
47 42
        $name = strtolower($name);
48 42
        $hasHeader = $this->headers[$name] ?? null;
49 42
        return $hasHeader !== null;
50
    }
51
52 20
    public function getHeader($name): array
53
    {
54 20
        $name = strtolower($name);
55 20
        return $this->headers[$name] ?? [];
56
    }
57
58 19
    public function getHeaderLine($name): string
59
    {
60 19
        return implode(', ', $this->getHeader($name));
61
    }
62
63 7
    public function withHeader($name, $value): self
64
    {
65 7
        $name = strtolower($name);
66 7
        $value = array_map('trim', (array)$value);
67 7
        $this->validateHeader($name, $value);
68
69 5
        $this->headers[$name] = $value;
70 5
        return $this;
71
    }
72
73 4
    public function withoutHeader($name): self
74
    {
75 4
        $name = strtolower($name);
76 4
        unset($this->headers[$name]);
77 4
        return $this;
78
    }
79
80 19
    public function getBody(): StreamInterface
81
    {
82 19
        if (!$this->stream) {
83 5
            $this->stream = Stream::create('');
84
        }
85
86 19
        return $this->stream;
87
    }
88
89 9
    public function withBody(StreamInterface $body): self
90
    {
91 9
        $this->stream = $body;
92 9
        return $this;
93
    }
94
95 2
    public function reset(): static
96
    {
97 2
        $this->headers = [];
98 2
        if ($this->stream) {
99 1
            $this->stream->close();
100 1
            $this->stream = null;
101
        }
102
103 2
        return $this;
104
    }
105
106 7
    protected function validateHeader(string $name, array $values): void
107
    {
108 7
        if (preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $name) !== 1) {
109 1
            throw new InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
110
        }
111
112 6
        foreach ($values as $v) {
113 6
            if (!is_string($v) || 1 !== preg_match("@^[ \x09\x21-\x7E\x80-\xFF]*$@", (string)$v)) {
114 1
                throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
115
            }
116
        }
117 5
    }
118
119 4
    public function withAddedHeader($name, $value): self
120
    {
121 4
        $this->setHeaders([$name => $value]);
122 4
        return $this;
123
    }
124
125 78
    protected function setHeaders(array $headers): void
126
    {
127 78
        $this->validateHeaders($headers);
128
129 78
        foreach ($headers as $name => $values) {
130 24
            $values = (array)$values;
131 24
            $name = strtolower((string)$name);
132
133 24
            if (!($this->headers[$name] ?? false)) {
134 24
                $this->headers[$name] = [];
135
            }
136
137 24
            foreach ($values as &$value) {
138 24
                $value = trim((string)$value);
139
            }
140 24
            $this->headers[$name] = [...$this->headers[$name], ...$values];
141
        }
142 78
    }
143
144
    /**
145
     * @param array $headers
146
     *
147
     * It is more strict validate than RFC-7230
148
     */
149 78
    public function validateHeaders(array $headers): void
150
    {
151 78
        if (count($headers)) {
152 24
            $names = implode('', array_keys($headers));
153 24
            if (preg_match("/^[~0-9A-Za-z-+_.]+$/", $names) !== 1) {
154
                throw new InvalidArgumentException("Header names is incorrect: $names");
155
            }
156
157 24
            $this->validateHeadersValues($headers);
158
        }
159 78
    }
160
161 24
    protected function validateHeadersValues(array $headers): void
162
    {
163 24
        $allValues = '';
164 24
        foreach ($headers as $values) {
165 24
            foreach ((array)$values as $value) {
166 24
                $allValues .= $value;
167
            }
168
        }
169
170
        # https://donsnotes.com/tech/charsets/ascii.html
171 24
        if ($allValues && preg_match("/^[\x09\x20-\x7E\x80-\xFF]+$/", $allValues) !== 1) {
172
            throw new InvalidArgumentException('The value is incorrect for one of the header.');
173
        }
174 24
    }
175
}
176