Completed
Push — master ( 66adf3...c22a1a )
by Tobias
02:03
created

MessageTrait::withAddedHeader()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.9
c 0
b 0
f 0
cc 3
nc 2
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Nyholm\Psr7\Factory\StreamFactory;
8
use Psr\Http\Message\StreamInterface;
9
10
/**
11
 * Trait implementing functionality common to requests and responses.
12
 *
13
 * @author Michael Dowling and contributors to guzzlehttp/psr7
14
 * @author Tobias Nyholm <[email protected]>
15
 */
16
trait MessageTrait
17
{
18
    /** @var array Map of all registered headers, as original name => array of values */
19
    private $headers = [];
20
21
    /** @var array Map of lowercase header name => original name at registration */
22
    private $headerNames = [];
23
24
    /** @var string */
25
    private $protocol = '1.1';
26
27
    /** @var StreamInterface */
28
    private $stream;
29
30 5
    public function getProtocolVersion(): string
31
    {
32 5
        return $this->protocol;
33
    }
34
35 4
    public function withProtocolVersion($version): self
36
    {
37 4
        if ($this->protocol === $version) {
38 1
            return $this;
39
        }
40
41 3
        $new = clone $this;
42 3
        $new->protocol = $version;
43
44 3
        return $new;
45
    }
46
47 18
    public function getHeaders(): array
48
    {
49 18
        return $this->headers;
50
    }
51
52 82
    public function hasHeader($header): bool
53
    {
54 82
        return isset($this->headerNames[strtolower($header)]);
55
    }
56
57 27
    public function getHeader($header): array
58
    {
59 27
        $header = strtolower($header);
60
61 27
        if (!isset($this->headerNames[$header])) {
62 5
            return [];
63
        }
64
65 26
        $header = $this->headerNames[$header];
66
67 26
        return $this->headers[$header];
68
    }
69
70 25
    public function getHeaderLine($header): string
71
    {
72 25
        return implode(', ', $this->getHeader($header));
73
    }
74
75 19
    public function withHeader($header, $value): self
76
    {
77 19
        $value = $this->validateAndTrimHeader($header, $value);
78 5
        $normalized = strtolower($header);
79
80 5
        $new = clone $this;
81 5
        if (isset($new->headerNames[$normalized])) {
82 1
            unset($new->headers[$new->headerNames[$normalized]]);
83
        }
84 5
        $new->headerNames[$normalized] = $header;
85 5
        $new->headers[$header] = $value;
86
87 5
        return $new;
88
    }
89
90 33
    public function withAddedHeader($header, $value): self
91
    {
92 33
        if (!is_string($header) || '' === $header) {
93 8
            throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
94
        }
95
96 25
        $new = clone $this;
97 25
        $new->setHeaders([$header => $value]);
98
99 19
        return $new;
100
    }
101
102 5
    public function withoutHeader($header): self
103
    {
104 5
        $normalized = strtolower($header);
105
106 5
        if (!isset($this->headerNames[$normalized])) {
107 2
            return $this;
108
        }
109
110 3
        $header = $this->headerNames[$normalized];
111
112 3
        $new = clone $this;
113 3
        unset($new->headers[$header], $new->headerNames[$normalized]);
114
115 3
        return $new;
116
    }
117
118 9
    public function getBody(): StreamInterface
119
    {
120 9
        if (!$this->stream) {
121 3
            $this->stream = (new StreamFactory())->createStream('');
122
        }
123
124 9
        return $this->stream;
125
    }
126
127 4
    public function withBody(StreamInterface $body): self
128
    {
129 4
        if ($body === $this->stream) {
130 1
            return $this;
131
        }
132
133 3
        $new = clone $this;
134 3
        $new->stream = $body;
135
136 3
        return $new;
137
    }
138
139 142
    private function setHeaders(array $headers): void
140
    {
141 142
        foreach ($headers as $header => $value) {
142 32
            $value = $this->validateAndTrimHeader($header, $value);
143 26
            $normalized = strtolower($header);
144 26
            if (isset($this->headerNames[$normalized])) {
145 14
                $header = $this->headerNames[$normalized];
146 14
                $this->headers[$header] = array_merge($this->headers[$header], $value);
147
            } else {
148 26
                $this->headerNames[$normalized] = $header;
149 26
                $this->headers[$header] = $value;
150
            }
151
        }
152 142
    }
153
154
    /**
155
     * Make sure the header complies with RFC 7230.
156
     *
157
     * Header names must be a non-empty string consisting of token characters.
158
     *
159
     * Header values must be strings consisting of visible characters with all optional
160
     * leading and trailing whitespace stripped. This method will always strip such
161
     * optional whitespace. Note that the method does not allow folding whitespace within
162
     * the values as this was deprecated for almost all instances by the RFC.
163
     *
164
     * header-field = field-name ":" OWS field-value OWS
165
     * field-name   = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
166
     *              / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
167
     * OWS          = *( SP / HTAB )
168
     * field-value  = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
169
     *
170
     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
171
     */
172 48
    private function validateAndTrimHeader($header, $values): array
173
    {
174 48
        if (!is_array($values)) {
175 41
            $values = [$values];
176
        } elseif (empty($values)) {
177 4
            throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.');
178
        } else {
179
            // Non empty array
180 9
            $values = array_values($values);
181
        }
182
183 44
        if (!is_string($header) || 1 !== preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $header)) {
184 8
            throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
185
        }
186
187 36
        foreach ($values as &$v) {
188 36
            if (is_numeric($v)) {
189 2
                $v = (string) $v;
190 36
            } elseif (!is_string($v) || 1 !== preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", $v)) {
191 36
                throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
192
            }
193
        }
194
195 28
        return array_map(function (string $value) {
196 28
            return trim($value, " \t");
197 28
        }, $values);
198
    }
199
}
200