MessageTrait::getBody()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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