| Code Coverage
| Lines | Functions and Methods | Classes and Traits
Total | 0.40% covered (danger) | 0.40% | 1 / 249 | 2.86% covered (danger) | 2.86% | 1 / 35 | CRAP | 0.00% covered (danger) | 0.00% | 0 / 1
Request | 0.40% covered (danger) | 0.40% | 1 / 249 | 2.86% covered (danger) | 2.86% | 1 / 35 | 14110.07 | 0.00% covered (danger) | 0.00% | 0 / 1
__construct | 100.00% covered (success) | 100.00% | 1 / 1 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
get | 0.00% covered (danger) | 0.00% | 0 / 5 | 0.00% covered (danger) | 0.00% | 0 / 1 | 12 |
post | 0.00% covered (danger) | 0.00% | 0 / 5 | 0.00% covered (danger) | 0.00% | 0 / 1 | 12 |
header | 0.00% covered (danger) | 0.00% | 0 / 6 | 0.00% covered (danger) | 0.00% | 0 / 1 | 12 |
cookie | 0.00% covered (danger) | 0.00% | 0 / 12 | 0.00% covered (danger) | 0.00% | 0 / 1 | 30 |
file | 0.00% covered (danger) | 0.00% | 0 / 11 | 0.00% covered (danger) | 0.00% | 0 / 1 | 42 |
method | 0.00% covered (danger) | 0.00% | 0 / 3 | 0.00% covered (danger) | 0.00% | 0 / 1 | 6 |
protocolVersion | 0.00% covered (danger) | 0.00% | 0 / 3 | 0.00% covered (danger) | 0.00% | 0 / 1 | 6 |
host | 0.00% covered (danger) | 0.00% | 0 / 4 | 0.00% covered (danger) | 0.00% | 0 / 1 | 12 |
uri | 0.00% covered (danger) | 0.00% | 0 / 3 | 0.00% covered (danger) | 0.00% | 0 / 1 | 6 |
path | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
queryString | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
session | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
sessionId | 0.00% covered (danger) | 0.00% | 0 / 14 | 0.00% covered (danger) | 0.00% | 0 / 1 | 72 |
isValidSessionId | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 6 |
sessionRegenerateId | 0.00% covered (danger) | 0.00% | 0 / 11 | 0.00% covered (danger) | 0.00% | 0 / 1 | 6 |
rawHead | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
rawBody | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
rawBuffer | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
parseHeadFirstLine | 0.00% covered (danger) | 0.00% | 0 / 4 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
parseProtocolVersion | 0.00% covered (danger) | 0.00% | 0 / 4 | 0.00% covered (danger) | 0.00% | 0 / 1 | 6 |
parseHeaders | 0.00% covered (danger) | 0.00% | 0 / 26 | 0.00% covered (danger) | 0.00% | 0 / 1 | 90 |
parseGet | 0.00% covered (danger) | 0.00% | 0 / 14 | 0.00% covered (danger) | 0.00% | 0 / 1 | 42 |
parsePost | 0.00% covered (danger) | 0.00% | 0 / 21 | 0.00% covered (danger) | 0.00% | 0 / 1 | 72 |
parseUploadFiles | 0.00% covered (danger) | 0.00% | 0 / 17 | 0.00% covered (danger) | 0.00% | 0 / 1 | 30 |
parseUploadFile | 0.00% covered (danger) | 0.00% | 0 / 52 | 0.00% covered (danger) | 0.00% | 0 / 1 | 342 |
createSessionId | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
setSidCookie | 0.00% covered (danger) | 0.00% | 0 / 9 | 0.00% covered (danger) | 0.00% | 0 / 1 | 72 |
__toString | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
__set | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
__get | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
__isset | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
__unset | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
__wakeup | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
destroy | 0.00% covered (danger) | 0.00% | 0 / 10 | 0.00% covered (danger) | 0.00% | 0 / 1 | 56 |

1 | <?php
2 | /**
3 | * This file is part of workerman.
4 | *
5 | * Licensed under The MIT License
6 | * For full copyright and license information, please see the MIT-LICENSE.txt
7 | * Redistributions of files must retain the above copyright notice.
8 | *
9 | * @author walkor<walkor@workerman.net>
10 | * @copyright walkor<walkor@workerman.net>
11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 |
15 | declare(strict_types=1);
16 |
17 | namespace Workerman\Protocols\Http;
18 |
19 | use Exception;
20 | use RuntimeException;
21 | use Stringable;
22 | use Workerman\Connection\TcpConnection;
23 | use Workerman\Protocols\Http;
24 | use function array_walk_recursive;
25 | use function bin2hex;
26 | use function clearstatcache;
27 | use function count;
28 | use function explode;
29 | use function file_put_contents;
30 | use function is_file;
31 | use function json_decode;
32 | use function ltrim;
33 | use function microtime;
34 | use function pack;
35 | use function parse_str;
36 | use function parse_url;
37 | use function preg_match;
38 | use function preg_replace;
39 | use function strlen;
40 | use function strpos;
41 | use function strstr;
42 | use function strtolower;
43 | use function substr;
44 | use function tempnam;
45 | use function trim;
46 | use function unlink;
47 | use function urlencode;
48 |
49 | /**
50 | * Class Request
51 | * @package Workerman\Protocols\Http
52 | */
53 | class Request implements Stringable
54 | {
55 | /**
56 | * Connection.
57 | *
58 | * @var ?TcpConnection
59 | */
60 | public ?TcpConnection $connection = null;
61 |
62 | /**
63 | * @var int
64 | */
65 | public static int $maxFileUploads = 1024;
66 |
67 | /**
68 | * Maximum string length for cache
69 | *
70 | * @var int
71 | */
72 | public const MAX_CACHE_STRING_LENGTH = 4096;
73 |
74 | /**
75 | * Maximum cache size.
76 | *
77 | * @var int
78 | */
79 | public const MAX_CACHE_SIZE = 256;
80 |
81 | /**
82 | * Properties.
83 | *
84 | * @var array
85 | */
86 | public array $properties = [];
87 |
88 | /**
89 | * Request data.
90 | *
91 | * @var array
92 | */
93 | protected array $data = [];
94 |
95 | /**
96 | * Is safe.
97 | *
98 | * @var bool
99 | */
100 | protected bool $isSafe = true;
101 |
102 | /**
103 | * Context.
104 | *
105 | * @var array
106 | */
107 | public array $context = [];
108 |
109 | /**
110 | * Request constructor.
111 | *
112 | */
113 | public function __construct(protected string $buffer) {}
114 |
115 | /**
116 | * Get query.
117 | *
118 | * @param string|null $name
119 | * @param mixed $default
120 | * @return mixed
121 | */
122 | public function get(?string $name = null, mixed $default = null): mixed
123 | {
124 | if (!isset($this->data['get'])) {
125 | $this->parseGet();
126 | }
127 | if (null === $name) {
128 | return $this->data['get'];
129 | }
130 | return $this->data['get'][$name] ?? $default;
131 | }
132 |
133 | /**
134 | * Get post.
135 | *
136 | * @param string|null $name
137 | * @param mixed $default
138 | * @return mixed
139 | */
140 | public function post(?string $name = null, mixed $default = null): mixed
141 | {
142 | if (!isset($this->data['post'])) {
143 | $this->parsePost();
144 | }
145 | if (null === $name) {
146 | return $this->data['post'];
147 | }
148 | return $this->data['post'][$name] ?? $default;
149 | }
150 |
151 | /**
152 | * Get header item by name.
153 | *
154 | * @param string|null $name
155 | * @param mixed $default
156 | * @return mixed
157 | */
158 | public function header(?string $name = null, mixed $default = null): mixed
159 | {
160 | if (!isset($this->data['headers'])) {
161 | $this->parseHeaders();
162 | }
163 | if (null === $name) {
164 | return $this->data['headers'];
165 | }
166 | $name = strtolower($name);
167 | return $this->data['headers'][$name] ?? $default;
168 | }
169 |
170 | /**
171 | * Get cookie item by name.
172 | *
173 | * @param string|null $name
174 | * @param mixed $default
175 | * @return mixed
176 | */
177 | public function cookie(?string $name = null, mixed $default = null): mixed
178 | {
179 | if (!isset($this->data['cookie'])) {
180 | $cookies = explode(';', $this->header('cookie', ''));
181 | $mapped = array();
182 |
183 | foreach ($cookies as $cookie) {
184 | $cookie = explode('=', $cookie, 2);
185 | if (count($cookie) !== 2) {
186 | continue;
187 | }
188 | $mapped[trim($cookie[0])] = $cookie[1];
189 | }
190 | $this->data['cookie'] = $mapped;
191 | }
192 | if ($name === null) {
193 | return $this->data['cookie'];
194 | }
195 | return $this->data['cookie'][$name] ?? $default;
196 | }
197 |
198 | /**
199 | * Get upload files.
200 | *
201 | * @param string|null $name
202 | * @return array|null
203 | */
204 | public function file(?string $name = null): mixed
205 | {
206 | clearstatcache();
207 | if (!empty($this->data['files'])) {
208 | array_walk_recursive($this->data['files'], function ($value, $key) {
209 | if ($key === 'tmp_name' && !is_file($value)) {
210 | $this->data['files'] = [];
211 | }
212 | });
213 | }
214 | if (empty($this->data['files'])) {
215 | $this->parsePost();
216 | }
217 | if (null === $name) {
218 | return $this->data['files'];
219 | }
220 | return $this->data['files'][$name] ?? null;
221 | }
222 |
223 | /**
224 | * Get method.
225 | *
226 | * @return string
227 | */
228 | public function method(): string
229 | {
230 | if (!isset($this->data['method'])) {
231 | $this->parseHeadFirstLine();
232 | }
233 | return $this->data['method'];
234 | }
235 |
236 | /**
237 | * Get http protocol version.
238 | *
239 | * @return string
240 | */
241 | public function protocolVersion(): string
242 | {
243 | if (!isset($this->data['protocolVersion'])) {
244 | $this->parseProtocolVersion();
245 | }
246 | return $this->data['protocolVersion'];
247 | }
248 |
249 | /**
250 | * Get host.
251 | *
252 | * @param bool $withoutPort
253 | * @return string|null
254 | */
255 | public function host(bool $withoutPort = false): ?string
256 | {
257 | $host = $this->header('host');
258 | if ($host && $withoutPort) {
259 | return preg_replace('/:\d{1,5}$/', '', $host);
260 | }
261 | return $host;
262 | }
263 |
264 | /**
265 | * Get uri.
266 | *
267 | * @return string
268 | */
269 | public function uri(): string
270 | {
271 | if (!isset($this->data['uri'])) {
272 | $this->parseHeadFirstLine();
273 | }
274 | return $this->data['uri'];
275 | }
276 |
277 | /**
278 | * Get path.
279 | *
280 | * @return string
281 | */
282 | public function path(): string
283 | {
284 | return $this->data['path'] ??= (string)parse_url($this->uri(), PHP_URL_PATH);
285 | }
286 |
287 | /**
288 | * Get query string.
289 | *
290 | * @return string
291 | */
292 | public function queryString(): string
293 | {
294 | return $this->data['query_string'] ??= (string)parse_url($this->uri(), PHP_URL_QUERY);
295 | }
296 |
297 | /**
298 | * Get session.
299 | *
300 | * @return Session
301 | * @throws Exception
302 | */
303 | public function session(): Session
304 | {
305 | return $this->context['session'] ??= new Session($this->sessionId());
306 | }
307 |
308 | /**
309 | * Get/Set session id.
310 | *
311 | * @param string|null $sessionId
312 | * @return string
313 | * @throws Exception
314 | */
315 | public function sessionId(?string $sessionId = null): string
316 | {
317 | if ($sessionId) {
318 | unset($this->context['sid']);
319 | }
320 | if (!isset($this->context['sid'])) {
321 | $sessionName = Session::$name;
322 | $sid = $sessionId ? '' : $this->cookie($sessionName);
323 | $sid = $this->isValidSessionId($sid) ? $sid : '';
324 | if ($sid === '') {
325 | if (!$this->connection) {
326 | throw new RuntimeException('Request->session() fail, header already send');
327 | }
328 | $sid = $sessionId ?: static::createSessionId();
329 | $cookieParams = Session::getCookieParams();
330 | $this->setSidCookie($sessionName, $sid, $cookieParams);
331 | }
332 | $this->context['sid'] = $sid;
333 | }
334 | return $this->context['sid'];
335 | }
336 |
337 | /**
338 | * Check if session id is valid.
339 | *
340 | * @param mixed $sessionId
341 | * @return bool
342 | */
343 | public function isValidSessionId(mixed $sessionId): bool
344 | {
345 | return is_string($sessionId) && preg_match('/^[a-zA-Z0-9"]+$/', $sessionId);
346 | }
347 |
348 | /**
349 | * Session regenerate id.
350 | *
351 | * @param bool $deleteOldSession
352 | * @return string
353 | * @throws Exception
354 | */
355 | public function sessionRegenerateId(bool $deleteOldSession = false): string
356 | {
357 | $session = $this->session();
358 | $sessionData = $session->all();
359 | if ($deleteOldSession) {
360 | $session->flush();
361 | }
362 | $newSid = static::createSessionId();
363 | $session = new Session($newSid);
364 | $session->put($sessionData);
365 | $cookieParams = Session::getCookieParams();
366 | $sessionName = Session::$name;
367 | $this->setSidCookie($sessionName, $newSid, $cookieParams);
368 | return $newSid;
369 | }
370 |
371 | /**
372 | * Get http raw head.
373 | *
374 | * @return string
375 | */
376 | public function rawHead(): string
377 | {
378 | return $this->data['head'] ??= strstr($this->buffer, "\r\n\r\n", true);
379 | }
380 |
381 | /**
382 | * Get http raw body.
383 | *
384 | * @return string
385 | */
386 | public function rawBody(): string
387 | {
388 | return substr($this->buffer, strpos($this->buffer, "\r\n\r\n") + 4);
389 | }
390 |
391 | /**
392 | * Get raw buffer.
393 | *
394 | * @return string
395 | */
396 | public function rawBuffer(): string
397 | {
398 | return $this->buffer;
399 | }
400 |
401 | /**
402 | * Parse first line of http header buffer.
403 | *
404 | * @return void
405 | */
406 | protected function parseHeadFirstLine(): void
407 | {
408 | $firstLine = strstr($this->buffer, "\r\n", true);
409 | $tmp = explode(' ', $firstLine, 3);
410 | $this->data['method'] = $tmp[0];
411 | $this->data['uri'] = $tmp[1] ?? '/';
412 | }
413 |
414 | /**
415 | * Parse protocol version.
416 | *
417 | * @return void
418 | */
419 | protected function parseProtocolVersion(): void
420 | {
421 | $firstLine = strstr($this->buffer, "\r\n", true);
422 | $httpStr = strstr($firstLine, 'HTTP/');
423 | $protocolVersion = $httpStr ? substr($httpStr, 5) : '1.0';
424 | $this->data['protocolVersion'] = $protocolVersion;
425 | }
426 |
427 | /**
428 | * Parse headers.
429 | *
430 | * @return void
431 | */
432 | protected function parseHeaders(): void
433 | {
434 | static $cache = [];
435 | $this->data['headers'] = [];
436 | $rawHead = $this->rawHead();
437 | $endLinePosition = strpos($rawHead, "\r\n");
438 | if ($endLinePosition === false) {
439 | return;
440 | }
441 | $headBuffer = substr($rawHead, $endLinePosition + 2);
442 | $cacheable = !isset($headBuffer[static::MAX_CACHE_STRING_LENGTH]);
443 | if ($cacheable && isset($cache[$headBuffer])) {
444 | $this->data['headers'] = $cache[$headBuffer];
445 | return;
446 | }
447 | $headData = explode("\r\n", $headBuffer);
448 | foreach ($headData as $content) {
449 | if (str_contains($content, ':')) {
450 | [$key, $value] = explode(':', $content, 2);
451 | $key = strtolower($key);
452 | $value = ltrim($value);
453 | } else {
454 | $key = strtolower($content);
455 | $value = '';
456 | }
457 | if (isset($this->data['headers'][$key])) {
458 | $this->data['headers'][$key] = "{$this->data['headers'][$key]},$value";
459 | } else {
460 | $this->data['headers'][$key] = $value;
461 | }
462 | }
463 | if ($cacheable) {
464 | $cache[$headBuffer] = $this->data['headers'];
465 | if (count($cache) > static::MAX_CACHE_SIZE) {
466 | unset($cache[key($cache)]);
467 | }
468 | }
469 | }
470 |
471 | /**
472 | * Parse head.
473 | *
474 | * @return void
475 | */
476 | protected function parseGet(): void
477 | {
478 | static $cache = [];
479 | $queryString = $this->queryString();
480 | $this->data['get'] = [];
481 | if ($queryString === '') {
482 | return;
483 | }
484 | $cacheable = !isset($queryString[static::MAX_CACHE_STRING_LENGTH]);
485 | if ($cacheable && isset($cache[$queryString])) {
486 | $this->data['get'] = $cache[$queryString];
487 | return;
488 | }
489 | parse_str($queryString, $this->data['get']);
490 | if ($cacheable) {
491 | $cache[$queryString] = $this->data['get'];
492 | if (count($cache) > static::MAX_CACHE_SIZE) {
493 | unset($cache[key($cache)]);
494 | }
495 | }
496 | }
497 |
498 | /**
499 | * Parse post.
500 | *
501 | * @return void
502 | */
503 | protected function parsePost(): void
504 | {
505 | static $cache = [];
506 | $this->data['post'] = $this->data['files'] = [];
507 | $contentType = $this->header('content-type', '');
508 | if (preg_match('/boundary="?(\S+)"?/', $contentType, $match)) {
509 | $httpPostBoundary = '--' . $match[1];
510 | $this->parseUploadFiles($httpPostBoundary);
511 | return;
512 | }
513 | $bodyBuffer = $this->rawBody();
514 | if ($bodyBuffer === '') {
515 | return;
516 | }
517 | $cacheable = !isset($bodyBuffer[static::MAX_CACHE_STRING_LENGTH]);
518 | if ($cacheable && isset($cache[$bodyBuffer])) {
519 | $this->data['post'] = $cache[$bodyBuffer];
520 | return;
521 | }
522 | if (preg_match('/\bjson\b/i', $contentType)) {
523 | $this->data['post'] = (array)json_decode($bodyBuffer, true);
524 | } else {
525 | parse_str($bodyBuffer, $this->data['post']);
526 | }
527 | if ($cacheable) {
528 | $cache[$bodyBuffer] = $this->data['post'];
529 | if (count($cache) > static::MAX_CACHE_SIZE) {
530 | unset($cache[key($cache)]);
531 | }
532 | }
533 | }
534 |
535 | /**
536 | * Parse upload files.
537 | *
538 | * @param string $httpPostBoundary
539 | * @return void
540 | */
541 | protected function parseUploadFiles(string $httpPostBoundary): void
542 | {
543 | $httpPostBoundary = trim($httpPostBoundary, '"');
544 | $buffer = $this->buffer;
545 | $postEncodeString = '';
546 | $filesEncodeString = '';
547 | $files = [];
548 | $bodyPosition = strpos($buffer, "\r\n\r\n") + 4;
549 | $offset = $bodyPosition + strlen($httpPostBoundary) + 2;
550 | $maxCount = static::$maxFileUploads;
551 | while ($maxCount-- > 0 && $offset) {
552 | $offset = $this->parseUploadFile($httpPostBoundary, $offset, $postEncodeString, $filesEncodeString, $files);
553 | }
554 | if ($postEncodeString) {
555 | parse_str($postEncodeString, $this->data['post']);
556 | }
557 |
558 | if ($filesEncodeString) {
559 | parse_str($filesEncodeString, $this->data['files']);
560 | array_walk_recursive($this->data['files'], function (&$value) use ($files) {
561 | $value = $files[$value];
562 | });
563 | }
564 | }
565 |
566 | /**
567 | * Parse upload file.
568 | *
569 | * @param string $boundary
570 | * @param int $sectionStartOffset
571 | * @param string $postEncodeString
572 | * @param string $filesEncodeStr
573 | * @param array $files
574 | * @return int
575 | */
576 | protected function parseUploadFile(string $boundary, int $sectionStartOffset, string &$postEncodeString, string &$filesEncodeStr, array &$files): int
577 | {
578 | $file = [];
579 | $boundary = "\r\n$boundary";
580 | if (strlen($this->buffer) < $sectionStartOffset) {
581 | return 0;
582 | }
583 | $sectionEndOffset = strpos($this->buffer, $boundary, $sectionStartOffset);
584 | if (!$sectionEndOffset) {
585 | return 0;
586 | }
587 | $contentLinesEndOffset = strpos($this->buffer, "\r\n\r\n", $sectionStartOffset);
588 | if (!$contentLinesEndOffset || $contentLinesEndOffset + 4 > $sectionEndOffset) {
589 | return 0;
590 | }
591 | $contentLinesStr = substr($this->buffer, $sectionStartOffset, $contentLinesEndOffset - $sectionStartOffset);
592 | $contentLines = explode("\r\n", trim($contentLinesStr . "\r\n"));
593 | $boundaryValue = substr($this->buffer, $contentLinesEndOffset + 4, $sectionEndOffset - $contentLinesEndOffset - 4);
594 | $uploadKey = false;
595 | foreach ($contentLines as $contentLine) {
596 | if (!strpos($contentLine, ': ')) {
597 | return 0;
598 | }
599 | [$key, $value] = explode(': ', $contentLine);
600 | switch (strtolower($key)) {
601 |
602 | case "content-disposition":
603 | // Is file data.
604 | if (preg_match('/name="(.*?)"; filename="(.*?)"/i', $value, $match)) {
605 | $error = 0;
606 | $tmpFile = '';
607 | $fileName = $match[1];
608 | $size = strlen($boundaryValue);
609 | $tmpUploadDir = HTTP::uploadTmpDir();
610 | if (!$tmpUploadDir) {
611 | $error = UPLOAD_ERR_NO_TMP_DIR;
612 | } else if ($boundaryValue === '' && $fileName === '') {
613 | $error = UPLOAD_ERR_NO_FILE;
614 | } else {
615 | $tmpFile = tempnam($tmpUploadDir, 'workerman.upload.');
616 | if ($tmpFile === false || false === file_put_contents($tmpFile, $boundaryValue)) {
617 | $error = UPLOAD_ERR_CANT_WRITE;
618 | }
619 | }
620 | $uploadKey = $fileName;
621 | // Parse upload files.
622 | $file = [...$file, 'name' => $match[2], 'tmp_name' => $tmpFile, 'size' => $size, 'error' => $error, 'full_path' => $match[2]];
623 | $file['type'] ??= '';
624 | break;
625 | }
626 | // Is post field.
627 | // Parse $POST.
628 | if (preg_match('/name="(.*?)"$/', $value, $match)) {
629 | $k = $match[1];
630 | $postEncodeString .= urlencode($k) . "=" . urlencode($boundaryValue) . '&';
631 | }
632 | return $sectionEndOffset + strlen($boundary) + 2;
633 |
634 | case "content-type":
635 | $file['type'] = trim($value);
636 | break;
637 |
638 | case "webkitrelativepath":
639 | $file['full_path'] = trim($value);
640 | break;
641 | }
642 | }
643 | if ($uploadKey === false) {
644 | return 0;
645 | }
646 | $filesEncodeStr .= urlencode($uploadKey) . '=' . count($files) . '&';
647 | $files[] = $file;
648 |
649 | return $sectionEndOffset + strlen($boundary) + 2;
650 | }
651 |
652 | /**
653 | * Create session id.
654 | *
655 | * @return string
656 | * @throws Exception
657 | */
658 | public static function createSessionId(): string
659 | {
660 | return bin2hex(pack('d', microtime(true)) . random_bytes(8));
661 | }
662 |
663 | /**
664 | * @param string $sessionName
665 | * @param string $sid
666 | * @param array $cookieParams
667 | * @return void
668 | */
669 | protected function setSidCookie(string $sessionName, string $sid, array $cookieParams): void
670 | {
671 | if (!$this->connection) {
672 | throw new RuntimeException('Request->setSidCookie() fail, header already send');
673 | }
674 | $this->connection->headers['Set-Cookie'] = [$sessionName . '=' . $sid
675 | . (empty($cookieParams['domain']) ? '' : '; Domain=' . $cookieParams['domain'])
676 | . (empty($cookieParams['lifetime']) ? '' : '; Max-Age=' . $cookieParams['lifetime'])
677 | . (empty($cookieParams['path']) ? '' : '; Path=' . $cookieParams['path'])
678 | . (empty($cookieParams['samesite']) ? '' : '; SameSite=' . $cookieParams['samesite'])
679 | . (!$cookieParams['secure'] ? '' : '; Secure')
680 | . (!$cookieParams['httponly'] ? '' : '; HttpOnly')];
681 | }
682 |
683 | /**
684 | * __toString.
685 | */
686 | public function __toString(): string
687 | {
688 | return $this->buffer;
689 | }
690 |
691 | /**
692 | * Setter.
693 | *
694 | * @param string $name
695 | * @param mixed $value
696 | * @return void
697 | */
698 | public function __set(string $name, mixed $value): void
699 | {
700 | $this->properties[$name] = $value;
701 | }
702 |
703 | /**
704 | * Getter.
705 | *
706 | * @param string $name
707 | * @return mixed
708 | */
709 | public function __get(string $name): mixed
710 | {
711 | return $this->properties[$name] ?? null;
712 | }
713 |
714 | /**
715 | * Isset.
716 | *
717 | * @param string $name
718 | * @return bool
719 | */
720 | public function __isset(string $name): bool
721 | {
722 | return isset($this->properties[$name]);
723 | }
724 |
725 | /**
726 | * Unset.
727 | *
728 | * @param string $name
729 | * @return void
730 | */
731 | public function __unset(string $name): void
732 | {
733 | unset($this->properties[$name]);
734 | }
735 |
736 | /**
737 | * __wakeup.
738 | *
739 | * @return void
740 | */
741 | public function __wakeup(): void
742 | {
743 | $this->isSafe = false;
744 | }
745 |
746 | /**
747 | * Destroy.
748 | *
749 | * @return void
750 | */
751 | public function destroy(): void
752 | {
753 | if ($this->context) {
754 | $this->context = [];
755 | }
756 | if ($this->properties) {
757 | $this->properties = [];
758 | }
759 | if (isset($this->data['files']) && $this->isSafe) {
760 | clearstatcache();
761 | array_walk_recursive($this->data['files'], function ($value, $key) {
762 | if ($key === 'tmp_name' && is_file($value)) {
763 | unlink($value);
764 | }
765 | });
766 | }
767 | }
768 |
769 | }