Test Failed
Push — master ( 0b6467...e6142c )
by Alexpts
16:38
created

Message   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 59
c 3
b 0
f 1
dl 0
loc 159
ccs 60
cts 60
cp 1
rs 9.84
wmc 32

16 Methods

Rating   Name   Duplication   Size   Complexity  
A reset() 0 9 2
A getHeaders() 0 3 1
A hasHeader() 0 5 1
A withBody() 0 4 1
A withHeader() 0 8 1
A withoutHeader() 0 5 1
A getHeader() 0 4 1
A validateHeadersValues() 0 12 5
A setHeaders() 0 16 4
A getProtocolVersion() 0 3 1
A withAddedHeader() 0 4 1
A validateHeader() 0 9 5
A getHeaderLine() 0 3 1
A validateHeaders() 0 9 3
A withProtocolVersion() 0 7 2
A getBody() 0 7 2
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 2
    public function withProtocolVersion($version): self
32
    {
33 2
        if ($this->protocol !== $version) {
34 1
            $this->protocol = $version;
35
        }
36
37 2
        return $this;
38
    }
39
40 25
    public function getHeaders(): array
41
    {
42 25
        return $this->headers;
43
    }
44
45 56
    public function hasHeader($name): bool
46
    {
47 56
        $name = strtolower($name);
48 56
        $hasHeader = $this->headers[$name] ?? null;
49 56
        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 8
    public function withHeader($name, $value): self
64
    {
65 8
        $name = strtolower($name);
66 8
        $value = array_map('trim', (array)$value);
67 6
        $this->validateHeader($name, $value);
68
69
        $this->headers[$name] = $value;
70 4
        return $this;
71
    }
72 4
73 4
    public function withoutHeader($name): self
74 4
    {
75
        $name = strtolower($name);
76
        unset($this->headers[$name]);
77 19
        return $this;
78
    }
79 19
80 5
    public function getBody(): StreamInterface
81
    {
82
        if (!$this->stream) {
83 19
            $this->stream = Stream::create('');
84
        }
85
86 8
        return $this->stream;
87
    }
88 8
89 8
    public function withBody(StreamInterface $body): self
90
    {
91
        $this->stream = $body;
92 2
        return $this;
93
    }
94 2
95 2
    public function reset(): static
96 1
    {
97 1
        $this->headers = [];
98
        if ($this->stream) {
99
            $this->stream->close();
100 2
            $this->stream = null;
101
        }
102
103 28
        return $this;
104
    }
105 28
106 1
    protected function validateHeader(string $name, array $values): void
107
    {
108
        if (preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $name) !== 1) {
109 27
            throw new InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
110 27
        }
111 27
112 1
        foreach ($values as $v) {
113
            if (!is_string($v) || 1 !== preg_match("@^[ \x09\x21-\x7E\x80-\xFF]*$@", (string)$v)) {
114
                throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
115 26
            }
116
        }
117
    }
118 26
119
    public function withAddedHeader($name, $value): self
120
    {
121 4
        $this->setHeaders([$name => $value]);
122
        return $this;
123 4
    }
124 4
125
    protected function setHeaders(array $headers): void
126
    {
127 77
        $this->validateHeaders($headers);
128
129 77
        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
                $value = trim((string)$value);
139 24
            }
140 24
            $this->headers[$name] = [...$this->headers[$name], ...$values];
141
        }
142
    }
143 77
144
    /**
145
     * @param array $headers
146
     *
147
     * It is more strict validate than RFC-7230
148
     */
149
    public function validateHeaders(array $headers): void
150
    {
151
        if (count($headers)) {
152
            $names = implode('', array_keys($headers));
153
            if (preg_match("/^[~0-9A-Za-z-+_.]+$/", $names) !== 1) {
154
                throw new InvalidArgumentException("Header names is incorrect: $names");
155
            }
156
157
            $this->validateHeadersValues($headers);
158
        }
159
    }
160
161
    protected function validateHeadersValues(array $headers): void
162
    {
163
        $allValues = '';
164
        foreach ($headers as $values) {
165
            foreach ((array)$values as $value) {
166
                $allValues .= $value;
167
            }
168
        }
169
170
        # https://donsnotes.com/tech/charsets/ascii.html
171
        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
    }
175
}
176