Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
httpheaders.php
1<?php
2
10namespace Bitrix\Main\Web;
11
14use IteratorAggregate;
15use Traversable;
16
17class HttpHeaders implements IteratorAggregate
18{
19 public const DEFAULT_HTTP_STATUS = 0;
20
21 protected array $headers = [];
22 protected string $version = '1.1';
23 protected int $status;
24 protected ?string $reasonPhrase = null;
25
29 public function __construct(array $headers = null)
30 {
31 if ($headers !== null)
32 {
33 foreach ($headers as $header => $value)
34 {
35 $this->add($header, $value);
36 }
37 }
38 }
39
46 public function add($name, $value)
47 {
48 // compatibility
49 $name = (string)$name;
50
51 // PSR-7: The implementation SHOULD reject invalid values and SHOULD NOT make any attempt to automatically correct the provided values.
52 if ($name == '' || !static::validateName($name))
53 {
54 throw new \InvalidArgumentException("Invalid header name '{$name}'.");
55 }
56
57 if (!is_array($value))
58 {
59 $value = [$value];
60 }
61
62 foreach ($value as $key => $val)
63 {
64 $value[$key] = (string)$val;
65 if (!static::validateValue($value[$key]))
66 {
67 throw new \InvalidArgumentException("Invalid header value '{$value[$key]}'.");
68 }
69 }
70
71 $nameLower = strtolower($name);
72
73 if (!isset($this->headers[$nameLower]))
74 {
75 $this->headers[$nameLower] = [
76 'name' => $name,
77 'values' => [],
78 ];
79 }
80 foreach ($value as $val)
81 {
82 $this->headers[$nameLower]['values'][] = $val;
83 }
84 }
85
94 protected static function validateName(string $name): bool
95 {
96 return (!str_contains($name, "\0") && preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name));
97 }
98
107 protected static function validateValue(string $value): bool
108 {
109 return (!str_contains($value, "\0") && preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/', $value));
110 }
111
117 public function set($name, $value)
118 {
119 $this->delete($name);
120 $this->add($name, $value);
121 }
122
129 public function get($name, $returnArray = false)
130 {
131 $nameLower = strtolower($name);
132
133 if (isset($this->headers[$nameLower]))
134 {
135 if ($returnArray)
136 {
137 return $this->headers[$nameLower]['values'];
138 }
139
140 return $this->headers[$nameLower]['values'][0];
141 }
142
143 return null;
144 }
145
152 public function delete($name)
153 {
154 $nameLower = strtolower($name);
155
156 if (isset($this->headers[$nameLower]))
157 {
158 unset($this->headers[$nameLower]);
159 }
160 }
161
168 public function has(string $name): bool
169 {
170 $nameLower = strtolower($name);
171
172 return isset($this->headers[$nameLower]);
173 }
174
178 public function clear()
179 {
180 $this->headers = [];
181 }
182
187 public function toString()
188 {
189 $str = "";
190 foreach ($this->headers as $header)
191 {
192 foreach ($header["values"] as $value)
193 {
194 $str .= $header["name"] . ": " . $value . "\r\n";
195 }
196 }
197
198 return $str;
199 }
200
205 public function toArray()
206 {
207 return $this->headers;
208 }
209
214 public function getContentType()
215 {
216 $contentType = $this->get("Content-Type");
217 if ($contentType !== null)
218 {
219 $parts = explode(";", $contentType);
220
221 // RFC 2045 says: "The type, subtype, and parameter names are not case-sensitive."
222 return strtolower(trim($parts[0]));
223 }
224
225 return null;
226 }
227
232 public function getContentTypeAttribute(string $attribute)
233 {
234 $contentType = $this->get('Content-Type');
235 if ($contentType !== null)
236 {
237 $attribute = strtolower($attribute);
238 $parts = explode(';', $contentType);
239
240 foreach ($parts as $part)
241 {
242 $values = explode('=', $part);
243 if (strtolower(trim($values[0])) == $attribute)
244 {
245 return trim($values[1]);
246 }
247 }
248 }
249
250 return null;
251 }
252
257 public function getBoundary()
258 {
259 return $this->getContentTypeAttribute('boundary');
260 }
261
266 public function getCharset()
267 {
268 return $this->getContentTypeAttribute('charset');
269 }
270
275 public function getContentDisposition()
276 {
277 $contentDisposition = $this->get("Content-Disposition");
278 if ($contentDisposition !== null)
279 {
280 $parts = explode(";", $contentDisposition);
281
282 return trim($parts[0]);
283 }
284
285 return null;
286 }
287
293 public function getFilename()
294 {
295 $contentDisposition = $this->get('Content-Disposition');
296 if ($contentDisposition !== null)
297 {
298 $filename = null;
299 $encoding = null;
300
301 $contentElements = explode(';', $contentDisposition);
302 foreach ($contentElements as $contentElement)
303 {
304 $contentElement = trim($contentElement);
305 if (preg_match('/^filename\*=(.+)\'(.+)?\'(.+)$/', $contentElement, $matches))
306 {
307 $filename = $matches[3];
308 $encoding = $matches[1];
309 break;
310 }
311 elseif (preg_match('/^filename="(.+)"$/', $contentElement, $matches))
312 {
313 $filename = $matches[1];
314 }
315 elseif (preg_match('/^filename=(.+)$/', $contentElement, $matches))
316 {
317 $filename = $matches[1];
318 }
319 }
320
321 if ($filename <> '')
322 {
323 $filename = urldecode($filename);
324
325 if ($encoding <> '')
326 {
327 $charset = Context::getCurrent()->getCulture()->getCharset();
328 $filename = Encoding::convertEncoding($filename, $encoding, $charset);
329 }
330 }
331
332 return $filename;
333 }
334
335 return null;
336 }
337
345 public function getIterator(): Traversable
346 {
347 $toIterate = [];
348 foreach ($this->headers as $header)
349 {
350 $toIterate[$header['name']] = $header['values'];
351 }
352
353 return new \ArrayIterator($toIterate);
354 }
355
360 public function getCookies(): HttpCookies
361 {
362 $cookies = new HttpCookies();
363
364 if ($this->has('set-cookie'))
365 {
366 foreach ($this->get('set-cookie', true) as $value)
367 {
368 $cookies->addFromString($value);
369 }
370 }
371
372 return $cookies;
373 }
374
380 public function getHeaders(): array
381 {
382 return iterator_to_array($this->getIterator());
383 }
384
391 public static function createFromString(string $response): HttpHeaders
392 {
393 $headers = new static();
394
395 $headerName = null;
396 foreach (explode("\n", $response) as $k => $header)
397 {
398 if ($k == 0)
399 {
400 $headers->parseStatus($header);
401 }
402 elseif (preg_match("/^[ \\t]/", $header))
403 {
404 if ($headerName !== null)
405 {
406 try
407 {
408 $headers->add($headerName, trim($header));
409 }
410 catch (\InvalidArgumentException)
411 {
412 // ignore an invalid header
413 }
414 }
415 }
416 elseif (str_contains($header, ':'))
417 {
418 [$headerName, $headerValue] = explode(':', $header, 2);
419 try
420 {
421 $headers->add($headerName, trim($headerValue));
422 }
423 catch (\InvalidArgumentException)
424 {
425 // ignore an invalid header
426 }
427 }
428 }
429
430 return $headers;
431 }
432
437 public function parseStatus(string $status): HttpHeaders
438 {
439 if (preg_match('#^HTTP/(\S+) (\d+) *(.*)#', $status, $find))
440 {
441 $this->version = $find[1];
442 $this->setStatus((int)$find[2], trim($find[3]));
443 }
444
445 return $this;
446 }
447
455 public function setStatus(int $status, ?string $reasonPhrase = null): HttpHeaders
456 {
457 $this->status = $status;
458 $this->reasonPhrase = $reasonPhrase;
459
460 return $this;
461 }
462
463 public function getStatus(): int
464 {
465 return $this->status ?? self::DEFAULT_HTTP_STATUS;
466 }
467
468 public function setVersion(string $version): HttpHeaders
469 {
470 $this->version = $version;
471
472 return $this;
473 }
474
475 public function getVersion(): ?string
476 {
477 return $this->version;
478 }
479
480 public function getReasonPhrase(): string
481 {
482 return $this->reasonPhrase ?? '';
483 }
484}
static getCurrent()
Definition context.php:241
getContentTypeAttribute(string $attribute)
setStatus(int $status, ?string $reasonPhrase=null)
static validateName(string $name)
static validateValue(string $value)
setVersion(string $version)
static createFromString(string $response)
__construct(array $headers=null)