PluginProbe
Depicter — Popup & Slider Builder / trunk
Depicter — Popup & Slider Builder vtrunk
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / modules / GuzzleHttp / Psr7 / MessageTrait.php

MessageTrait.php in Depicter — Popup & Slider Builder trunk, at modules/GuzzleHttp/Psr7/MessageTrait.php

266 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Depicter\GuzzleHttp\Psr7;
6
7 use Depicter\Psr\Http\Message\MessageInterface;
8 use Depicter\Psr\Http\Message\StreamInterface;
9
10 /**
11 * Trait implementing functionality common to requests and responses.
12 */
13 trait MessageTrait
14 {
15 /** @var string[][] Map of all registered headers, as original name => array of values */
16 private $headers = [];
17
18 /** @var string[] Map of lowercase header name => original name at registration */
19 private $headerNames = [];
20
21 /** @var string */
22 private $protocol = '1.1';
23
24 /** @var StreamInterface|null */
25 private $stream;
26
27 public function getProtocolVersion(): string
28 {
29 return $this->protocol;
30 }
31
32 public function withProtocolVersion($version): MessageInterface
33 {
34 if ($this->protocol === $version) {
35 return $this;
36 }
37
38 $new = clone $this;
39 $new->protocol = $version;
40
41 return $new;
42 }
43
44 public function getHeaders(): array
45 {
46 return $this->headers;
47 }
48
49 public function hasHeader($header): bool
50 {
51 return isset($this->headerNames[strtolower($header)]);
52 }
53
54 public function getHeader($header): array
55 {
56 $header = strtolower($header);
57
58 if (!isset($this->headerNames[$header])) {
59 return [];
60 }
61
62 $header = $this->headerNames[$header];
63
64 return $this->headers[$header];
65 }
66
67 public function getHeaderLine($header): string
68 {
69 return implode(', ', $this->getHeader($header));
70 }
71
72 public function withHeader($header, $value): MessageInterface
73 {
74 $this->assertHeader($header);
75 $value = $this->normalizeHeaderValue($value);
76 $normalized = strtolower($header);
77
78 $new = clone $this;
79 if (isset($new->headerNames[$normalized])) {
80 unset($new->headers[$new->headerNames[$normalized]]);
81 }
82 $new->headerNames[$normalized] = $header;
83 $new->headers[$header] = $value;
84
85 return $new;
86 }
87
88 public function withAddedHeader($header, $value): MessageInterface
89 {
90 $this->assertHeader($header);
91 $value = $this->normalizeHeaderValue($value);
92 $normalized = strtolower($header);
93
94 $new = clone $this;
95 if (isset($new->headerNames[$normalized])) {
96 $header = $this->headerNames[$normalized];
97 $new->headers[$header] = array_merge($this->headers[$header], $value);
98 } else {
99 $new->headerNames[$normalized] = $header;
100 $new->headers[$header] = $value;
101 }
102
103 return $new;
104 }
105
106 public function withoutHeader($header): MessageInterface
107 {
108 $normalized = strtolower($header);
109
110 if (!isset($this->headerNames[$normalized])) {
111 return $this;
112 }
113
114 $header = $this->headerNames[$normalized];
115
116 $new = clone $this;
117 unset($new->headers[$header], $new->headerNames[$normalized]);
118
119 return $new;
120 }
121
122 public function getBody(): StreamInterface
123 {
124 if (!$this->stream) {
125 $this->stream = Utils::streamFor('');
126 }
127
128 return $this->stream;
129 }
130
131 public function withBody(StreamInterface $body): MessageInterface
132 {
133 if ($body === $this->stream) {
134 return $this;
135 }
136
137 $new = clone $this;
138 $new->stream = $body;
139
140 return $new;
141 }
142
143 /**
144 * @param (string|string[])[] $headers
145 */
146 private function setHeaders(array $headers): void
147 {
148 $this->headerNames = $this->headers = [];
149 foreach ($headers as $header => $value) {
150 // Numeric array keys are converted to int by PHP.
151 $header = (string) $header;
152
153 $this->assertHeader($header);
154 $value = $this->normalizeHeaderValue($value);
155 $normalized = strtolower($header);
156 if (isset($this->headerNames[$normalized])) {
157 $header = $this->headerNames[$normalized];
158 $this->headers[$header] = array_merge($this->headers[$header], $value);
159 } else {
160 $this->headerNames[$normalized] = $header;
161 $this->headers[$header] = $value;
162 }
163 }
164 }
165
166 /**
167 * @param mixed $value
168 *
169 * @return string[]
170 */
171 private function normalizeHeaderValue($value): array
172 {
173 if (!is_array($value)) {
174 return $this->trimAndValidateHeaderValues([$value]);
175 }
176
177 if (count($value) === 0) {
178 throw new \InvalidArgumentException('Header value can not be an empty array.');
179 }
180
181 return $this->trimAndValidateHeaderValues($value);
182 }
183
184 /**
185 * Trims whitespace from the header values.
186 *
187 * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
188 *
189 * header-field = field-name ":" OWS field-value OWS
190 * OWS = *( SP / HTAB )
191 *
192 * @param mixed[] $values Header values
193 *
194 * @return string[] Trimmed header values
195 *
196 * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
197 */
198 private function trimAndValidateHeaderValues(array $values): array
199 {
200 return array_map(function ($value) {
201 if (!is_scalar($value) && null !== $value) {
202 throw new \InvalidArgumentException(sprintf(
203 'Header value must be scalar or null but %s provided.',
204 is_object($value) ? get_class($value) : gettype($value)
205 ));
206 }
207
208 $trimmed = trim((string) $value, " \t");
209 $this->assertValue($trimmed);
210
211 return $trimmed;
212 }, array_values($values));
213 }
214
215 /**
216 * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
217 *
218 * @param mixed $header
219 */
220 private function assertHeader($header): void
221 {
222 if (!is_string($header)) {
223 throw new \InvalidArgumentException(sprintf(
224 'Header name must be a string but %s provided.',
225 is_object($header) ? get_class($header) : gettype($header)
226 ));
227 }
228
229 if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
230 throw new \InvalidArgumentException(
231 sprintf('"%s" is not valid header name.', $header)
232 );
233 }
234 }
235
236 /**
237 * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
238 *
239 * field-value = *( field-content / obs-fold )
240 * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
241 * field-vchar = VCHAR / obs-text
242 * VCHAR = %x21-7E
243 * obs-text = %x80-FF
244 * obs-fold = CRLF 1*( SP / HTAB )
245 */
246 private function assertValue(string $value): void
247 {
248 // The regular expression intentionally does not support the obs-fold production, because as
249 // per RFC 7230#3.2.4:
250 //
251 // A sender MUST NOT generate a message that includes
252 // line folding (i.e., that has any field-value that contains a match to
253 // the obs-fold rule) unless the message is intended for packaging
254 // within the message/http media type.
255 //
256 // Clients must not send a request with line folding and a server sending folded headers is
257 // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
258 // folding is not likely to break any legitimate use case.
259 if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
260 throw new \InvalidArgumentException(
261 sprintf('"%s" is not valid header value.', $value)
262 );
263 }
264 }
265 }
266