source: vendor/guzzlehttp/psr7/src/MessageTrait.php@ f9c482b

Last change on this file since f9c482b was f9c482b, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload new project files

  • Property mode set to 100644
File size: 7.6 KB
Line 
1<?php
2
3declare(strict_types=1);
4
5namespace GuzzleHttp\Psr7;
6
7use Psr\Http\Message\MessageInterface;
8use Psr\Http\Message\StreamInterface;
9
10/**
11 * Trait implementing functionality common to requests and responses.
12 */
13trait 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}
Note: See TracBrowser for help on using the repository browser.