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