Request.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  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 = [];
  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 = [];
  61. /**
  62. * Get cache.
  63. *
  64. * @var array
  65. */
  66. protected static $_getCache = [];
  67. /**
  68. * Post cache.
  69. *
  70. * @var array
  71. */
  72. protected static $_postCache = [];
  73. /**
  74. * Enable cache.
  75. *
  76. * @var bool
  77. */
  78. protected static $_enableCache = true;
  79. /**
  80. * Request constructor.
  81. *
  82. * @param string $buffer
  83. */
  84. public function __construct($buffer)
  85. {
  86. $this->_buffer = $buffer;
  87. }
  88. /**
  89. * $_GET.
  90. *
  91. * @param string|null $name
  92. * @param mixed|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 string|null $name
  109. * @param mixed|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 string|null $name
  126. * @param mixed|null $default
  127. * @return array|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 string|null $name
  144. * @param mixed|null $default
  145. * @return array|string|null
  146. */
  147. public function cookie($name = null, $default = null)
  148. {
  149. if (!isset($this->_data['cookie'])) {
  150. $this->_data['cookie'] = [];
  151. \parse_str(\preg_replace('/; ?/', '&', $this->header('cookie', '')), $this->_data['cookie']);
  152. }
  153. if ($name === null) {
  154. return $this->_data['cookie'];
  155. }
  156. return isset($this->_data['cookie'][$name]) ? $this->_data['cookie'][$name] : $default;
  157. }
  158. /**
  159. * Get upload files.
  160. *
  161. * @param string|null $name
  162. * @return array|null
  163. */
  164. public function file($name = null)
  165. {
  166. if (!isset($this->_data['files'])) {
  167. $this->parsePost();
  168. }
  169. if (null === $name) {
  170. return $this->_data['files'];
  171. }
  172. return isset($this->_data['files'][$name]) ? $this->_data['files'][$name] : null;
  173. }
  174. /**
  175. * Get method.
  176. *
  177. * @return string
  178. */
  179. public function method()
  180. {
  181. if (!isset($this->_data['method'])) {
  182. $this->parseHeadFirstLine();
  183. }
  184. return $this->_data['method'];
  185. }
  186. /**
  187. * Get http protocol version.
  188. *
  189. * @return string
  190. */
  191. public function protocolVersion()
  192. {
  193. if (!isset($this->_data['protocolVersion'])) {
  194. $this->parseProtocolVersion();
  195. }
  196. return $this->_data['protocolVersion'];
  197. }
  198. /**
  199. * Get host.
  200. *
  201. * @param bool $without_port
  202. * @return string
  203. */
  204. public function host($without_port = false)
  205. {
  206. $host = $this->header('host');
  207. if ($host && $without_port && $pos = \strpos($host, ':')) {
  208. return \substr($host, 0, $pos);
  209. }
  210. return $host;
  211. }
  212. /**
  213. * Get uri.
  214. *
  215. * @return mixed
  216. */
  217. public function uri()
  218. {
  219. if (!isset($this->_data['uri'])) {
  220. $this->parseHeadFirstLine();
  221. }
  222. return $this->_data['uri'];
  223. }
  224. /**
  225. * Get path.
  226. *
  227. * @return mixed
  228. */
  229. public function path()
  230. {
  231. if (!isset($this->_data['path'])) {
  232. $this->_data['path'] = (string)\parse_url($this->uri(), PHP_URL_PATH);
  233. }
  234. return $this->_data['path'];
  235. }
  236. /**
  237. * Get query string.
  238. *
  239. * @return mixed
  240. */
  241. public function queryString()
  242. {
  243. if (!isset($this->_data['query_string'])) {
  244. $this->_data['query_string'] = (string)\parse_url($this->uri(), PHP_URL_QUERY);
  245. }
  246. return $this->_data['query_string'];
  247. }
  248. /**
  249. * Get session.
  250. *
  251. * @return bool|\Workerman\Protocols\Http\Session
  252. */
  253. public function session()
  254. {
  255. if ($this->session === null) {
  256. $session_id = $this->sessionId();
  257. if ($session_id === false) {
  258. return false;
  259. }
  260. $this->session = new Session($session_id);
  261. }
  262. return $this->session;
  263. }
  264. /**
  265. * Get session id.
  266. *
  267. * @return bool|mixed
  268. */
  269. public function sessionId($session_id = null)
  270. {
  271. if ($session_id !== null) {
  272. $this->sid = $session_id;
  273. }
  274. if (!isset($this->sid)) {
  275. $session_name = Http::sessionName();
  276. $sid = $this->cookie($session_name);
  277. if ($sid === '' || $sid === null) {
  278. if ($this->connection === null) {
  279. Worker::safeEcho('Request->session() fail, header already send');
  280. return false;
  281. }
  282. $sid = static::createSessionId();
  283. $cookie_params = \session_get_cookie_params();
  284. $this->setSidCookie($session_name, $sid, $cookie_params);
  285. }
  286. $this->sid = $sid;
  287. }
  288. return $this->sid;
  289. }
  290. /**
  291. * Session regenerate id
  292. * @param bool $delete_old_session
  293. * @return void
  294. */
  295. public function sessionRegenerateId($delete_old_session = false)
  296. {
  297. $session = $this->session();
  298. $session_data = $session->all();
  299. if ($delete_old_session) {
  300. $session->flush();
  301. }
  302. $new_sid = static::createSessionId();
  303. $session = new Session($new_sid);
  304. $session->put($session_data);
  305. $cookie_params = \session_get_cookie_params();
  306. $session_name = Http::sessionName();
  307. $this->setSidCookie($session_name, $new_sid, $cookie_params);
  308. }
  309. /**
  310. * Get http raw head.
  311. *
  312. * @return string
  313. */
  314. public function rawHead()
  315. {
  316. if (!isset($this->_data['head'])) {
  317. $this->_data['head'] = \strstr($this->_buffer, "\r\n\r\n", true);
  318. }
  319. return $this->_data['head'];
  320. }
  321. /**
  322. * Get http raw body.
  323. *
  324. * @return string
  325. */
  326. public function rawBody()
  327. {
  328. return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4);
  329. }
  330. /**
  331. * Get raw buffer.
  332. *
  333. * @return string
  334. */
  335. public function rawBuffer()
  336. {
  337. return $this->_buffer;
  338. }
  339. /**
  340. * Enable or disable cache.
  341. *
  342. * @param mixed $value
  343. */
  344. public static function enableCache($value)
  345. {
  346. static::$_enableCache = (bool)$value;
  347. }
  348. /**
  349. * Parse first line of http header buffer.
  350. *
  351. * @return void
  352. */
  353. protected function parseHeadFirstLine()
  354. {
  355. $first_line = \strstr($this->_buffer, "\r\n", true);
  356. $tmp = \explode(' ', $first_line, 3);
  357. $this->_data['method'] = $tmp[0];
  358. $this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/';
  359. }
  360. /**
  361. * Parse protocol version.
  362. *
  363. * @return void
  364. */
  365. protected function parseProtocolVersion()
  366. {
  367. $first_line = \strstr($this->_buffer, "\r\n", true);
  368. $protoco_version = substr(\strstr($first_line, 'HTTP/'), 5);
  369. $this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0';
  370. }
  371. /**
  372. * Parse headers.
  373. *
  374. * @return void
  375. */
  376. protected function parseHeaders()
  377. {
  378. $this->_data['headers'] = [];
  379. $raw_head = $this->rawHead();
  380. $end_line_position = \strpos($raw_head, "\r\n");
  381. if ($end_line_position === false) {
  382. return;
  383. }
  384. $head_buffer = \substr($raw_head, $end_line_position + 2);
  385. $cacheable = static::$_enableCache && !isset($head_buffer[2048]);
  386. if ($cacheable && isset(static::$_headerCache[$head_buffer])) {
  387. $this->_data['headers'] = static::$_headerCache[$head_buffer];
  388. return;
  389. }
  390. $head_data = \explode("\r\n", $head_buffer);
  391. foreach ($head_data as $content) {
  392. if (false !== \strpos($content, ':')) {
  393. list($key, $value) = \explode(':', $content, 2);
  394. $key = \strtolower($key);
  395. $value = \ltrim($value);
  396. } else {
  397. $key = \strtolower($content);
  398. $value = '';
  399. }
  400. if (isset($this->_data['headers'][$key])) {
  401. $this->_data['headers'][$key] = "{$this->_data['headers'][$key]},$value";
  402. } else {
  403. $this->_data['headers'][$key] = $value;
  404. }
  405. }
  406. if ($cacheable) {
  407. static::$_headerCache[$head_buffer] = $this->_data['headers'];
  408. if (\count(static::$_headerCache) > 128) {
  409. unset(static::$_headerCache[key(static::$_headerCache)]);
  410. }
  411. }
  412. }
  413. /**
  414. * Parse head.
  415. *
  416. * @return void
  417. */
  418. protected function parseGet()
  419. {
  420. $query_string = $this->queryString();
  421. $this->_data['get'] = [];
  422. if ($query_string === '') {
  423. return;
  424. }
  425. $cacheable = static::$_enableCache && !isset($query_string[1024]);
  426. if ($cacheable && isset(static::$_getCache[$query_string])) {
  427. $this->_data['get'] = static::$_getCache[$query_string];
  428. return;
  429. }
  430. \parse_str($query_string, $this->_data['get']);
  431. if ($cacheable) {
  432. static::$_getCache[$query_string] = $this->_data['get'];
  433. if (\count(static::$_getCache) > 256) {
  434. unset(static::$_getCache[key(static::$_getCache)]);
  435. }
  436. }
  437. }
  438. /**
  439. * Parse post.
  440. *
  441. * @return void
  442. */
  443. protected function parsePost()
  444. {
  445. $body_buffer = $this->rawBody();
  446. $this->_data['post'] = $this->_data['files'] = [];
  447. if ($body_buffer === '') {
  448. return;
  449. }
  450. $cacheable = static::$_enableCache && !isset($body_buffer[1024]);
  451. if ($cacheable && isset(static::$_postCache[$body_buffer])) {
  452. $this->_data['post'] = static::$_postCache[$body_buffer];
  453. return;
  454. }
  455. $content_type = $this->header('content-type', '');
  456. if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) {
  457. $http_post_boundary = '--' . $match[1];
  458. $this->parseUploadFiles($http_post_boundary);
  459. return;
  460. }
  461. if (\preg_match('/\bjson\b/i', $content_type)) {
  462. $this->_data['post'] = (array)\json_decode($body_buffer, true);
  463. } else {
  464. \parse_str($body_buffer, $this->_data['post']);
  465. }
  466. if ($cacheable) {
  467. static::$_postCache[$body_buffer] = $this->_data['post'];
  468. if (\count(static::$_postCache) > 256) {
  469. unset(static::$_postCache[key(static::$_postCache)]);
  470. }
  471. }
  472. }
  473. /**
  474. * Parse upload files.
  475. *
  476. * @param string $http_post_boundary
  477. * @return void
  478. */
  479. protected function parseUploadFiles($http_post_boundary)
  480. {
  481. $http_post_boundary = \trim($http_post_boundary, '"');
  482. $http_body = $this->rawBody();
  483. $http_body = \substr($http_body, 0, \strlen($http_body) - (\strlen($http_post_boundary) + 4));
  484. $boundary_data_array = \explode($http_post_boundary . "\r\n", $http_body);
  485. if ($boundary_data_array[0] === '' || $boundary_data_array[0] === "\r\n") {
  486. unset($boundary_data_array[0]);
  487. }
  488. $key = -1;
  489. $files = [];
  490. $post_str = '';
  491. foreach ($boundary_data_array as $boundary_data_buffer) {
  492. list($boundary_header_buffer, $boundary_value) = \explode("\r\n\r\n", $boundary_data_buffer, 2);
  493. // Remove \r\n from the end of buffer.
  494. $boundary_value = \substr($boundary_value, 0, -2);
  495. $key++;
  496. foreach (\explode("\r\n", $boundary_header_buffer) as $item) {
  497. list($header_key, $header_value) = \explode(": ", $item);
  498. $header_key = \strtolower($header_key);
  499. switch ($header_key) {
  500. case "content-disposition":
  501. // Is file data.
  502. if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $header_value, $match)) {
  503. $error = 0;
  504. $tmp_file = '';
  505. $size = \strlen($boundary_value);
  506. $tmp_upload_dir = HTTP::uploadTmpDir();
  507. if (!$tmp_upload_dir) {
  508. $error = UPLOAD_ERR_NO_TMP_DIR;
  509. } else if ($boundary_value === '') {
  510. $error = UPLOAD_ERR_NO_FILE;
  511. } else {
  512. $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.');
  513. if ($tmp_file === false || false == \file_put_contents($tmp_file, $boundary_value)) {
  514. $error = UPLOAD_ERR_CANT_WRITE;
  515. }
  516. }
  517. if (!isset($files[$key])) {
  518. $files[$key] = [];
  519. }
  520. // Parse upload files.
  521. $files[$key] += [
  522. 'key' => $match[1],
  523. 'name' => $match[2],
  524. 'tmp_name' => $tmp_file,
  525. 'size' => $size,
  526. 'error' => $error,
  527. 'type' => null,
  528. ];
  529. break;
  530. } // Is post field.
  531. else {
  532. // Parse $_POST.
  533. if (\preg_match('/name="(.*?)"$/', $header_value, $match)) {
  534. $key = $match[1];
  535. $post_str .= \urlencode($key) . "=" . \urlencode($boundary_value) . '&';
  536. }
  537. }
  538. break;
  539. case "content-type":
  540. // add file_type
  541. if (!isset($files[$key])) {
  542. $files[$key] = [];
  543. }
  544. $files[$key]['type'] = \trim($header_value);
  545. break;
  546. }
  547. }
  548. }
  549. foreach ($files as $file) {
  550. $key = $file['key'];
  551. unset($file['key']);
  552. $str = \urlencode($key) . "=1";
  553. $result = [];
  554. \parse_str($str, $result);
  555. \array_walk_recursive($result, function (&$value) use ($file) {
  556. $value = $file;
  557. });
  558. $this->_data['files'] = \array_merge_recursive($this->_data['files'], $result);
  559. }
  560. if ($post_str) {
  561. parse_str($post_str, $this->_data['post']);
  562. }
  563. }
  564. /**
  565. * Create session id.
  566. *
  567. * @return string
  568. */
  569. public static function createSessionId()
  570. {
  571. return \bin2hex(\pack('d', \microtime(true)) . \pack('N', \mt_rand()));
  572. }
  573. /**
  574. * Setter.
  575. *
  576. * @param string $name
  577. * @param mixed $value
  578. * @return void
  579. */
  580. public function __set($name, $value)
  581. {
  582. $this->properties[$name] = $value;
  583. }
  584. /**
  585. * Getter.
  586. *
  587. * @param string $name
  588. * @return mixed|null
  589. */
  590. public function __get($name)
  591. {
  592. return isset($this->properties[$name]) ? $this->properties[$name] : null;
  593. }
  594. /**
  595. * Isset.
  596. *
  597. * @param string $name
  598. * @return bool
  599. */
  600. public function __isset($name)
  601. {
  602. return isset($this->properties[$name]);
  603. }
  604. /**
  605. * Unset.
  606. *
  607. * @param string $name
  608. * @return void
  609. */
  610. public function __unset($name)
  611. {
  612. unset($this->properties[$name]);
  613. }
  614. /**
  615. * __toString.
  616. */
  617. public function __toString()
  618. {
  619. return $this->_buffer;
  620. }
  621. /**
  622. * __destruct.
  623. *
  624. * @return void
  625. */
  626. public function __destruct()
  627. {
  628. if (isset($this->_data['files'])) {
  629. \clearstatcache();
  630. \array_walk_recursive($this->_data['files'], function ($value, $key) {
  631. if ($key === 'tmp_name') {
  632. if (\is_file($value)) {
  633. \unlink($value);
  634. }
  635. }
  636. });
  637. }
  638. }
  639. /**
  640. * @param string $session_name
  641. * @param string $sid
  642. * @param array $cookie_params
  643. * @return void
  644. */
  645. protected function setSidCookie(string $session_name, string $sid, array $cookie_params)
  646. {
  647. $this->connection->__header['Set-Cookie'] = [$session_name . '=' . $sid
  648. . (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain'])
  649. . (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . $cookie_params['lifetime'])
  650. . (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path'])
  651. . (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite'])
  652. . (!$cookie_params['secure'] ? '' : '; Secure')
  653. . (!$cookie_params['httponly'] ? '' : '; HttpOnly')];
  654. }
  655. }