| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736 |
- <?php
- /**
- * This file is part of workerman.
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the MIT-LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @author walkor<walkor@workerman.net>
- * @copyright walkor<walkor@workerman.net>
- * @link http://www.workerman.net/
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
- */
- declare(strict_types=1);
- namespace Workerman\Protocols\Http;
- use Exception;
- use RuntimeException;
- use Workerman\Connection\TcpConnection;
- use Workerman\Protocols\Http;
- use function array_walk_recursive;
- use function bin2hex;
- use function clearstatcache;
- use function count;
- use function explode;
- use function file_put_contents;
- use function is_file;
- use function json_decode;
- use function ltrim;
- use function microtime;
- use function pack;
- use function parse_str;
- use function parse_url;
- use function preg_match;
- use function preg_replace;
- use function strlen;
- use function strpos;
- use function strstr;
- use function strtolower;
- use function substr;
- use function tempnam;
- use function trim;
- use function unlink;
- use function urlencode;
- /**
- * Class Request
- * @package Workerman\Protocols\Http
- */
- class Request
- {
- /**
- * Connection.
- *
- * @var ?TcpConnection
- */
- public ?TcpConnection $connection = null;
- /**
- * Session instance.
- *
- * @var ?Session
- */
- public ?Session $session = null;
- /**
- * @var int
- */
- public static int $maxFileUploads = 1024;
- /**
- * Properties.
- *
- * @var array
- */
- public array $properties = [];
- /**
- * Http buffer.
- *
- * @var string
- */
- protected string $buffer;
- /**
- * Request data.
- *
- * @var array
- */
- protected array $data = [];
- /**
- * Enable cache.
- *
- * @var bool
- */
- protected static bool $enableCache = true;
- /**
- * Request constructor.
- *
- * @param string $buffer
- */
- public function __construct(string $buffer)
- {
- $this->buffer = $buffer;
- }
- /**
- * Get query.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return mixed
- */
- public function get(string $name = null, mixed $default = null): mixed
- {
- if (!isset($this->data['get'])) {
- $this->parseGet();
- }
- if (null === $name) {
- return $this->data['get'];
- }
- return $this->data['get'][$name] ?? $default;
- }
- /**
- * Get post.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return mixed
- */
- public function post(string $name = null, mixed $default = null): mixed
- {
- if (!isset($this->data['post'])) {
- $this->parsePost();
- }
- if (null === $name) {
- return $this->data['post'];
- }
- return $this->data['post'][$name] ?? $default;
- }
- /**
- * Get header item by name.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return mixed
- */
- public function header(string $name = null, mixed $default = null): mixed
- {
- if (!isset($this->data['headers'])) {
- $this->parseHeaders();
- }
- if (null === $name) {
- return $this->data['headers'];
- }
- $name = strtolower($name);
- return $this->data['headers'][$name] ?? $default;
- }
- /**
- * Get cookie item by name.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return mixed
- */
- public function cookie(string $name = null, mixed $default = null): mixed
- {
- if (!isset($this->data['cookie'])) {
- $this->data['cookie'] = [];
- parse_str(preg_replace('/; ?/', '&', $this->header('cookie', '')), $this->data['cookie']);
- }
- if ($name === null) {
- return $this->data['cookie'];
- }
- return $this->data['cookie'][$name] ?? $default;
- }
- /**
- * Get upload files.
- *
- * @param string|null $name
- * @return array|null
- */
- public function file(string $name = null)
- {
- if (!isset($this->data['files'])) {
- $this->parsePost();
- }
- if (null === $name) {
- return $this->data['files'];
- }
- return $this->data['files'][$name] ?? null;
- }
- /**
- * Get method.
- *
- * @return string
- */
- public function method(): string
- {
- if (!isset($this->data['method'])) {
- $this->parseHeadFirstLine();
- }
- return $this->data['method'];
- }
- /**
- * Get http protocol version.
- *
- * @return string
- */
- public function protocolVersion(): string
- {
- if (!isset($this->data['protocolVersion'])) {
- $this->parseProtocolVersion();
- }
- return $this->data['protocolVersion'];
- }
- /**
- * Get host.
- *
- * @param bool $withoutPort
- * @return string|null
- */
- public function host(bool $withoutPort = false): ?string
- {
- $host = $this->header('host');
- if ($host && $withoutPort && $pos = strpos($host, ':')) {
- return substr($host, 0, $pos);
- }
- return $host;
- }
- /**
- * Get uri.
- *
- * @return string
- */
- public function uri(): string
- {
- if (!isset($this->data['uri'])) {
- $this->parseHeadFirstLine();
- }
- return $this->data['uri'];
- }
- /**
- * Get path.
- *
- * @return string
- */
- public function path(): string
- {
- if (!isset($this->data['path'])) {
- $this->data['path'] = (string)parse_url($this->uri(), PHP_URL_PATH);
- }
- return $this->data['path'];
- }
- /**
- * Get query string.
- *
- * @return string
- */
- public function queryString(): string
- {
- if (!isset($this->data['query_string'])) {
- $this->data['query_string'] = (string)parse_url($this->uri(), PHP_URL_QUERY);
- }
- return $this->data['query_string'];
- }
- /**
- * Get session.
- *
- * @return Session
- * @throws Exception
- */
- public function session(): Session
- {
- if ($this->session === null) {
- $this->session = new Session($this->sessionId());
- }
- return $this->session;
- }
- /**
- * Get/Set session id.
- *
- * @param null $sessionId
- * @return string
- * @throws Exception
- */
- public function sessionId($sessionId = null): string
- {
- if ($sessionId) {
- unset($this->sid);
- }
- if (!isset($this->sid)) {
- $sessionName = Session::$name;
- $sid = $sessionId ? '' : $this->cookie($sessionName);
- if ($sid === '' || $sid === null) {
- if (!$this->connection) {
- throw new RuntimeException('Request->session() fail, header already send');
- }
- $sid = $sessionId ?: static::createSessionId();
- $cookieParams = Session::getCookieParams();
- $this->setSidCookie($sessionName, $sid, $cookieParams);
- }
- $this->sid = $sid;
- }
- return $this->sid;
- }
- /**
- * Session regenerate id.
- *
- * @param bool $deleteOldSession
- * @return string
- * @throws Exception
- */
- public function sessionRegenerateId(bool $deleteOldSession = false): string
- {
- $session = $this->session();
- $sessionData = $session->all();
- if ($deleteOldSession) {
- $session->flush();
- }
- $newSid = static::createSessionId();
- $session = new Session($newSid);
- $session->put($sessionData);
- $cookieParams = Session::getCookieParams();
- $sessionName = Session::$name;
- $this->setSidCookie($sessionName, $newSid, $cookieParams);
- return $newSid;
- }
- /**
- * Get http raw head.
- *
- * @return string
- */
- public function rawHead(): string
- {
- if (!isset($this->data['head'])) {
- $this->data['head'] = strstr($this->buffer, "\r\n\r\n", true);
- }
- return $this->data['head'];
- }
- /**
- * Get http raw body.
- *
- * @return string
- */
- public function rawBody(): string
- {
- return substr($this->buffer, strpos($this->buffer, "\r\n\r\n") + 4);
- }
- /**
- * Get raw buffer.
- *
- * @return string
- */
- public function rawBuffer(): string
- {
- return $this->buffer;
- }
- /**
- * Enable or disable cache.
- *
- * @param bool $value
- */
- public static function enableCache(bool $value): void
- {
- static::$enableCache = $value;
- }
- /**
- * Parse first line of http header buffer.
- *
- * @return void
- */
- protected function parseHeadFirstLine(): void
- {
- $firstLine = strstr($this->buffer, "\r\n", true);
- $tmp = explode(' ', $firstLine, 3);
- $this->data['method'] = $tmp[0];
- $this->data['uri'] = $tmp[1] ?? '/';
- }
- /**
- * Parse protocol version.
- *
- * @return void
- */
- protected function parseProtocolVersion(): void
- {
- $firstLine = strstr($this->buffer, "\r\n", true);
- $protocolVersion = substr(strstr($firstLine, 'HTTP/'), 5);
- $this->data['protocolVersion'] = $protocolVersion ?: '1.0';
- }
- /**
- * Parse headers.
- *
- * @return void
- */
- protected function parseHeaders(): void
- {
- static $cache = [];
- $this->data['headers'] = [];
- $rawHead = $this->rawHead();
- $endLinePosition = strpos($rawHead, "\r\n");
- if ($endLinePosition === false) {
- return;
- }
- $headBuffer = substr($rawHead, $endLinePosition + 2);
- $cacheable = static::$enableCache && !isset($headBuffer[4096]);
- if ($cacheable && isset($cache[$headBuffer])) {
- $this->data['headers'] = $cache[$headBuffer];
- return;
- }
- $headData = explode("\r\n", $headBuffer);
- foreach ($headData as $content) {
- if (str_contains($content, ':')) {
- [$key, $value] = explode(':', $content, 2);
- $key = strtolower($key);
- $value = ltrim($value);
- } else {
- $key = strtolower($content);
- $value = '';
- }
- if (isset($this->data['headers'][$key])) {
- $this->data['headers'][$key] = "{$this->data['headers'][$key]},$value";
- } else {
- $this->data['headers'][$key] = $value;
- }
- }
- if ($cacheable) {
- $cache[$headBuffer] = $this->data['headers'];
- if (count($cache) > 128) {
- unset($cache[key($cache)]);
- }
- }
- }
- /**
- * Parse head.
- *
- * @return void
- */
- protected function parseGet(): void
- {
- static $cache = [];
- $queryString = $this->queryString();
- $this->data['get'] = [];
- if ($queryString === '') {
- return;
- }
- $cacheable = static::$enableCache && !isset($queryString[1024]);
- if ($cacheable && isset($cache[$queryString])) {
- $this->data['get'] = $cache[$queryString];
- return;
- }
- parse_str($queryString, $this->data['get']);
- if ($cacheable) {
- $cache[$queryString] = $this->data['get'];
- if (count($cache) > 256) {
- unset($cache[key($cache)]);
- }
- }
- }
- /**
- * Parse post.
- *
- * @return void
- */
- protected function parsePost(): void
- {
- static $cache = [];
- $this->data['post'] = $this->data['files'] = [];
- $contentType = $this->header('content-type', '');
- if (preg_match('/boundary="?(\S+)"?/', $contentType, $match)) {
- $httpPostBoundary = '--' . $match[1];
- $this->parseUploadFiles($httpPostBoundary);
- return;
- }
- $bodyBuffer = $this->rawBody();
- if ($bodyBuffer === '') {
- return;
- }
- $cacheable = static::$enableCache && !isset($bodyBuffer[1024]);
- if ($cacheable && isset($cache[$bodyBuffer])) {
- $this->data['post'] = $cache[$bodyBuffer];
- return;
- }
- if (preg_match('/\bjson\b/i', $contentType)) {
- $this->data['post'] = (array)json_decode($bodyBuffer, true);
- } else {
- parse_str($bodyBuffer, $this->data['post']);
- }
- if ($cacheable) {
- $cache[$bodyBuffer] = $this->data['post'];
- if (count($cache) > 256) {
- unset($cache[key($cache)]);
- }
- }
- }
- /**
- * Parse upload files.
- *
- * @param string $httpPostBoundary
- * @return void
- */
- protected function parseUploadFiles(string $httpPostBoundary): void
- {
- $httpPostBoundary = trim($httpPostBoundary, '"');
- $buffer = $this->buffer;
- $postEncodeString = '';
- $filesEncodeString = '';
- $files = [];
- $bodyPosition = strpos($buffer, "\r\n\r\n") + 4;
- $offset = $bodyPosition + strlen($httpPostBoundary) + 2;
- $maxCount = static::$maxFileUploads;
- while ($maxCount-- > 0 && $offset) {
- $offset = $this->parseUploadFile($httpPostBoundary, $offset, $postEncodeString, $filesEncodeString, $files);
- }
- if ($postEncodeString) {
- parse_str($postEncodeString, $this->data['post']);
- }
- if ($filesEncodeString) {
- parse_str($filesEncodeString, $this->data['files']);
- array_walk_recursive($this->data['files'], function (&$value) use ($files) {
- $value = $files[$value];
- });
- }
- }
- /**
- * Parse upload file.
- *
- * @param $boundary
- * @param $sectionStartOffset
- * @param $postEncodeString
- * @param $filesEncodeStr
- * @param $files
- * @return int
- */
- protected function parseUploadFile($boundary, $sectionStartOffset, &$postEncodeString, &$filesEncodeStr, &$files): int
- {
- $file = [];
- $boundary = "\r\n$boundary";
- if (strlen($this->buffer) < $sectionStartOffset) {
- return 0;
- }
- $sectionEndOffset = strpos($this->buffer, $boundary, $sectionStartOffset);
- if (!$sectionEndOffset) {
- return 0;
- }
- $contentLinesEndOffset = strpos($this->buffer, "\r\n\r\n", $sectionStartOffset);
- if (!$contentLinesEndOffset || $contentLinesEndOffset + 4 > $sectionEndOffset) {
- return 0;
- }
- $contentLinesStr = substr($this->buffer, $sectionStartOffset, $contentLinesEndOffset - $sectionStartOffset);
- $contentLines = explode("\r\n", trim($contentLinesStr . "\r\n"));
- $boundaryValue = substr($this->buffer, $contentLinesEndOffset + 4, $sectionEndOffset - $contentLinesEndOffset - 4);
- $uploadKey = false;
- foreach ($contentLines as $contentLine) {
- if (!strpos($contentLine, ': ')) {
- return 0;
- }
- [$key, $value] = explode(': ', $contentLine);
- switch (strtolower($key)) {
- case "content-disposition":
- // Is file data.
- if (preg_match('/name="(.*?)"; filename="(.*?)"/i', $value, $match)) {
- $error = 0;
- $tmpFile = '';
- $fileName = $match[1];
- $size = strlen($boundaryValue);
- $tmpUploadDir = HTTP::uploadTmpDir();
- if (!$tmpUploadDir) {
- $error = UPLOAD_ERR_NO_TMP_DIR;
- } else if ($boundaryValue === '' && $fileName === '') {
- $error = UPLOAD_ERR_NO_FILE;
- } else {
- $tmpFile = tempnam($tmpUploadDir, 'workerman.upload.');
- if ($tmpFile === false || false === file_put_contents($tmpFile, $boundaryValue)) {
- $error = UPLOAD_ERR_CANT_WRITE;
- }
- }
- $uploadKey = $fileName;
- // Parse upload files.
- $file = [...$file, 'name' => $match[2], 'tmp_name' => $tmpFile, 'size' => $size, 'error' => $error];
- if (!isset($file['type'])) {
- $file['type'] = '';
- }
- break;
- }
- // Is post field.
- // Parse $POST.
- if (preg_match('/name="(.*?)"$/', $value, $match)) {
- $k = $match[1];
- $postEncodeString .= urlencode($k) . "=" . urlencode($boundaryValue) . '&';
- }
- return $sectionEndOffset + strlen($boundary) + 2;
- case "content-type":
- $file['type'] = trim($value);
- break;
- }
- }
- if ($uploadKey === false) {
- return 0;
- }
- $filesEncodeStr .= urlencode($uploadKey) . '=' . count($files) . '&';
- $files[] = $file;
- return $sectionEndOffset + strlen($boundary) + 2;
- }
- /**
- * Create session id.
- *
- * @return string
- * @throws Exception
- */
- public static function createSessionId(): string
- {
- return bin2hex(pack('d', microtime(true)) . random_bytes(8));
- }
- /**
- * @param string $sessionName
- * @param string $sid
- * @param array $cookieParams
- * @return void
- */
- protected function setSidCookie(string $sessionName, string $sid, array $cookieParams): void
- {
- if (!$this->connection) {
- throw new RuntimeException('Request->setSidCookie() fail, header already send');
- }
- $this->connection->headers['Set-Cookie'] = [$sessionName . '=' . $sid
- . (empty($cookieParams['domain']) ? '' : '; Domain=' . $cookieParams['domain'])
- . (empty($cookieParams['lifetime']) ? '' : '; Max-Age=' . $cookieParams['lifetime'])
- . (empty($cookieParams['path']) ? '' : '; Path=' . $cookieParams['path'])
- . (empty($cookieParams['samesite']) ? '' : '; SameSite=' . $cookieParams['samesite'])
- . (!$cookieParams['secure'] ? '' : '; Secure')
- . (!$cookieParams['httponly'] ? '' : '; HttpOnly')];
- }
- /**
- * __toString.
- */
- public function __toString()
- {
- return $this->buffer;
- }
- /**
- * Setter.
- *
- * @param string $name
- * @param mixed $value
- * @return void
- */
- public function __set(string $name, mixed $value)
- {
- $this->properties[$name] = $value;
- }
- /**
- * Getter.
- *
- * @param string $name
- * @return mixed|null
- */
- public function __get(string $name)
- {
- return $this->properties[$name] ?? null;
- }
- /**
- * Isset.
- *
- * @param string $name
- * @return bool
- */
- public function __isset(string $name)
- {
- return isset($this->properties[$name]);
- }
- /**
- * Unset.
- *
- * @param string $name
- * @return void
- */
- public function __unset(string $name)
- {
- unset($this->properties[$name]);
- }
- /**
- * __destruct.
- *
- * @return void
- */
- public function __destruct()
- {
- if (isset($this->data['files'])) {
- clearstatcache();
- array_walk_recursive($this->data['files'], function ($value, $key) {
- if ($key === 'tmp_name' && is_file($value)) {
- unlink($value);
- }
- });
- }
- }
- }
|