Request.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  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 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'] = array();
  151. \parse_str(\str_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 ($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()
  270. {
  271. if (!isset($this->sid)) {
  272. $session_name = Http::sessionName();
  273. $sid = $this->cookie($session_name);
  274. if ($sid === '' || $sid === null) {
  275. if ($this->connection === null) {
  276. Worker::safeEcho('Request->session() fail, header already send');
  277. return false;
  278. }
  279. $sid = static::createSessionId();
  280. $cookie_params = \session_get_cookie_params();
  281. $this->connection->__header['Set-Cookie'] = array($session_name . '=' . $sid
  282. . (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain'])
  283. . (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . ($cookie_params['lifetime'] + \time()))
  284. . (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path'])
  285. . (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite'])
  286. . (!$cookie_params['secure'] ? '' : '; Secure')
  287. . (!$cookie_params['httponly'] ? '' : '; HttpOnly'));
  288. }
  289. $this->sid = $sid;
  290. }
  291. return $this->sid;
  292. }
  293. /**
  294. * Get http raw head.
  295. *
  296. * @return string
  297. */
  298. public function rawHead()
  299. {
  300. if (!isset($this->_data['head'])) {
  301. $this->_data['head'] = \strstr($this->_buffer, "\r\n\r\n", true);
  302. }
  303. return $this->_data['head'];
  304. }
  305. /**
  306. * Get http raw body.
  307. *
  308. * @return string
  309. */
  310. public function rawBody()
  311. {
  312. return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4);
  313. }
  314. /**
  315. * Get raw buffer.
  316. *
  317. * @return string
  318. */
  319. public function rawBuffer()
  320. {
  321. return $this->_buffer;
  322. }
  323. /**
  324. * Enable or disable cache.
  325. *
  326. * @param mixed $value
  327. */
  328. public static function enableCache($value)
  329. {
  330. static::$_enableCache = (bool)$value;
  331. }
  332. /**
  333. * Parse first line of http header buffer.
  334. *
  335. * @return void
  336. */
  337. protected function parseHeadFirstLine()
  338. {
  339. $first_line = \strstr($this->_buffer, "\r\n", true);
  340. $tmp = \explode(' ', $first_line, 3);
  341. $this->_data['method'] = $tmp[0];
  342. $this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/';
  343. }
  344. /**
  345. * Parse protocol version.
  346. *
  347. * @return void
  348. */
  349. protected function parseProtocolVersion()
  350. {
  351. $first_line = \strstr($this->_buffer, "\r\n", true);
  352. $protoco_version = substr(\strstr($first_line, 'HTTP/'), 5);
  353. $this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0';
  354. }
  355. /**
  356. * Parse headers.
  357. *
  358. * @return void
  359. */
  360. protected function parseHeaders()
  361. {
  362. $this->_data['headers'] = array();
  363. $raw_head = $this->rawHead();
  364. $end_line_position = \strpos($raw_head, "\r\n");
  365. if ($end_line_position === false) {
  366. return;
  367. }
  368. $head_buffer = \substr($raw_head, $end_line_position + 2);
  369. $cacheable = static::$_enableCache && !isset($head_buffer[2048]);
  370. if ($cacheable && isset(static::$_headerCache[$head_buffer])) {
  371. $this->_data['headers'] = static::$_headerCache[$head_buffer];
  372. return;
  373. }
  374. $head_data = \explode("\r\n", $head_buffer);
  375. foreach ($head_data as $content) {
  376. if (false !== \strpos($content, ':')) {
  377. list($key, $value) = \explode(':', $content, 2);
  378. $key = \strtolower($key);
  379. $value = \ltrim($value);
  380. } else {
  381. $key = \strtolower($content);
  382. $value = '';
  383. }
  384. if (isset($this->_data['headers'][$key])) {
  385. $this->_data['headers'][$key] = "{$this->_data['headers'][$key]},$value";
  386. } else {
  387. $this->_data['headers'][$key] = $value;
  388. }
  389. }
  390. if ($cacheable) {
  391. static::$_headerCache[$head_buffer] = $this->_data['headers'];
  392. if (\count(static::$_headerCache) > 128) {
  393. unset(static::$_headerCache[key(static::$_headerCache)]);
  394. }
  395. }
  396. }
  397. /**
  398. * Parse head.
  399. *
  400. * @return void
  401. */
  402. protected function parseGet()
  403. {
  404. $query_string = $this->queryString();
  405. $this->_data['get'] = array();
  406. if ($query_string === '') {
  407. return;
  408. }
  409. $cacheable = static::$_enableCache && !isset($query_string[1024]);
  410. if ($cacheable && isset(static::$_getCache[$query_string])) {
  411. $this->_data['get'] = static::$_getCache[$query_string];
  412. return;
  413. }
  414. \parse_str($query_string, $this->_data['get']);
  415. if ($cacheable) {
  416. static::$_getCache[$query_string] = $this->_data['get'];
  417. if (\count(static::$_getCache) > 256) {
  418. unset(static::$_getCache[key(static::$_getCache)]);
  419. }
  420. }
  421. }
  422. /**
  423. * Parse post.
  424. *
  425. * @return void
  426. */
  427. protected function parsePost()
  428. {
  429. $body_buffer = $this->rawBody();
  430. $this->_data['post'] = $this->_data['files'] = array();
  431. if ($body_buffer === '') {
  432. return;
  433. }
  434. $cacheable = static::$_enableCache && !isset($body_buffer[1024]);
  435. if ($cacheable && isset(static::$_postCache[$body_buffer])) {
  436. $this->_data['post'] = static::$_postCache[$body_buffer];
  437. return;
  438. }
  439. $content_type = $this->header('content-type', '');
  440. if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) {
  441. $http_post_boundary = '--' . $match[1];
  442. $this->parseUploadFiles($http_post_boundary);
  443. return;
  444. }
  445. if (\preg_match('/\bjson\b/i', $content_type)) {
  446. $this->_data['post'] = (array) json_decode($body_buffer, true);
  447. } else {
  448. \parse_str($body_buffer, $this->_data['post']);
  449. }
  450. if ($cacheable) {
  451. static::$_postCache[$body_buffer] = $this->_data['post'];
  452. if (\count(static::$_postCache) > 256) {
  453. unset(static::$_postCache[key(static::$_postCache)]);
  454. }
  455. }
  456. }
  457. /**
  458. * Parse upload files.
  459. *
  460. * @param string $http_post_boundary
  461. * @return void
  462. */
  463. protected function parseUploadFiles($http_post_boundary)
  464. {
  465. $http_post_boundary = \trim($http_post_boundary, '"');
  466. $http_body = $this->rawBody();
  467. $http_body = \substr($http_body, 0, \strlen($http_body) - (\strlen($http_post_boundary) + 4));
  468. $boundary_data_array = \explode($http_post_boundary . "\r\n", $http_body);
  469. if ($boundary_data_array[0] === '' || $boundary_data_array[0] === "\r\n") {
  470. unset($boundary_data_array[0]);
  471. }
  472. $key = -1;
  473. $files = array();
  474. $post_str = '';
  475. foreach ($boundary_data_array as $boundary_data_buffer) {
  476. list($boundary_header_buffer, $boundary_value) = \explode("\r\n\r\n", $boundary_data_buffer, 2);
  477. // Remove \r\n from the end of buffer.
  478. $boundary_value = \substr($boundary_value, 0, -2);
  479. $key++;
  480. foreach (\explode("\r\n", $boundary_header_buffer) as $item) {
  481. list($header_key, $header_value) = \explode(": ", $item);
  482. $header_key = \strtolower($header_key);
  483. switch ($header_key) {
  484. case "content-disposition":
  485. // Is file data.
  486. if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $header_value, $match)) {
  487. $error = 0;
  488. $tmp_file = '';
  489. $size = \strlen($boundary_value);
  490. $tmp_upload_dir = HTTP::uploadTmpDir();
  491. if (!$tmp_upload_dir) {
  492. $error = UPLOAD_ERR_NO_TMP_DIR;
  493. } else {
  494. $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.');
  495. if ($tmp_file === false || false == \file_put_contents($tmp_file, $boundary_value)) {
  496. $error = UPLOAD_ERR_CANT_WRITE;
  497. }
  498. }
  499. if (!isset($files[$key])) {
  500. $files[$key] = array();
  501. }
  502. // Parse upload files.
  503. $files[$key] += array(
  504. 'key' => $match[1],
  505. 'name' => $match[2],
  506. 'tmp_name' => $tmp_file,
  507. 'size' => $size,
  508. 'error' => $error
  509. );
  510. break;
  511. } // Is post field.
  512. else {
  513. // Parse $_POST.
  514. if (\preg_match('/name="(.*?)"$/', $header_value, $match)) {
  515. $key = $match[1];
  516. $post_str .= \urlencode($key)."=".\urlencode($boundary_value).'&';
  517. }
  518. }
  519. break;
  520. case "content-type":
  521. // add file_type
  522. if (!isset($files[$key])) {
  523. $files[$key] = array();
  524. }
  525. $files[$key]['type'] = \trim($header_value);
  526. break;
  527. }
  528. }
  529. }
  530. foreach ($files as $file) {
  531. $key = $file['key'];
  532. unset($file['key']);
  533. $str = \urlencode($key)."=1";
  534. $result = [];
  535. \parse_str($str, $result);
  536. \array_walk_recursive($result, function(&$value) use ($file) {
  537. $value = $file;
  538. });
  539. $this->_data['files'] = \array_merge($this->_data['files'], $result);
  540. }
  541. if ($post_str) {
  542. parse_str($post_str, $this->_data['post']);
  543. }
  544. }
  545. /**
  546. * Create session id.
  547. *
  548. * @return string
  549. */
  550. protected static function createSessionId()
  551. {
  552. return \bin2hex(\pack('d', \microtime(true)) . \pack('N', \mt_rand()));
  553. }
  554. /**
  555. * Setter.
  556. *
  557. * @param string $name
  558. * @param mixed $value
  559. * @return void
  560. */
  561. public function __set($name, $value)
  562. {
  563. $this->properties[$name] = $value;
  564. }
  565. /**
  566. * Getter.
  567. *
  568. * @param string $name
  569. * @return mixed|null
  570. */
  571. public function __get($name)
  572. {
  573. return isset($this->properties[$name]) ? $this->properties[$name] : null;
  574. }
  575. /**
  576. * Isset.
  577. *
  578. * @param string $name
  579. * @return bool
  580. */
  581. public function __isset($name)
  582. {
  583. return isset($this->properties[$name]);
  584. }
  585. /**
  586. * Unset.
  587. *
  588. * @param string $name
  589. * @return void
  590. */
  591. public function __unset($name)
  592. {
  593. unset($this->properties[$name]);
  594. }
  595. /**
  596. * __toString.
  597. */
  598. public function __toString()
  599. {
  600. return $this->_buffer;
  601. }
  602. /**
  603. * __destruct.
  604. *
  605. * @return void
  606. */
  607. public function __destruct()
  608. {
  609. if (isset($this->_data['files'])) {
  610. \clearstatcache();
  611. foreach ($this->_data['files'] as $items) {
  612. if (!\is_array(\current($items))) {
  613. $items = [$items];
  614. }
  615. foreach ($items as $item) {
  616. if (\is_file($item['tmp_name'])) {
  617. \unlink($item['tmp_name']);
  618. }
  619. }
  620. }
  621. }
  622. }
  623. }