Request.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  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. $end_line_position = \strpos($raw_head, "\r\n");
  364. if ($end_line_position === false) {
  365. return;
  366. }
  367. $head_buffer = \substr($raw_head, $end_line_position + 2);
  368. $cacheable = static::$_enableCache && !isset($head_buffer[2048]);
  369. if ($cacheable && isset(static::$_headerCache[$head_buffer])) {
  370. $this->_data['headers'] = static::$_headerCache[$head_buffer];
  371. return;
  372. }
  373. $head_data = \explode("\r\n", $head_buffer);
  374. foreach ($head_data as $content) {
  375. if (false !== \strpos($content, ':')) {
  376. list($key, $value) = \explode(':', $content, 2);
  377. $key = \strtolower($key);
  378. $value = \ltrim($value);
  379. } else {
  380. $key = \strtolower($content);
  381. $value = '';
  382. }
  383. if (isset($this->_data['headers'][$key])) {
  384. $this->_data['headers'][$key] = "{$this->_data['headers'][$key]},$value";
  385. } else {
  386. $this->_data['headers'][$key] = $value;
  387. }
  388. }
  389. if ($cacheable) {
  390. static::$_headerCache[$head_buffer] = $this->_data['headers'];
  391. if (\count(static::$_headerCache) > 128) {
  392. unset(static::$_headerCache[key(static::$_headerCache)]);
  393. }
  394. }
  395. }
  396. /**
  397. * Parse head.
  398. *
  399. * @return void
  400. */
  401. protected function parseGet()
  402. {
  403. $query_string = $this->queryString();
  404. $this->_data['get'] = array();
  405. if ($query_string === '') {
  406. return;
  407. }
  408. $cacheable = static::$_enableCache && !isset($query_string[1024]);
  409. if ($cacheable && isset(static::$_getCache[$query_string])) {
  410. $this->_data['get'] = static::$_getCache[$query_string];
  411. return;
  412. }
  413. \parse_str($query_string, $this->_data['get']);
  414. if ($cacheable) {
  415. static::$_getCache[$query_string] = $this->_data['get'];
  416. if (\count(static::$_getCache) > 256) {
  417. unset(static::$_getCache[key(static::$_getCache)]);
  418. }
  419. }
  420. }
  421. /**
  422. * Parse post.
  423. *
  424. * @return void
  425. */
  426. protected function parsePost()
  427. {
  428. $body_buffer = $this->rawBody();
  429. $this->_data['post'] = $this->_data['files'] = array();
  430. if ($body_buffer === '') {
  431. return;
  432. }
  433. $cacheable = static::$_enableCache && !isset($body_buffer[1024]);
  434. if ($cacheable && isset(static::$_postCache[$body_buffer])) {
  435. $this->_data['post'] = static::$_postCache[$body_buffer];
  436. return;
  437. }
  438. $content_type = $this->header('content-type', '');
  439. if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) {
  440. $http_post_boundary = '--' . $match[1];
  441. $this->parseUploadFiles($http_post_boundary);
  442. return;
  443. }
  444. if (\preg_match('/\bjson\b/i', $content_type)) {
  445. $this->_data['post'] = (array) json_decode($body_buffer, true);
  446. } else {
  447. \parse_str($body_buffer, $this->_data['post']);
  448. }
  449. if ($cacheable) {
  450. static::$_postCache[$body_buffer] = $this->_data['post'];
  451. if (\count(static::$_postCache) > 256) {
  452. unset(static::$_postCache[key(static::$_postCache)]);
  453. }
  454. }
  455. }
  456. /**
  457. * Parse upload files.
  458. *
  459. * @param $http_post_boundary
  460. * @return void
  461. */
  462. protected function parseUploadFiles($http_post_boundary)
  463. {
  464. $http_body = $this->rawBody();
  465. $http_body = \substr($http_body, 0, \strlen($http_body) - (\strlen($http_post_boundary) + 4));
  466. $boundary_data_array = \explode($http_post_boundary . "\r\n", $http_body);
  467. if ($boundary_data_array[0] === '') {
  468. unset($boundary_data_array[0]);
  469. }
  470. $key = -1;
  471. $files = array();
  472. foreach ($boundary_data_array as $boundary_data_buffer) {
  473. list($boundary_header_buffer, $boundary_value) = \explode("\r\n\r\n", $boundary_data_buffer, 2);
  474. // Remove \r\n from the end of buffer.
  475. $boundary_value = \substr($boundary_value, 0, -2);
  476. $key++;
  477. foreach (\explode("\r\n", $boundary_header_buffer) as $item) {
  478. list($header_key, $header_value) = \explode(": ", $item);
  479. $header_key = \strtolower($header_key);
  480. switch ($header_key) {
  481. case "content-disposition":
  482. // Is file data.
  483. if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $header_value, $match)) {
  484. $error = 0;
  485. $tmp_file = '';
  486. $size = \strlen($boundary_value);
  487. $tmp_upload_dir = HTTP::uploadTmpDir();
  488. if (!$tmp_upload_dir) {
  489. $error = UPLOAD_ERR_NO_TMP_DIR;
  490. } else {
  491. $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.');
  492. if ($tmp_file === false || false == \file_put_contents($tmp_file, $boundary_value)) {
  493. $error = UPLOAD_ERR_CANT_WRITE;
  494. }
  495. }
  496. // Parse upload files.
  497. $files[$key] = array(
  498. 'key' => $match[1],
  499. 'name' => $match[2],
  500. 'tmp_name' => $tmp_file,
  501. 'size' => $size,
  502. 'error' => $error
  503. );
  504. break;
  505. } // Is post field.
  506. else {
  507. // Parse $_POST.
  508. if (\preg_match('/name="(.*?)"$/', $header_value, $match)) {
  509. $this->_data['post'][$match[1]] = $boundary_value;
  510. }
  511. }
  512. break;
  513. case "content-type":
  514. // add file_type
  515. $files[$key]['type'] = \trim($header_value);
  516. break;
  517. }
  518. }
  519. }
  520. foreach ($files as $file) {
  521. $key = $file['key'];
  522. unset($file['key']);
  523. $this->_data['files'][$key] = $file;
  524. }
  525. }
  526. /**
  527. * Create session id.
  528. *
  529. * @return string
  530. */
  531. protected static function createSessionId()
  532. {
  533. return \bin2hex(\pack('d', \microtime(true)) . \pack('N', \mt_rand()));
  534. }
  535. /**
  536. * Setter.
  537. *
  538. * @param $name
  539. * @param $value
  540. * @return void
  541. */
  542. public function __set($name, $value)
  543. {
  544. $this->properties[$name] = $value;
  545. }
  546. /**
  547. * Getter.
  548. *
  549. * @param $name
  550. * @return mixed|null
  551. */
  552. public function __get($name)
  553. {
  554. return isset($this->properties[$name]) ? $this->properties[$name] : null;
  555. }
  556. /**
  557. * Isset.
  558. *
  559. * @param $name
  560. * @return bool
  561. */
  562. public function __isset($name)
  563. {
  564. return isset($this->properties[$name]);
  565. }
  566. /**
  567. * Unset.
  568. *
  569. * @param $name
  570. * @return void
  571. */
  572. public function __unset($name)
  573. {
  574. unset($this->properties[$name]);
  575. }
  576. /**
  577. * __toString.
  578. */
  579. public function __toString()
  580. {
  581. return $this->_buffer;
  582. }
  583. /**
  584. * __destruct.
  585. *
  586. * @return void
  587. */
  588. public function __destruct()
  589. {
  590. if (isset($this->_data['files'])) {
  591. \clearstatcache();
  592. foreach ($this->_data['files'] as $item) {
  593. if (\is_file($item['tmp_name'])) {
  594. \unlink($item['tmp_name']);
  595. }
  596. }
  597. }
  598. }
  599. }