Request.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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. namespace Workerman\Protocols\Http;
  15. use Workerman\Connection\TcpConnection;
  16. use Workerman\Protocols\Http\Session;
  17. use Workerman\Protocols\Http;
  18. use Workerman\Worker;
  19. /**
  20. * Class Request
  21. * @package Workerman\Protocols\Http
  22. */
  23. class Request
  24. {
  25. /**
  26. * Connection.
  27. *
  28. * @var TcpConnection
  29. */
  30. public $connection = null;
  31. /**
  32. * Session instance.
  33. *
  34. * @var Session
  35. */
  36. public $session = null;
  37. /**
  38. * Properties.
  39. *
  40. * @var array
  41. */
  42. public $properties = array();
  43. /**
  44. * Http buffer.
  45. *
  46. * @var string
  47. */
  48. protected $_buffer = null;
  49. /**
  50. * Request data.
  51. *
  52. * @var array
  53. */
  54. protected $_data = null;
  55. /**
  56. * Header cache.
  57. *
  58. * @var array
  59. */
  60. protected static $_headerCache = array();
  61. /**
  62. * Get cache.
  63. *
  64. * @var array
  65. */
  66. protected static $_getCache = array();
  67. /**
  68. * Post cache.
  69. *
  70. * @var array
  71. */
  72. protected static $_postCache = array();
  73. /**
  74. * Enable cache.
  75. *
  76. * @var bool
  77. */
  78. protected static $_enableCache = true;
  79. /**
  80. * Request constructor.
  81. *
  82. * @param $buffer
  83. */
  84. public function __construct($buffer)
  85. {
  86. $this->_buffer = $buffer;
  87. }
  88. /**
  89. * $_GET.
  90. *
  91. * @param null $name
  92. * @param null $default
  93. * @return mixed|null
  94. */
  95. public function get($name = null, $default = null)
  96. {
  97. if (!isset($this->_data['get'])) {
  98. $this->parseGet();
  99. }
  100. if (null === $name) {
  101. return $this->_data['get'];
  102. }
  103. return isset($this->_data['get'][$name]) ? $this->_data['get'][$name] : $default;
  104. }
  105. /**
  106. * $_POST.
  107. *
  108. * @param $name
  109. * @param null $default
  110. * @return mixed|null
  111. */
  112. public function post($name = null, $default = null)
  113. {
  114. if (!isset($this->_data['post'])) {
  115. $this->parsePost();
  116. }
  117. if (null === $name) {
  118. return $this->_data['post'];
  119. }
  120. return isset($this->_data['post'][$name]) ? $this->_data['post'][$name] : $default;
  121. }
  122. /**
  123. * Get header item by name.
  124. *
  125. * @param null $name
  126. * @param null $default
  127. * @return string|null
  128. */
  129. public function header($name = null, $default = null)
  130. {
  131. if (!isset($this->_data['headers'])) {
  132. $this->parseHeaders();
  133. }
  134. if (null === $name) {
  135. return $this->_data['headers'];
  136. }
  137. $name = \strtolower($name);
  138. return isset($this->_data['headers'][$name]) ? $this->_data['headers'][$name] : $default;
  139. }
  140. /**
  141. * Get cookie item by name.
  142. *
  143. * @param null $name
  144. * @param null $default
  145. * @return string|null
  146. */
  147. public function cookie($name = null, $default = null)
  148. {
  149. if (!isset($this->_data['cookie'])) {
  150. \parse_str(\str_replace('; ', '&', $this->header('cookie')), $this->_data['cookie']);
  151. }
  152. if ($name === null) {
  153. return $this->_data['cookie'];
  154. }
  155. return isset($this->_data['cookie'][$name]) ? $this->_data['cookie'][$name] : $default;
  156. }
  157. /**
  158. * Get upload files.
  159. *
  160. * @param null $name
  161. * @return array|null
  162. */
  163. public function file($name = null)
  164. {
  165. if (!isset($this->_data['files'])) {
  166. $this->parsePost();
  167. }
  168. if (null === $name) {
  169. return $this->_data['files'];
  170. }
  171. return isset($this->_data['files'][$name]) ? $this->_data['files'][$name] : null;
  172. }
  173. /**
  174. * Get method.
  175. *
  176. * @return string
  177. */
  178. public function method()
  179. {
  180. if (!isset($this->_data['method'])) {
  181. $this->parseHeadFirstLine();
  182. }
  183. return $this->_data['method'];
  184. }
  185. /**
  186. * Get http protocol version.
  187. *
  188. * @return string.
  189. */
  190. public function protocolVersion()
  191. {
  192. if (!isset($this->_data['protocolVersion'])) {
  193. $this->parseProtocolVersion();
  194. }
  195. return $this->_data['protocolVersion'];
  196. }
  197. /**
  198. * Get host.
  199. *
  200. * @param bool $without_port
  201. * @return string
  202. */
  203. public function host($without_port = false)
  204. {
  205. $host = $this->header('host');
  206. if ($without_port && $pos = \strpos($host, ':')) {
  207. return \substr($host, 0, $pos);
  208. }
  209. return $host;
  210. }
  211. /**
  212. * Get uri.
  213. *
  214. * @return mixed
  215. */
  216. public function uri()
  217. {
  218. if (!isset($this->_data['uri'])) {
  219. $this->parseHeadFirstLine();
  220. }
  221. return $this->_data['uri'];
  222. }
  223. /**
  224. * Get path.
  225. *
  226. * @return mixed
  227. */
  228. public function path()
  229. {
  230. if (!isset($this->_data['path'])) {
  231. $this->_data['path'] = \parse_url($this->uri(), PHP_URL_PATH);
  232. }
  233. return $this->_data['path'];
  234. }
  235. /**
  236. * Get query string.
  237. *
  238. * @return mixed
  239. */
  240. public function queryString()
  241. {
  242. if (!isset($this->_data['query_string'])) {
  243. $this->_data['query_string'] = \parse_url($this->uri(), PHP_URL_QUERY);
  244. }
  245. return $this->_data['query_string'];
  246. }
  247. /**
  248. * Get session.
  249. *
  250. * @return bool|\Workerman\Protocols\Http\Session
  251. */
  252. public function session()
  253. {
  254. if ($this->session === null) {
  255. $session_id = $this->sessionId();
  256. if ($session_id === false) {
  257. return false;
  258. }
  259. $this->session = new Session($session_id);
  260. }
  261. return $this->session;
  262. }
  263. /**
  264. * Get session id.
  265. *
  266. * @return bool|mixed
  267. */
  268. public function sessionId()
  269. {
  270. if (!isset($this->_data['sid'])) {
  271. $session_name = Http::sessionName();
  272. $sid = $this->cookie($session_name);
  273. if ($sid === '' || $sid === null) {
  274. if ($this->connection === null) {
  275. Worker::safeEcho('Request->session() fail, header already send');
  276. return false;
  277. }
  278. $sid = static::createSessionId();
  279. $cookie_params = \session_get_cookie_params();
  280. $this->connection->__header['Set-Cookie'] = array($session_name . '=' . $sid
  281. . (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain'])
  282. . (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . ($cookie_params['lifetime'] + \time()))
  283. . (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path'])
  284. . (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite'])
  285. . (!$cookie_params['secure'] ? '' : '; Secure')
  286. . (!$cookie_params['httponly'] ? '' : '; HttpOnly'));
  287. }
  288. $this->_data['sid'] = $sid;
  289. }
  290. return $this->_data['sid'];
  291. }
  292. /**
  293. * Get http raw head.
  294. *
  295. * @return string
  296. */
  297. public function rawHead()
  298. {
  299. if (!isset($this->_data['head'])) {
  300. $this->_data['head'] = \strstr($this->_buffer, "\r\n\r\n", true);
  301. }
  302. return $this->_data['head'];
  303. }
  304. /**
  305. * Get http raw body.
  306. *
  307. * @return string
  308. */
  309. public function rawBody()
  310. {
  311. return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4);
  312. }
  313. /**
  314. * Get raw buffer.
  315. *
  316. * @return string
  317. */
  318. public function rawBuffer()
  319. {
  320. return $this->_buffer;
  321. }
  322. /**
  323. * Enable or disable cache.
  324. *
  325. * @param $value
  326. */
  327. public static function enableCache($value)
  328. {
  329. static::$_enableCache = (bool)$value;
  330. }
  331. /**
  332. * Parse first line of http header buffer.
  333. *
  334. * @return void
  335. */
  336. protected function parseHeadFirstLine()
  337. {
  338. $first_line = \strstr($this->_buffer, "\r\n", true);
  339. $tmp = \explode(' ', $first_line, 3);
  340. $this->_data['method'] = $tmp[0];
  341. $this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/';
  342. }
  343. /**
  344. * Parse protocol version.
  345. *
  346. * @return void
  347. */
  348. protected function parseProtocolVersion()
  349. {
  350. $first_line = \strstr($this->_buffer, "\r\n", true);
  351. $protoco_version = substr(\strstr($first_line, 'HTTP/'), 5);
  352. $this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0';
  353. }
  354. /**
  355. * Parse headers.
  356. *
  357. * @return void
  358. */
  359. protected function parseHeaders()
  360. {
  361. $this->_data['headers'] = array();
  362. $raw_head = $this->rawHead();
  363. $head_buffer = \substr($raw_head, \strpos($raw_head, "\r\n") + 2);
  364. $cacheable = static::$_enableCache && !isset($head_buffer[2048]);
  365. if ($cacheable && isset(static::$_headerCache[$head_buffer])) {
  366. $this->_data['headers'] = static::$_headerCache[$head_buffer];
  367. return;
  368. }
  369. $head_data = \explode("\r\n", $head_buffer);
  370. foreach ($head_data as $content) {
  371. if (false !== \strpos($content, ':')) {
  372. list($key, $value) = \explode(':', $content, 2);
  373. $this->_data['headers'][\strtolower($key)] = \ltrim($value);
  374. } else {
  375. $this->_data['headers'][\strtolower($content)] = '';
  376. }
  377. }
  378. if ($cacheable) {
  379. static::$_headerCache[$head_buffer] = $this->_data['headers'];
  380. if (\count(static::$_headerCache) > 128) {
  381. unset(static::$_headerCache[key(static::$_headerCache)]);
  382. }
  383. }
  384. }
  385. /**
  386. * Parse head.
  387. *
  388. * @return void
  389. */
  390. protected function parseGet()
  391. {
  392. $query_string = $this->queryString();
  393. $this->_data['get'] = array();
  394. if ($query_string === '') {
  395. return;
  396. }
  397. $cacheable = static::$_enableCache && !isset($query_string[1024]);
  398. if ($cacheable && isset(static::$_getCache[$query_string])) {
  399. $this->_data['get'] = static::$_getCache[$query_string];
  400. return;
  401. }
  402. \parse_str($query_string, $this->_data['get']);
  403. if ($cacheable) {
  404. static::$_getCache[$query_string] = $this->_data['get'];
  405. if (\count(static::$_getCache) > 256) {
  406. unset(static::$_getCache[key(static::$_getCache)]);
  407. }
  408. }
  409. }
  410. /**
  411. * Parse post.
  412. *
  413. * @return void
  414. */
  415. protected function parsePost()
  416. {
  417. $body_buffer = $this->rawBody();
  418. $this->_data['post'] = $this->_data['files'] = array();
  419. if ($body_buffer === '') {
  420. return;
  421. }
  422. $cacheable = static::$_enableCache && !isset($body_buffer[1024]);
  423. if ($cacheable && isset(static::$_postCache[$body_buffer])) {
  424. $this->_data['post'] = static::$_postCache[$body_buffer];
  425. return;
  426. }
  427. $content_type = $this->header('content-type');
  428. if ($content_type !== null && \preg_match('/boundary="?(\S+)"?/', $content_type, $match)) {
  429. $http_post_boundary = '--' . $match[1];
  430. $this->parseUploadFiles($http_post_boundary);
  431. return;
  432. }
  433. \parse_str($body_buffer, $this->_data['post']);
  434. if ($cacheable) {
  435. static::$_postCache[$body_buffer] = $this->_data['post'];
  436. if (\count(static::$_postCache) > 256) {
  437. unset(static::$_postCache[key(static::$_postCache)]);
  438. }
  439. }
  440. }
  441. /**
  442. * Parse upload files.
  443. *
  444. * @param $http_post_boundary
  445. * @return void
  446. */
  447. protected function parseUploadFiles($http_post_boundary)
  448. {
  449. $http_body = $this->rawBody();
  450. $http_body = \substr($http_body, 0, \strlen($http_body) - (\strlen($http_post_boundary) + 4));
  451. $boundary_data_array = \explode($http_post_boundary . "\r\n", $http_body);
  452. if ($boundary_data_array[0] === '') {
  453. unset($boundary_data_array[0]);
  454. }
  455. $key = -1;
  456. $files = array();
  457. foreach ($boundary_data_array as $boundary_data_buffer) {
  458. list($boundary_header_buffer, $boundary_value) = \explode("\r\n\r\n", $boundary_data_buffer, 2);
  459. // Remove \r\n from the end of buffer.
  460. $boundary_value = \substr($boundary_value, 0, -2);
  461. $key++;
  462. foreach (\explode("\r\n", $boundary_header_buffer) as $item) {
  463. list($header_key, $header_value) = \explode(": ", $item);
  464. $header_key = \strtolower($header_key);
  465. switch ($header_key) {
  466. case "content-disposition":
  467. // Is file data.
  468. if (\preg_match('/name="(.*?)"; filename="(.*?)"$/i', $header_value, $match)) {
  469. $error = 0;
  470. $tmp_file = '';
  471. $size = \strlen($boundary_value);
  472. $tmp_upload_dir = HTTP::uploadTmpDir();
  473. if (!$tmp_upload_dir) {
  474. $error = UPLOAD_ERR_NO_TMP_DIR;
  475. } else {
  476. $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.');
  477. if ($tmp_file === false || false == \file_put_contents($tmp_file, $boundary_value)) {
  478. $error = UPLOAD_ERR_CANT_WRITE;
  479. }
  480. }
  481. // Parse upload files.
  482. $files[$key] = array(
  483. 'key' => $match[1],
  484. 'name' => $match[2],
  485. 'tmp_name' => $tmp_file,
  486. 'size' => $size,
  487. 'error' => $error
  488. );
  489. break;
  490. } // Is post field.
  491. else {
  492. // Parse $_POST.
  493. if (\preg_match('/name="(.*?)"$/', $header_value, $match)) {
  494. $this->_data['post'][$match[1]] = $boundary_value;
  495. }
  496. }
  497. break;
  498. case "content-type":
  499. // add file_type
  500. $files[$key]['type'] = \trim($header_value);
  501. break;
  502. }
  503. }
  504. }
  505. foreach ($files as $file) {
  506. $key = $file['key'];
  507. unset($file['key']);
  508. $this->_data['files'][$key] = $file;
  509. }
  510. }
  511. /**
  512. * Create session id.
  513. *
  514. * @return string
  515. */
  516. protected static function createSessionId()
  517. {
  518. return \bin2hex(\pack('d', \microtime(true)) . \pack('N', \mt_rand()));
  519. }
  520. /**
  521. * Setter.
  522. *
  523. * @param $name
  524. * @param $value
  525. * @return void
  526. */
  527. public function __set($name, $value)
  528. {
  529. $this->properties[$name] = $value;
  530. }
  531. /**
  532. * Getter.
  533. *
  534. * @param $name
  535. * @return mixed|null
  536. */
  537. public function __get($name)
  538. {
  539. return isset($this->properties[$name]) ? $this->properties[$name] : null;
  540. }
  541. /**
  542. * Isset.
  543. *
  544. * @param $name
  545. * @return bool
  546. */
  547. public function __isset($name)
  548. {
  549. return isset($this->properties[$name]);
  550. }
  551. /**
  552. * Unset.
  553. *
  554. * @param $name
  555. * @return void
  556. */
  557. public function __unset($name)
  558. {
  559. unset($this->properties[$name]);
  560. }
  561. /**
  562. * __destruct.
  563. *
  564. * @return void
  565. */
  566. public function __destruct()
  567. {
  568. if (isset($this->_data['files'])) {
  569. \clearstatcache();
  570. foreach ($this->_data['files'] as $item) {
  571. if (\is_file($item['tmp_name'])) {
  572. \unlink($item['tmp_name']);
  573. }
  574. }
  575. }
  576. }
  577. }