Request.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. <?php
  2. namespace Typecho;
  3. /**
  4. * 服务器请求处理类
  5. *
  6. * @package Request
  7. */
  8. class Request
  9. {
  10. /**
  11. * 单例句柄
  12. *
  13. * @access private
  14. * @var Request
  15. */
  16. private static $instance;
  17. /**
  18. * 沙箱参数
  19. *
  20. * @access private
  21. * @var Config|null
  22. */
  23. private $sandbox;
  24. /**
  25. * 用户参数
  26. *
  27. * @access private
  28. * @var Config|null
  29. */
  30. private $params;
  31. /**
  32. * 路径信息
  33. *
  34. * @access private
  35. * @var string
  36. */
  37. private $pathInfo = null;
  38. /**
  39. * requestUri
  40. *
  41. * @var string
  42. * @access private
  43. */
  44. private $requestUri = null;
  45. /**
  46. * requestRoot
  47. *
  48. * @var mixed
  49. * @access private
  50. */
  51. private $requestRoot = null;
  52. /**
  53. * 获取baseurl
  54. *
  55. * @var string
  56. * @access private
  57. */
  58. private $baseUrl = null;
  59. /**
  60. * 客户端ip地址
  61. *
  62. * @access private
  63. * @var string
  64. */
  65. private $ip = null;
  66. /**
  67. * 域名前缀
  68. *
  69. * @var string
  70. */
  71. private $urlPrefix = null;
  72. /**
  73. * 获取单例句柄
  74. *
  75. * @access public
  76. * @return Request
  77. */
  78. public static function getInstance(): Request
  79. {
  80. if (!isset(self::$instance)) {
  81. self::$instance = new self();
  82. }
  83. return self::$instance;
  84. }
  85. /**
  86. * 初始化变量
  87. *
  88. * @return $this
  89. */
  90. public function beginSandbox(Config $sandbox): Request
  91. {
  92. $this->sandbox = $sandbox;
  93. return $this;
  94. }
  95. /**
  96. * @return $this
  97. */
  98. public function endSandbox(): Request
  99. {
  100. $this->sandbox = null;
  101. return $this;
  102. }
  103. /**
  104. * @param Config $params
  105. * @return $this
  106. */
  107. public function proxy(Config $params): Request
  108. {
  109. $this->params = $params;
  110. return $this;
  111. }
  112. /**
  113. * 获取实际传递参数
  114. *
  115. * @param string $key 指定参数
  116. * @param mixed $default 默认参数 (default: NULL)
  117. * @param bool|null $exists detect exists
  118. * @return mixed
  119. */
  120. public function get(string $key, $default = null, ?bool &$exists = true)
  121. {
  122. $exists = true;
  123. $value = null;
  124. switch (true) {
  125. case isset($this->params) && isset($this->params[$key]):
  126. $value = $this->params[$key];
  127. break;
  128. case isset($this->sandbox):
  129. if (isset($this->sandbox[$key])) {
  130. $value = $this->sandbox[$key];
  131. } else {
  132. $exists = false;
  133. }
  134. break;
  135. case $key === '@json':
  136. $exists = false;
  137. if ($this->isJson()) {
  138. $body = file_get_contents('php://input');
  139. if (false !== $body) {
  140. $exists = true;
  141. $value = json_decode($body, true, 16);
  142. $default = $default ?? $value;
  143. }
  144. }
  145. break;
  146. case isset($_GET[$key]):
  147. $value = $_GET[$key];
  148. break;
  149. case isset($_POST[$key]):
  150. $value = $_POST[$key];
  151. break;
  152. default:
  153. $exists = false;
  154. break;
  155. }
  156. // reset params
  157. if (isset($this->params)) {
  158. $this->params = null;
  159. }
  160. if (isset($value)) {
  161. return is_array($default) == is_array($value) ? $value : $default;
  162. } else {
  163. return $default;
  164. }
  165. }
  166. /**
  167. * 获取实际传递参数(magic)
  168. *
  169. * @param string $key 指定参数
  170. * @return mixed
  171. */
  172. public function __get(string $key)
  173. {
  174. return $this->get($key);
  175. }
  176. /**
  177. * 判断参数是否存在
  178. *
  179. * @param string $key 指定参数
  180. * @return boolean
  181. */
  182. public function __isset(string $key)
  183. {
  184. $this->get($key, null, $exists);
  185. return $exists;
  186. }
  187. /**
  188. * 获取一个数组
  189. *
  190. * @param $key
  191. * @return array
  192. */
  193. public function getArray($key): array
  194. {
  195. $result = $this->get($key, [], $exists);
  196. if (!empty($result) || !$exists) {
  197. return $result;
  198. }
  199. return [$this->get($key)];
  200. }
  201. /**
  202. * 从参数列表指定的值中获取http传递参数
  203. *
  204. * @param mixed $params 指定的参数
  205. * @return array
  206. */
  207. public function from($params): array
  208. {
  209. $result = [];
  210. $args = is_array($params) ? $params : func_get_args();
  211. foreach ($args as $arg) {
  212. $result[$arg] = $this->get($arg);
  213. }
  214. return $result;
  215. }
  216. /**
  217. * getRequestRoot
  218. *
  219. * @return string
  220. */
  221. public function getRequestRoot(): string
  222. {
  223. if (null === $this->requestRoot) {
  224. $root = rtrim($this->getUrlPrefix() . $this->getBaseUrl(), '/') . '/';
  225. $pos = strrpos($root, '.php/');
  226. if ($pos) {
  227. $root = dirname(substr($root, 0, $pos));
  228. }
  229. $this->requestRoot = rtrim($root, '/');
  230. }
  231. return $this->requestRoot;
  232. }
  233. /**
  234. * 获取当前请求url
  235. *
  236. * @return string
  237. */
  238. public function getRequestUrl(): string
  239. {
  240. return $this->getUrlPrefix() . $this->getRequestUri();
  241. }
  242. /**
  243. * 根据当前uri构造指定参数的uri
  244. *
  245. * @param mixed $parameter 指定的参数
  246. * @return string
  247. */
  248. public function makeUriByRequest($parameter = null): string
  249. {
  250. /** 初始化地址 */
  251. $requestUri = $this->getRequestUrl();
  252. $parts = parse_url($requestUri);
  253. /** 初始化参数 */
  254. if (is_string($parameter)) {
  255. parse_str($parameter, $args);
  256. } elseif (is_array($parameter)) {
  257. $args = $parameter;
  258. } else {
  259. return $requestUri;
  260. }
  261. /** 构造query */
  262. if (isset($parts['query'])) {
  263. parse_str($parts['query'], $currentArgs);
  264. $args = array_merge($currentArgs, $args);
  265. }
  266. $parts['query'] = http_build_query($args);
  267. /** 返回地址 */
  268. return Common::buildUrl($parts);
  269. }
  270. /**
  271. * 获取当前pathinfo
  272. *
  273. * @return string
  274. */
  275. public function getPathInfo(): ?string
  276. {
  277. /** 缓存信息 */
  278. if (null !== $this->pathInfo) {
  279. return $this->pathInfo;
  280. }
  281. //参考Zend Framework对pathinfo的处理, 更好的兼容性
  282. $pathInfo = null;
  283. //处理requestUri
  284. $requestUri = $this->getRequestUri();
  285. $finalBaseUrl = $this->getBaseUrl();
  286. // Remove the query string from REQUEST_URI
  287. if ($pos = strpos($requestUri, '?')) {
  288. $requestUri = substr($requestUri, 0, $pos);
  289. }
  290. if (
  291. (null !== $finalBaseUrl)
  292. && (false === ($pathInfo = substr($requestUri, strlen($finalBaseUrl))))
  293. ) {
  294. // If substr() returns false then PATH_INFO is set to an empty string
  295. $pathInfo = '/';
  296. } elseif (null === $finalBaseUrl) {
  297. $pathInfo = $requestUri;
  298. }
  299. if (!empty($pathInfo)) {
  300. //针对iis的utf8编码做强制转换
  301. $pathInfo = defined('__TYPECHO_PATHINFO_ENCODING__') ?
  302. mb_convert_encoding($pathInfo, 'UTF-8', __TYPECHO_PATHINFO_ENCODING__) : $pathInfo;
  303. } else {
  304. $pathInfo = '/';
  305. }
  306. // fix issue 456
  307. return ($this->pathInfo = '/' . ltrim(urldecode($pathInfo), '/'));
  308. }
  309. /**
  310. * 获取环境变量
  311. *
  312. * @param string $name 获取环境变量名
  313. * @param string|null $default
  314. * @return string|null
  315. */
  316. public function getServer(string $name, string $default = null): ?string
  317. {
  318. return $_SERVER[$name] ?? $default;
  319. }
  320. /**
  321. * 获取ip地址
  322. *
  323. * @return string
  324. */
  325. public function getIp(): string
  326. {
  327. if (null === $this->ip) {
  328. $header = defined('__TYPECHO_IP_SOURCE__') ? __TYPECHO_IP_SOURCE__ : 'X-Forwarded-For';
  329. $ip = $this->getHeader($header, $this->getHeader('Client-Ip', $this->getServer('REMOTE_ADDR')));
  330. if (!empty($ip)) {
  331. [$ip] = array_map('trim', explode(',', $ip));
  332. $ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6);
  333. }
  334. if (!empty($ip)) {
  335. $this->ip = $ip;
  336. } else {
  337. $this->ip = 'unknown';
  338. }
  339. }
  340. return $this->ip;
  341. }
  342. /**
  343. * get header value
  344. *
  345. * @param string $key
  346. * @param string|null $default
  347. * @return string|null
  348. */
  349. public function getHeader(string $key, ?string $default = null): ?string
  350. {
  351. $key = 'HTTP_' . strtoupper(str_replace('-', '_', $key));
  352. return $this->getServer($key, $default);
  353. }
  354. /**
  355. * 获取客户端
  356. *
  357. * @return string
  358. */
  359. public function getAgent(): ?string
  360. {
  361. return $this->getHeader('User-Agent');
  362. }
  363. /**
  364. * 获取客户端
  365. *
  366. * @return string|null
  367. */
  368. public function getReferer(): ?string
  369. {
  370. return $this->getHeader('Referer');
  371. }
  372. /**
  373. * 判断是否为https
  374. *
  375. * @return bool
  376. */
  377. public function isSecure(): bool
  378. {
  379. return (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && !strcasecmp('https', $_SERVER['HTTP_X_FORWARDED_PROTO']))
  380. || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && !strcasecmp('quic', $_SERVER['HTTP_X_FORWARDED_PROTO']))
  381. || (!empty($_SERVER['HTTP_X_FORWARDED_PORT']) && 443 == $_SERVER['HTTP_X_FORWARDED_PORT'])
  382. || (!empty($_SERVER['HTTPS']) && 'off' != strtolower($_SERVER['HTTPS']))
  383. || (!empty($_SERVER['SERVER_PORT']) && 443 == $_SERVER['SERVER_PORT'])
  384. || (defined('__TYPECHO_SECURE__') && __TYPECHO_SECURE__);
  385. }
  386. /**
  387. * @return bool
  388. */
  389. public function isCli(): bool
  390. {
  391. return php_sapi_name() == 'cli';
  392. }
  393. /**
  394. * 判断是否为get方法
  395. *
  396. * @return boolean
  397. */
  398. public function isGet(): bool
  399. {
  400. return 'GET' == $this->getServer('REQUEST_METHOD');
  401. }
  402. /**
  403. * 判断是否为post方法
  404. *
  405. * @return boolean
  406. */
  407. public function isPost(): bool
  408. {
  409. return 'POST' == $this->getServer('REQUEST_METHOD');
  410. }
  411. /**
  412. * 判断是否为put方法
  413. *
  414. * @return boolean
  415. */
  416. public function isPut(): bool
  417. {
  418. return 'PUT' == $this->getServer('REQUEST_METHOD');
  419. }
  420. /**
  421. * 判断是否为ajax
  422. *
  423. * @return boolean
  424. */
  425. public function isAjax(): bool
  426. {
  427. return 'XMLHttpRequest' == $this->getHeader('X-Requested-With');
  428. }
  429. /**
  430. * 判断是否为Json请求
  431. *
  432. * @return bool
  433. */
  434. public function isJson(): bool
  435. {
  436. return !!preg_match("/^\s*application\/json(;|$)/i", $this->getHeader('Content-Type', ''));
  437. }
  438. /**
  439. * 判断输入是否满足要求
  440. *
  441. * @param mixed $query 条件
  442. * @return boolean
  443. */
  444. public function is($query): bool
  445. {
  446. $validated = false;
  447. /** 解析串 */
  448. if (is_string($query)) {
  449. parse_str($query, $params);
  450. } elseif (is_array($query)) {
  451. $params = $query;
  452. }
  453. /** 验证串 */
  454. if (!empty($params)) {
  455. $validated = true;
  456. foreach ($params as $key => $val) {
  457. $param = $this->get($key, null, $exists);
  458. $validated = empty($val) ? $exists : ($val == $param);
  459. if (!$validated) {
  460. break;
  461. }
  462. }
  463. }
  464. return $validated;
  465. }
  466. /**
  467. * 获取请求资源地址
  468. *
  469. * @return string|null
  470. */
  471. public function getRequestUri(): ?string
  472. {
  473. if (!empty($this->requestUri)) {
  474. return $this->requestUri;
  475. }
  476. //处理requestUri
  477. $requestUri = '/';
  478. if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // check this first so IIS will catch
  479. $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
  480. } elseif (
  481. // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem)
  482. isset($_SERVER['IIS_WasUrlRewritten'])
  483. && $_SERVER['IIS_WasUrlRewritten'] == '1'
  484. && isset($_SERVER['UNENCODED_URL'])
  485. && $_SERVER['UNENCODED_URL'] != ''
  486. ) {
  487. $requestUri = $_SERVER['UNENCODED_URL'];
  488. } elseif (isset($_SERVER['REQUEST_URI'])) {
  489. $requestUri = $_SERVER['REQUEST_URI'];
  490. $parts = @parse_url($requestUri);
  491. if (isset($_SERVER['HTTP_HOST']) && strstr($requestUri, $_SERVER['HTTP_HOST'])) {
  492. if (false !== $parts) {
  493. $requestUri = (empty($parts['path']) ? '' : $parts['path'])
  494. . ((empty($parts['query'])) ? '' : '?' . $parts['query']);
  495. }
  496. } elseif (!empty($_SERVER['QUERY_STRING']) && empty($parts['query'])) {
  497. // fix query missing
  498. $requestUri .= '?' . $_SERVER['QUERY_STRING'];
  499. }
  500. } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0, PHP as CGI
  501. $requestUri = $_SERVER['ORIG_PATH_INFO'];
  502. if (!empty($_SERVER['QUERY_STRING'])) {
  503. $requestUri .= '?' . $_SERVER['QUERY_STRING'];
  504. }
  505. }
  506. return $this->requestUri = $requestUri;
  507. }
  508. /**
  509. * 获取url前缀
  510. *
  511. * @return string|null
  512. */
  513. public function getUrlPrefix(): ?string
  514. {
  515. if (empty($this->urlPrefix)) {
  516. if (defined('__TYPECHO_URL_PREFIX__')) {
  517. $this->urlPrefix = __TYPECHO_URL_PREFIX__;
  518. } elseif (php_sapi_name() != 'cli') {
  519. $this->urlPrefix = ($this->isSecure() ? 'https' : 'http') . '://'
  520. . ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME']);
  521. }
  522. }
  523. return $this->urlPrefix;
  524. }
  525. /**
  526. * getBaseUrl
  527. *
  528. * @return string
  529. */
  530. private function getBaseUrl(): ?string
  531. {
  532. if (null !== $this->baseUrl) {
  533. return $this->baseUrl;
  534. }
  535. //处理baseUrl
  536. $filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : '';
  537. if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) {
  538. $baseUrl = $_SERVER['SCRIPT_NAME'];
  539. } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) {
  540. $baseUrl = $_SERVER['PHP_SELF'];
  541. } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) {
  542. $baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility
  543. } else {
  544. // Backtrack up the script_filename to find the portion matching
  545. // php_self
  546. $path = $_SERVER['PHP_SELF'] ?? '';
  547. $file = $_SERVER['SCRIPT_FILENAME'] ?? '';
  548. $segs = explode('/', trim($file, '/'));
  549. $segs = array_reverse($segs);
  550. $index = 0;
  551. $last = count($segs);
  552. $baseUrl = '';
  553. do {
  554. $seg = $segs[$index];
  555. $baseUrl = '/' . $seg . $baseUrl;
  556. ++$index;
  557. } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
  558. }
  559. // Does the baseUrl have anything in common with the request_uri?
  560. $finalBaseUrl = null;
  561. $requestUri = $this->getRequestUri();
  562. if (0 === strpos($requestUri, $baseUrl)) {
  563. // full $baseUrl matches
  564. $finalBaseUrl = $baseUrl;
  565. } elseif (0 === strpos($requestUri, dirname($baseUrl))) {
  566. // directory portion of $baseUrl matches
  567. $finalBaseUrl = rtrim(dirname($baseUrl), '/');
  568. } elseif (!strpos($requestUri, basename($baseUrl))) {
  569. // no match whatsoever; set it blank
  570. $finalBaseUrl = '';
  571. } elseif (
  572. (strlen($requestUri) >= strlen($baseUrl))
  573. && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))
  574. ) {
  575. // If using mod_rewrite or ISAPI_Rewrite strip the script filename
  576. // out of baseUrl. $pos !== 0 makes sure it is not matching a value
  577. // from PATH_INFO or QUERY_STRING
  578. $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
  579. }
  580. return ($this->baseUrl = (null === $finalBaseUrl) ? rtrim($baseUrl, '/') : $finalBaseUrl);
  581. }
  582. }