Request.php 19 KB

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