123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 |
- <?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
- */
- namespace Workerman\Protocols\Http;
- use Workerman\Connection\TcpConnection;
- use Workerman\Protocols\Http\Session;
- use Workerman\Protocols\Http;
- use Workerman\Worker;
- /**
- * Class Request
- * @package Workerman\Protocols\Http
- */
- class Request
- {
- /**
- * Connection.
- *
- * @var TcpConnection
- */
- public $connection = null;
- /**
- * Session instance.
- *
- * @var Session
- */
- public $session = null;
- /**
- * Properties.
- *
- * @var array
- */
- public $properties = array();
- /**
- * @var int
- */
- public static $maxFileUploads = 1024;
- /**
- * Http buffer.
- *
- * @var string
- */
- protected $_buffer = null;
- /**
- * Request data.
- *
- * @var array
- */
- protected $_data = null;
- /**
- * Enable cache.
- *
- * @var bool
- */
- protected static $_enableCache = true;
- /**
- * Is safe.
- *
- * @var bool
- */
- protected $_isSafe = true;
- /**
- * Request constructor.
- *
- * @param string $buffer
- */
- public function __construct($buffer)
- {
- $this->_buffer = $buffer;
- }
- /**
- * $_GET.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return mixed|null
- */
- public function get($name = null, $default = null)
- {
- if (!isset($this->_data['get'])) {
- $this->parseGet();
- }
- if (null === $name) {
- return $this->_data['get'];
- }
- return isset($this->_data['get'][$name]) ? $this->_data['get'][$name] : $default;
- }
- /**
- * $_POST.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return mixed|null
- */
- public function post($name = null, $default = null)
- {
- if (!isset($this->_data['post'])) {
- $this->parsePost();
- }
- if (null === $name) {
- return $this->_data['post'];
- }
- return isset($this->_data['post'][$name]) ? $this->_data['post'][$name] : $default;
- }
- /**
- * Get header item by name.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return array|string|null
- */
- public function header($name = null, $default = null)
- {
- if (!isset($this->_data['headers'])) {
- $this->parseHeaders();
- }
- if (null === $name) {
- return $this->_data['headers'];
- }
- $name = \strtolower($name);
- return isset($this->_data['headers'][$name]) ? $this->_data['headers'][$name] : $default;
- }
- /**
- * Get cookie item by name.
- *
- * @param string|null $name
- * @param mixed|null $default
- * @return array|string|null
- */
- public function cookie($name = null, $default = null)
- {
- if (!isset($this->_data['cookie'])) {
- $this->_data['cookie'] = array();
- \parse_str(\preg_replace('/; ?/', '&', $this->header('cookie', '')), $this->_data['cookie']);
- }
- if ($name === null) {
- return $this->_data['cookie'];
- }
- return isset($this->_data['cookie'][$name]) ? $this->_data['cookie'][$name] : $default;
- }
- /**
- * Get upload files.
- *
- * @param string|null $name
- * @return array|null
- */
- public function file($name = null)
- {
- if (!isset($this->_data['files'])) {
- $this->parsePost();
- }
- if (null === $name) {
- return $this->_data['files'];
- }
- return isset($this->_data['files'][$name]) ? $this->_data['files'][$name] : null;
- }
- /**
- * Get method.
- *
- * @return string
- */
- public function method()
- {
- if (!isset($this->_data['method'])) {
- $this->parseHeadFirstLine();
- }
- return $this->_data['method'];
- }
- /**
- * Get http protocol version.
- *
- * @return string
- */
- public function protocolVersion()
- {
- if (!isset($this->_data['protocolVersion'])) {
- $this->parseProtocolVersion();
- }
- return $this->_data['protocolVersion'];
- }
- /**
- * Get host.
- *
- * @param bool $without_port
- * @return string
- */
- public function host($without_port = false)
- {
- $host = $this->header('host');
- if ($host && $without_port) {
- return preg_replace('/:\d{1,5}$/', '', $host);
- }
- return $host;
- }
- /**
- * Get uri.
- *
- * @return mixed
- */
- public function uri()
- {
- if (!isset($this->_data['uri'])) {
- $this->parseHeadFirstLine();
- }
- return $this->_data['uri'];
- }
- /**
- * Get path.
- *
- * @return mixed
- */
- public function path()
- {
- if (!isset($this->_data['path'])) {
- $this->_data['path'] = (string)\parse_url($this->uri(), PHP_URL_PATH);
- }
- return $this->_data['path'];
- }
- /**
- * Get query string.
- *
- * @return mixed
- */
- public function queryString()
- {
- 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 bool|\Workerman\Protocols\Http\Session
- */
- public function session()
- {
- if ($this->session === null) {
- $session_id = $this->sessionId();
- if ($session_id === false) {
- return false;
- }
- $this->session = new Session($session_id);
- }
- return $this->session;
- }
- /**
- * Get/Set session id.
- *
- * @param $session_id
- * @return string
- */
- public function sessionId($session_id = null)
- {
- if ($session_id) {
- unset($this->sid);
- }
- if (!isset($this->sid)) {
- $session_name = Session::$name;
- $sid = $session_id ? '' : $this->cookie($session_name);
- if ($sid === '' || $sid === null) {
- if ($this->connection === null) {
- Worker::safeEcho('Request->session() fail, header already send');
- return false;
- }
- $sid = $session_id ? $session_id : static::createSessionId();
- $cookie_params = Session::getCookieParams();
- $this->connection->__header['Set-Cookie'] = array($session_name . '=' . $sid
- . (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain'])
- . (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . $cookie_params['lifetime'])
- . (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path'])
- . (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite'])
- . (!$cookie_params['secure'] ? '' : '; Secure')
- . (!$cookie_params['httponly'] ? '' : '; HttpOnly'));
- }
- $this->sid = $sid;
- }
- return $this->sid;
- }
- /**
- * Get http raw head.
- *
- * @return string
- */
- public function rawHead()
- {
- 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()
- {
- return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4);
- }
- /**
- * Get raw buffer.
- *
- * @return string
- */
- public function rawBuffer()
- {
- return $this->_buffer;
- }
- /**
- * Enable or disable cache.
- *
- * @param mixed $value
- */
- public static function enableCache($value)
- {
- static::$_enableCache = (bool)$value;
- }
- /**
- * Parse first line of http header buffer.
- *
- * @return void
- */
- protected function parseHeadFirstLine()
- {
- $first_line = \strstr($this->_buffer, "\r\n", true);
- $tmp = \explode(' ', $first_line, 3);
- $this->_data['method'] = $tmp[0];
- $this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/';
- }
- /**
- * Parse protocol version.
- *
- * @return void
- */
- protected function parseProtocolVersion()
- {
- $first_line = \strstr($this->_buffer, "\r\n", true);
- $protoco_version = substr(\strstr($first_line, 'HTTP/'), 5);
- $this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0';
- }
- /**
- * Parse headers.
- *
- * @return void
- */
- protected function parseHeaders()
- {
- static $cache = [];
- $this->_data['headers'] = array();
- $raw_head = $this->rawHead();
- $end_line_position = \strpos($raw_head, "\r\n");
- if ($end_line_position === false) {
- return;
- }
- $head_buffer = \substr($raw_head, $end_line_position + 2);
- $cacheable = static::$_enableCache && !isset($head_buffer[2048]);
- if ($cacheable && isset($cache[$head_buffer])) {
- $this->_data['headers'] = $cache[$head_buffer];
- return;
- }
- $head_data = \explode("\r\n", $head_buffer);
- foreach ($head_data as $content) {
- if (false !== \strpos($content, ':')) {
- list($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[$head_buffer] = $this->_data['headers'];
- if (\count($cache) > 128) {
- unset($cache[key($cache)]);
- }
- }
- }
- /**
- * Parse head.
- *
- * @return void
- */
- protected function parseGet()
- {
- static $cache = [];
- $query_string = $this->queryString();
- $this->_data['get'] = array();
- if ($query_string === '') {
- return;
- }
- $cacheable = static::$_enableCache && !isset($query_string[1024]);
- if ($cacheable && isset($cache[$query_string])) {
- $this->_data['get'] = $cache[$query_string];
- return;
- }
- \parse_str($query_string, $this->_data['get']);
- if ($cacheable) {
- $cache[$query_string] = $this->_data['get'];
- if (\count($cache) > 256) {
- unset($cache[key($cache)]);
- }
- }
- }
- /**
- * Parse post.
- *
- * @return void
- */
- protected function parsePost()
- {
- static $cache = [];
- $this->_data['post'] = $this->_data['files'] = array();
- $content_type = $this->header('content-type', '');
- if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) {
- $http_post_boundary = '--' . $match[1];
- $this->parseUploadFiles($http_post_boundary);
- return;
- }
- $body_buffer = $this->rawBody();
- if ($body_buffer === '') {
- return;
- }
- $cacheable = static::$_enableCache && !isset($body_buffer[1024]);
- if ($cacheable && isset($cache[$body_buffer])) {
- $this->_data['post'] = $cache[$body_buffer];
- return;
- }
- if (\preg_match('/\bjson\b/i', $content_type)) {
- $this->_data['post'] = (array) json_decode($body_buffer, true);
- } else {
- \parse_str($body_buffer, $this->_data['post']);
- }
- if ($cacheable) {
- $cache[$body_buffer] = $this->_data['post'];
- if (\count($cache) > 256) {
- unset($cache[key($cache)]);
- }
- }
- }
- /**
- * Parse upload files.
- *
- * @param string $http_post_boundary
- * @return void
- */
- protected function parseUploadFiles($http_post_boundary)
- {
- $http_post_boundary = \trim($http_post_boundary, '"');
- $buffer = $this->_buffer;
- $post_encode_string = '';
- $files_encode_string = '';
- $files = [];
- $boday_position = strpos($buffer, "\r\n\r\n") + 4;
- $offset = $boday_position + strlen($http_post_boundary) + 2;
- $max_count = static::$maxFileUploads;
- while ($max_count-- > 0 && $offset) {
- $offset = $this->parseUploadFile($http_post_boundary, $offset, $post_encode_string, $files_encode_string, $files);
- }
- if ($post_encode_string) {
- parse_str($post_encode_string, $this->_data['post']);
- }
- if ($files_encode_string) {
- parse_str($files_encode_string, $this->_data['files']);
- \array_walk_recursive($this->_data['files'], function (&$value) use ($files) {
- $value = $files[$value];
- });
- }
- }
- /**
- * @param $boundary
- * @param $section_start_offset
- * @return int
- */
- protected function parseUploadFile($boundary, $section_start_offset, &$post_encode_string, &$files_encode_str, &$files)
- {
- $file = [];
- $boundary = "\r\n$boundary";
- if (\strlen($this->_buffer) < $section_start_offset) {
- return 0;
- }
- $section_end_offset = \strpos($this->_buffer, $boundary, $section_start_offset);
- if (!$section_end_offset) {
- return 0;
- }
- $content_lines_end_offset = \strpos($this->_buffer, "\r\n\r\n", $section_start_offset);
- if (!$content_lines_end_offset || $content_lines_end_offset + 4 > $section_end_offset) {
- return 0;
- }
- $content_lines_str = \substr($this->_buffer, $section_start_offset, $content_lines_end_offset - $section_start_offset);
- $content_lines = \explode("\r\n", trim($content_lines_str . "\r\n"));
- $boundary_value = \substr($this->_buffer, $content_lines_end_offset + 4, $section_end_offset - $content_lines_end_offset - 4);
- $upload_key = false;
- foreach ($content_lines as $content_line) {
- if (!\strpos($content_line, ': ')) {
- return 0;
- }
- list($key, $value) = \explode(': ', $content_line);
- switch (strtolower($key)) {
- case "content-disposition":
- // Is file data.
- if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $value, $match)) {
- $error = 0;
- $tmp_file = '';
- $file_name = $match[2];
- $size = \strlen($boundary_value);
- $tmp_upload_dir = HTTP::uploadTmpDir();
- if (!$tmp_upload_dir) {
- $error = UPLOAD_ERR_NO_TMP_DIR;
- } else if ($boundary_value === '' && $file_name === '') {
- $error = UPLOAD_ERR_NO_FILE;
- } else {
- $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.');
- if ($tmp_file === false || false === \file_put_contents($tmp_file, $boundary_value)) {
- $error = UPLOAD_ERR_CANT_WRITE;
- }
- }
- $upload_key = $match[1];
- // Parse upload files.
- $file = [
- 'name' => $file_name,
- 'tmp_name' => $tmp_file,
- 'size' => $size,
- 'error' => $error,
- 'type' => '',
- ];
- break;
- } // Is post field.
- else {
- // Parse $_POST.
- if (\preg_match('/name="(.*?)"$/', $value, $match)) {
- $k = $match[1];
- $post_encode_string .= \urlencode($k) . "=" . \urlencode($boundary_value) . '&';
- }
- return $section_end_offset + \strlen($boundary) + 2;
- }
- break;
- case "content-type":
- $file['type'] = \trim($value);
- break;
- }
- }
- if ($upload_key === false) {
- return 0;
- }
- $files_encode_str .= \urlencode($upload_key) . '=' . \count($files) . '&';
- $files[] = $file;
- return $section_end_offset + \strlen($boundary) + 2;
- }
- /**
- * Create session id.
- *
- * @return string
- */
- protected static function createSessionId()
- {
- return \bin2hex(\pack('d', \microtime(true)) . random_bytes(8));
- }
- /**
- * Setter.
- *
- * @param string $name
- * @param mixed $value
- * @return void
- */
- public function __set($name, $value)
- {
- $this->properties[$name] = $value;
- }
- /**
- * Getter.
- *
- * @param string $name
- * @return mixed|null
- */
- public function __get($name)
- {
- return isset($this->properties[$name]) ? $this->properties[$name] : null;
- }
- /**
- * Isset.
- *
- * @param string $name
- * @return bool
- */
- public function __isset($name)
- {
- return isset($this->properties[$name]);
- }
- /**
- * Unset.
- *
- * @param string $name
- * @return void
- */
- public function __unset($name)
- {
- unset($this->properties[$name]);
- }
- /**
- * __toString.
- */
- public function __toString()
- {
- return $this->_buffer;
- }
- /**
- * __wakeup.
- *
- * @return void
- */
- public function __wakeup()
- {
- $this->_isSafe = false;
- }
- /**
- * __destruct.
- *
- * @return void
- */
- public function __destruct()
- {
- if (isset($this->_data['files']) && $this->_isSafe) {
- \clearstatcache();
- \array_walk_recursive($this->_data['files'], function($value, $key){
- if ($key === 'tmp_name') {
- if (\is_file($value)) {
- \unlink($value);
- }
- }
- });
- }
- }
- }
|