Request.php 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Network;
  16. use ArrayAccess;
  17. use BadMethodCallException;
  18. use Cake\Core\Configure;
  19. use Cake\Network\Exception\MethodNotAllowedException;
  20. use Cake\Utility\Hash;
  21. /**
  22. * A class that helps wrap Request information and particulars about a single request.
  23. * Provides methods commonly used to introspect on the request headers and request body.
  24. *
  25. * Has both an Array and Object interface. You can access framework parameters using indexes:
  26. *
  27. * `$request['controller']` or `$request->controller`.
  28. */
  29. class Request implements ArrayAccess
  30. {
  31. /**
  32. * Array of parameters parsed from the URL.
  33. *
  34. * @var array
  35. */
  36. public $params = [
  37. 'plugin' => null,
  38. 'controller' => null,
  39. 'action' => null,
  40. '_ext' => null,
  41. 'pass' => []
  42. ];
  43. /**
  44. * Array of POST data. Will contain form data as well as uploaded files.
  45. * In PUT/PATCH/DELETE requests this property will contain the form-urlencoded
  46. * data.
  47. *
  48. * @var array
  49. */
  50. public $data = [];
  51. /**
  52. * Array of querystring arguments
  53. *
  54. * @var array
  55. */
  56. public $query = [];
  57. /**
  58. * Array of cookie data.
  59. *
  60. * @var array
  61. */
  62. public $cookies = [];
  63. /**
  64. * Array of environment data.
  65. *
  66. * @var array
  67. */
  68. protected $_environment = [];
  69. /**
  70. * The URL string used for the request.
  71. *
  72. * @var string
  73. */
  74. public $url;
  75. /**
  76. * Base URL path.
  77. *
  78. * @var string
  79. */
  80. public $base;
  81. /**
  82. * webroot path segment for the request.
  83. *
  84. * @var string
  85. */
  86. public $webroot = '/';
  87. /**
  88. * The full address to the current request
  89. *
  90. * @var string
  91. */
  92. public $here;
  93. /**
  94. * Whether or not to trust HTTP_X headers set by most load balancers.
  95. * Only set to true if your application runs behind load balancers/proxies
  96. * that you control.
  97. *
  98. * @var bool
  99. */
  100. public $trustProxy = false;
  101. /**
  102. * The built in detectors used with `is()` can be modified with `addDetector()`.
  103. *
  104. * There are several ways to specify a detector, see Cake\Network\Request::addDetector() for the
  105. * various formats and ways to define detectors.
  106. *
  107. * @var array
  108. */
  109. protected static $_detectors = [
  110. 'get' => ['env' => 'REQUEST_METHOD', 'value' => 'GET'],
  111. 'post' => ['env' => 'REQUEST_METHOD', 'value' => 'POST'],
  112. 'put' => ['env' => 'REQUEST_METHOD', 'value' => 'PUT'],
  113. 'patch' => ['env' => 'REQUEST_METHOD', 'value' => 'PATCH'],
  114. 'delete' => ['env' => 'REQUEST_METHOD', 'value' => 'DELETE'],
  115. 'head' => ['env' => 'REQUEST_METHOD', 'value' => 'HEAD'],
  116. 'options' => ['env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'],
  117. 'ssl' => ['env' => 'HTTPS', 'options' => [1, 'on']],
  118. 'ajax' => ['env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'],
  119. 'flash' => ['env' => 'HTTP_USER_AGENT', 'pattern' => '/^(Shockwave|Adobe) Flash/'],
  120. 'requested' => ['param' => 'requested', 'value' => 1],
  121. 'json' => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'],
  122. 'xml' => ['accept' => ['application/xml', 'text/xml'], 'param' => '_ext', 'value' => 'xml'],
  123. ];
  124. /**
  125. * Instance cache for results of is(something) calls
  126. *
  127. * @var array
  128. */
  129. protected $_detectorCache = [];
  130. /**
  131. * Copy of php://input. Since this stream can only be read once in most SAPI's
  132. * keep a copy of it so users don't need to know about that detail.
  133. *
  134. * @var string
  135. */
  136. protected $_input = '';
  137. /**
  138. * Instance of a Session object relative to this request
  139. *
  140. * @var \Cake\Network\Session
  141. */
  142. protected $_session;
  143. /**
  144. * Wrapper method to create a new request from PHP superglobals.
  145. *
  146. * Uses the $_GET, $_POST, $_FILES, $_COOKIE, $_SERVER, $_ENV and php://input data to construct
  147. * the request.
  148. *
  149. * @return \Cake\Network\Request
  150. */
  151. public static function createFromGlobals()
  152. {
  153. list($base, $webroot) = static::_base();
  154. $sessionConfig = (array)Configure::read('Session') + [
  155. 'defaults' => 'php',
  156. 'cookiePath' => $webroot
  157. ];
  158. $config = [
  159. 'query' => $_GET,
  160. 'post' => $_POST,
  161. 'files' => $_FILES,
  162. 'cookies' => $_COOKIE,
  163. 'environment' => $_SERVER + $_ENV,
  164. 'base' => $base,
  165. 'webroot' => $webroot,
  166. 'session' => Session::create($sessionConfig)
  167. ];
  168. $config['url'] = static::_url($config);
  169. return new static($config);
  170. }
  171. /**
  172. * Create a new request object.
  173. *
  174. * You can supply the data as either an array or as a string. If you use
  175. * a string you can only supply the URL for the request. Using an array will
  176. * let you provide the following keys:
  177. *
  178. * - `post` POST data or non query string data
  179. * - `query` Additional data from the query string.
  180. * - `files` Uploaded file data formatted like $_FILES.
  181. * - `cookies` Cookies for this request.
  182. * - `environment` $_SERVER and $_ENV data.
  183. * - `url` The URL without the base path for the request.
  184. * - `base` The base URL for the request.
  185. * - `webroot` The webroot directory for the request.
  186. * - `input` The data that would come from php://input this is useful for simulating
  187. * - `session` An instance of a Session object
  188. * requests with put, patch or delete data.
  189. *
  190. * @param string|array $config An array of request data to create a request with.
  191. */
  192. public function __construct($config = [])
  193. {
  194. if (is_string($config)) {
  195. $config = ['url' => $config];
  196. }
  197. $config += [
  198. 'params' => $this->params,
  199. 'query' => [],
  200. 'post' => [],
  201. 'files' => [],
  202. 'cookies' => [],
  203. 'environment' => [],
  204. 'url' => '',
  205. 'base' => '',
  206. 'webroot' => '',
  207. 'input' => null,
  208. ];
  209. $this->_setConfig($config);
  210. }
  211. /**
  212. * Process the config/settings data into properties.
  213. *
  214. * @param array $config The config data to use.
  215. * @return void
  216. */
  217. protected function _setConfig($config)
  218. {
  219. if (!empty($config['url']) && $config['url'][0] === '/') {
  220. $config['url'] = substr($config['url'], 1);
  221. }
  222. if (empty($config['session'])) {
  223. $config['session'] = new Session([
  224. 'cookiePath' => $config['base']
  225. ]);
  226. }
  227. $this->url = $config['url'];
  228. $this->base = $config['base'];
  229. $this->cookies = $config['cookies'];
  230. $this->here = $this->base . '/' . $this->url;
  231. $this->webroot = $config['webroot'];
  232. $this->_environment = $config['environment'];
  233. if (isset($config['input'])) {
  234. $this->_input = $config['input'];
  235. }
  236. $config['post'] = $this->_processPost($config['post']);
  237. $this->data = $this->_processFiles($config['post'], $config['files']);
  238. $this->query = $this->_processGet($config['query']);
  239. $this->params = $config['params'];
  240. $this->_session = $config['session'];
  241. }
  242. /**
  243. * Sets the REQUEST_METHOD environment variable based on the simulated _method
  244. * HTTP override value. The 'ORIGINAL_REQUEST_METHOD' is also preserved, if you
  245. * want the read the non-simulated HTTP method the client used.
  246. *
  247. * @param array $data Array of post data.
  248. * @return array
  249. */
  250. protected function _processPost($data)
  251. {
  252. $method = $this->env('REQUEST_METHOD');
  253. $override = false;
  254. if (in_array($method, ['PUT', 'DELETE', 'PATCH']) &&
  255. strpos($this->contentType(), 'application/x-www-form-urlencoded') === 0
  256. ) {
  257. $data = $this->input();
  258. parse_str($data, $data);
  259. }
  260. if ($this->env('HTTP_X_HTTP_METHOD_OVERRIDE')) {
  261. $data['_method'] = $this->env('HTTP_X_HTTP_METHOD_OVERRIDE');
  262. $override = true;
  263. }
  264. $this->_environment['ORIGINAL_REQUEST_METHOD'] = $method;
  265. if (isset($data['_method'])) {
  266. $this->_environment['REQUEST_METHOD'] = $data['_method'];
  267. unset($data['_method']);
  268. $override = true;
  269. }
  270. if ($override && !in_array($this->_environment['REQUEST_METHOD'], ['PUT', 'POST', 'DELETE', 'PATCH'])) {
  271. $data = [];
  272. }
  273. return $data;
  274. }
  275. /**
  276. * Process the GET parameters and move things into the object.
  277. *
  278. * @param array $query The array to which the parsed keys/values are being added.
  279. * @return array An array containing the parsed querystring keys/values.
  280. */
  281. protected function _processGet($query)
  282. {
  283. $unsetUrl = '/' . str_replace(['.', ' '], '_', urldecode($this->url));
  284. unset($query[$unsetUrl]);
  285. unset($query[$this->base . $unsetUrl]);
  286. if (strpos($this->url, '?') !== false) {
  287. list(, $querystr) = explode('?', $this->url);
  288. parse_str($querystr, $queryArgs);
  289. $query += $queryArgs;
  290. }
  291. return $query;
  292. }
  293. /**
  294. * Get the request uri. Looks in PATH_INFO first, as this is the exact value we need prepared
  295. * by PHP. Following that, REQUEST_URI, PHP_SELF, HTTP_X_REWRITE_URL and argv are checked in that order.
  296. * Each of these server variables have the base path, and query strings stripped off
  297. *
  298. * @param array $config Configuration to set.
  299. * @return string URI The CakePHP request path that is being accessed.
  300. */
  301. protected static function _url($config)
  302. {
  303. if (!empty($_SERVER['PATH_INFO'])) {
  304. return $_SERVER['PATH_INFO'];
  305. }
  306. if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) {
  307. $uri = $_SERVER['REQUEST_URI'];
  308. } elseif (isset($_SERVER['REQUEST_URI'])) {
  309. $uri = $_SERVER['REQUEST_URI'];
  310. $fullBaseUrl = Configure::read('App.fullBaseUrl');
  311. if (strpos($uri, $fullBaseUrl) === 0) {
  312. $uri = substr($_SERVER['REQUEST_URI'], strlen($fullBaseUrl));
  313. }
  314. } elseif (isset($_SERVER['PHP_SELF'], $_SERVER['SCRIPT_NAME'])) {
  315. $uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']);
  316. } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
  317. $uri = $_SERVER['HTTP_X_REWRITE_URL'];
  318. } elseif ($var = env('argv')) {
  319. $uri = $var[0];
  320. }
  321. $base = $config['base'];
  322. if (strlen($base) > 0 && strpos($uri, $base) === 0) {
  323. $uri = substr($uri, strlen($base));
  324. }
  325. if (strpos($uri, '?') !== false) {
  326. list($uri) = explode('?', $uri, 2);
  327. }
  328. if (empty($uri) || $uri === '/' || $uri === '//' || $uri === '/index.php') {
  329. $uri = '/';
  330. }
  331. $endsWithIndex = '/webroot/index.php';
  332. $endsWithLength = strlen($endsWithIndex);
  333. if (strlen($uri) >= $endsWithLength &&
  334. substr($uri, -$endsWithLength) === $endsWithIndex
  335. ) {
  336. $uri = '/';
  337. }
  338. return $uri;
  339. }
  340. /**
  341. * Returns a base URL and sets the proper webroot
  342. *
  343. * If CakePHP is called with index.php in the URL even though
  344. * URL Rewriting is activated (and thus not needed) it swallows
  345. * the unnecessary part from $base to prevent issue #3318.
  346. *
  347. * @return array Base URL, webroot dir ending in /
  348. */
  349. protected static function _base()
  350. {
  351. $base = $webroot = $baseUrl = null;
  352. $config = Configure::read('App');
  353. extract($config);
  354. if ($base !== false && $base !== null) {
  355. return [$base, $base . '/'];
  356. }
  357. if (!$baseUrl) {
  358. $base = dirname(env('PHP_SELF'));
  359. // Clean up additional / which cause following code to fail..
  360. $base = preg_replace('#/+#', '/', $base);
  361. $indexPos = strpos($base, '/' . $webroot . '/index.php');
  362. if ($indexPos !== false) {
  363. $base = substr($base, 0, $indexPos) . '/' . $webroot;
  364. }
  365. if ($webroot === basename($base)) {
  366. $base = dirname($base);
  367. }
  368. if ($base === DIRECTORY_SEPARATOR || $base === '.') {
  369. $base = '';
  370. }
  371. $base = implode('/', array_map('rawurlencode', explode('/', $base)));
  372. return [$base, $base . '/'];
  373. }
  374. $file = '/' . basename($baseUrl);
  375. $base = dirname($baseUrl);
  376. if ($base === DIRECTORY_SEPARATOR || $base === '.') {
  377. $base = '';
  378. }
  379. $webrootDir = $base . '/';
  380. $docRoot = env('DOCUMENT_ROOT');
  381. $docRootContainsWebroot = strpos($docRoot, $webroot);
  382. if (!empty($base) || !$docRootContainsWebroot) {
  383. if (strpos($webrootDir, '/' . $webroot . '/') === false) {
  384. $webrootDir .= $webroot . '/';
  385. }
  386. }
  387. return [$base . $file, $webrootDir];
  388. }
  389. /**
  390. * Process uploaded files and move things onto the post data.
  391. *
  392. * @param array $post Post data to merge files onto.
  393. * @param array $files Uploaded files to merge in.
  394. * @return array merged post + file data.
  395. */
  396. protected function _processFiles($post, $files)
  397. {
  398. if (is_array($files)) {
  399. foreach ($files as $key => $data) {
  400. if (isset($data['tmp_name']) && is_string($data['tmp_name'])) {
  401. $post[$key] = $data;
  402. } else {
  403. $keyData = isset($post[$key]) ? $post[$key] : [];
  404. $post[$key] = $this->_processFileData($keyData, $data);
  405. }
  406. }
  407. }
  408. return $post;
  409. }
  410. /**
  411. * Recursively walks the FILES array restructuring the data
  412. * into something sane and usable.
  413. *
  414. * @param array $data The data being built
  415. * @param array $post The post data being traversed
  416. * @param string $path The dot separated path to insert $data into.
  417. * @param string $field The terminal field in the path. This is one of the
  418. * $_FILES properties e.g. name, tmp_name, size, error
  419. * @return array The restructured FILES data
  420. */
  421. protected function _processFileData($data, $post, $path = '', $field = '')
  422. {
  423. foreach ($post as $key => $fields) {
  424. $newField = $field;
  425. $newPath = $path;
  426. if ($path === '' && $newField === '') {
  427. $newField = $key;
  428. }
  429. if ($field === $newField) {
  430. $newPath .= '.' . $key;
  431. }
  432. if (is_array($fields)) {
  433. $data = $this->_processFileData($data, $fields, $newPath, $newField);
  434. } else {
  435. $newPath = trim($newPath . '.' . $field, '.');
  436. $data = Hash::insert($data, $newPath, $fields);
  437. }
  438. }
  439. return $data;
  440. }
  441. /**
  442. * Get the content type used in this request.
  443. *
  444. * @return string
  445. */
  446. public function contentType()
  447. {
  448. $type = $this->env('CONTENT_TYPE');
  449. if ($type) {
  450. return $type;
  451. }
  452. return $this->env('HTTP_CONTENT_TYPE');
  453. }
  454. /**
  455. * Returns the instance of the Session object for this request
  456. *
  457. * If a session object is passed as first argument it will be set as
  458. * the session to use for this request
  459. *
  460. * @param \Cake\Network\Session|null $session the session object to use
  461. * @return \Cake\Network\Session
  462. */
  463. public function session(Session $session = null)
  464. {
  465. if ($session === null) {
  466. return $this->_session;
  467. }
  468. return $this->_session = $session;
  469. }
  470. /**
  471. * Get the IP the client is using, or says they are using.
  472. *
  473. * @return string The client IP.
  474. */
  475. public function clientIp()
  476. {
  477. if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_FOR')) {
  478. $ipaddr = preg_replace('/(?:,.*)/', '', $this->env('HTTP_X_FORWARDED_FOR'));
  479. } elseif ($this->trustProxy && $this->env('HTTP_CLIENT_IP')) {
  480. $ipaddr = $this->env('HTTP_CLIENT_IP');
  481. } else {
  482. $ipaddr = $this->env('REMOTE_ADDR');
  483. }
  484. return trim($ipaddr);
  485. }
  486. /**
  487. * Returns the referer that referred this request.
  488. *
  489. * @param bool $local Attempt to return a local address.
  490. * Local addresses do not contain hostnames.
  491. * @return string The referring address for this request.
  492. */
  493. public function referer($local = false)
  494. {
  495. $ref = $this->env('HTTP_REFERER');
  496. $base = Configure::read('App.fullBaseUrl') . $this->webroot;
  497. if (!empty($ref) && !empty($base)) {
  498. if ($local && strpos($ref, $base) === 0) {
  499. $ref = substr($ref, strlen($base));
  500. if (!strlen($ref)) {
  501. $ref = '/';
  502. }
  503. if ($ref[0] !== '/') {
  504. $ref = '/' . $ref;
  505. }
  506. return $ref;
  507. }
  508. if (!$local) {
  509. return $ref;
  510. }
  511. }
  512. return '/';
  513. }
  514. /**
  515. * Missing method handler, handles wrapping older style isAjax() type methods
  516. *
  517. * @param string $name The method called
  518. * @param array $params Array of parameters for the method call
  519. * @return mixed
  520. * @throws \BadMethodCallException when an invalid method is called.
  521. */
  522. public function __call($name, $params)
  523. {
  524. if (strpos($name, 'is') === 0) {
  525. $type = strtolower(substr($name, 2));
  526. array_unshift($params, $type);
  527. return call_user_func_array([$this, 'is'], $params);
  528. }
  529. throw new BadMethodCallException(sprintf('Method %s does not exist', $name));
  530. }
  531. /**
  532. * Magic get method allows access to parsed routing parameters directly on the object.
  533. *
  534. * Allows access to `$this->params['controller']` via `$this->controller`
  535. *
  536. * @param string $name The property being accessed.
  537. * @return mixed Either the value of the parameter or null.
  538. */
  539. public function __get($name)
  540. {
  541. if (isset($this->params[$name])) {
  542. return $this->params[$name];
  543. }
  544. return null;
  545. }
  546. /**
  547. * Magic isset method allows isset/empty checks
  548. * on routing parameters.
  549. *
  550. * @param string $name The property being accessed.
  551. * @return bool Existence
  552. */
  553. public function __isset($name)
  554. {
  555. return isset($this->params[$name]);
  556. }
  557. /**
  558. * Check whether or not a Request is a certain type.
  559. *
  560. * Uses the built in detection rules as well as additional rules
  561. * defined with Cake\Network\CakeRequest::addDetector(). Any detector can be called
  562. * as `is($type)` or `is$Type()`.
  563. *
  564. * @param string|array $type The type of request you want to check. If an array
  565. * this method will return true if the request matches any type.
  566. * @return bool Whether or not the request is the type you are checking.
  567. */
  568. public function is($type)
  569. {
  570. if (is_array($type)) {
  571. $result = array_map([$this, 'is'], $type);
  572. return count(array_filter($result)) > 0;
  573. }
  574. $args = func_get_args();
  575. array_shift($args);
  576. $type = strtolower($type);
  577. if (!isset(static::$_detectors[$type])) {
  578. return false;
  579. }
  580. if ($args) {
  581. return $this->_is($type, $args);
  582. }
  583. if (!isset($this->_detectorCache[$type])) {
  584. $this->_detectorCache[$type] = $this->_is($type, $args);
  585. }
  586. return $this->_detectorCache[$type];
  587. }
  588. /**
  589. * Clears the instance detector cache, used by the is() function
  590. *
  591. * @return void
  592. */
  593. public function clearDetectorCache()
  594. {
  595. $this->_detectorCache = [];
  596. }
  597. /**
  598. * Worker for the public is() function
  599. *
  600. * @param string|array $type The type of request you want to check. If an array
  601. * this method will return true if the request matches any type.
  602. * @param array $args Array of custom detector arguments.
  603. * @return bool Whether or not the request is the type you are checking.
  604. */
  605. protected function _is($type, $args)
  606. {
  607. $detect = static::$_detectors[$type];
  608. if (is_callable($detect)) {
  609. array_unshift($args, $this);
  610. return call_user_func_array($detect, $args);
  611. }
  612. if (isset($detect['env']) && $this->_environmentDetector($detect)) {
  613. return true;
  614. }
  615. if (isset($detect['header']) && $this->_headerDetector($detect)) {
  616. return true;
  617. }
  618. if (isset($detect['accept']) && $this->_acceptHeaderDetector($detect)) {
  619. return true;
  620. }
  621. if (isset($detect['param']) && $this->_paramDetector($detect)) {
  622. return true;
  623. }
  624. return false;
  625. }
  626. /**
  627. * Detects if a specific accept header is present.
  628. *
  629. * @param array $detect Detector options array.
  630. * @return bool Whether or not the request is the type you are checking.
  631. */
  632. protected function _acceptHeaderDetector($detect)
  633. {
  634. $acceptHeaders = explode(',', $this->env('HTTP_ACCEPT'));
  635. foreach ($detect['accept'] as $header) {
  636. if (in_array($header, $acceptHeaders)) {
  637. return true;
  638. }
  639. }
  640. return false;
  641. }
  642. /**
  643. * Detects if a specific header is present.
  644. *
  645. * @param array $detect Detector options array.
  646. * @return bool Whether or not the request is the type you are checking.
  647. */
  648. protected function _headerDetector($detect)
  649. {
  650. foreach ($detect['header'] as $header => $value) {
  651. $header = $this->env('http_' . $header);
  652. if ($header !== null) {
  653. if (!is_string($value) && !is_bool($value) && is_callable($value)) {
  654. return call_user_func($value, $header);
  655. }
  656. return ($header === $value);
  657. }
  658. }
  659. return false;
  660. }
  661. /**
  662. * Detects if a specific request parameter is present.
  663. *
  664. * @param array $detect Detector options array.
  665. * @return bool Whether or not the request is the type you are checking.
  666. */
  667. protected function _paramDetector($detect)
  668. {
  669. $key = $detect['param'];
  670. if (isset($detect['value'])) {
  671. $value = $detect['value'];
  672. return isset($this->params[$key]) ? $this->params[$key] == $value : false;
  673. }
  674. if (isset($detect['options'])) {
  675. return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false;
  676. }
  677. return false;
  678. }
  679. /**
  680. * Detects if a specific environment variable is present.
  681. *
  682. * @param array $detect Detector options array.
  683. * @return bool Whether or not the request is the type you are checking.
  684. */
  685. protected function _environmentDetector($detect)
  686. {
  687. if (isset($detect['env'])) {
  688. if (isset($detect['value'])) {
  689. return $this->env($detect['env']) == $detect['value'];
  690. }
  691. if (isset($detect['pattern'])) {
  692. return (bool)preg_match($detect['pattern'], $this->env($detect['env']));
  693. }
  694. if (isset($detect['options'])) {
  695. $pattern = '/' . implode('|', $detect['options']) . '/i';
  696. return (bool)preg_match($pattern, $this->env($detect['env']));
  697. }
  698. }
  699. return false;
  700. }
  701. /**
  702. * Check that a request matches all the given types.
  703. *
  704. * Allows you to test multiple types and union the results.
  705. * See Request::is() for how to add additional types and the
  706. * built-in types.
  707. *
  708. * @param array $types The types to check.
  709. * @return bool Success.
  710. * @see \Cake\Network\Request::is()
  711. */
  712. public function isAll(array $types)
  713. {
  714. $result = array_filter(array_map([$this, 'is'], $types));
  715. return count($result) === count($types);
  716. }
  717. /**
  718. * Add a new detector to the list of detectors that a request can use.
  719. * There are several different formats and types of detectors that can be set.
  720. *
  721. * ### Callback detectors
  722. *
  723. * Callback detectors allow you to provide a callable to handle the check.
  724. * The callback will receive the request object as its only parameter.
  725. *
  726. * ```
  727. * addDetector('custom', function ($request) { //Return a boolean });
  728. * addDetector('custom', ['SomeClass', 'somemethod']);
  729. * ```
  730. *
  731. * ### Environment value comparison
  732. *
  733. * An environment value comparison, compares a value fetched from `env()` to a known value
  734. * the environment value is equality checked against the provided value.
  735. *
  736. * e.g `addDetector('post', ['env' => 'REQUEST_METHOD', 'value' => 'POST'])`
  737. *
  738. * ### Pattern value comparison
  739. *
  740. * Pattern value comparison allows you to compare a value fetched from `env()` to a regular expression.
  741. *
  742. * ```
  743. * addDetector('iphone', ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i']);
  744. * ```
  745. *
  746. * ### Option based comparison
  747. *
  748. * Option based comparisons use a list of options to create a regular expression. Subsequent calls
  749. * to add an already defined options detector will merge the options.
  750. *
  751. * ```
  752. * addDetector('mobile', ['env' => 'HTTP_USER_AGENT', 'options' => ['Fennec']]);
  753. * ```
  754. *
  755. * ### Request parameter detectors
  756. *
  757. * Allows for custom detectors on the request parameters.
  758. *
  759. * e.g `addDetector('requested', ['param' => 'requested', 'value' => 1]`
  760. *
  761. * You can also make parameter detectors that accept multiple values
  762. * using the `options` key. This is useful when you want to check
  763. * if a request parameter is in a list of options.
  764. *
  765. * `addDetector('extension', ['param' => 'ext', 'options' => ['pdf', 'csv']]`
  766. *
  767. * @param string $name The name of the detector.
  768. * @param callable|array $callable A callable or options array for the detector definition.
  769. * @return void
  770. */
  771. public static function addDetector($name, $callable)
  772. {
  773. $name = strtolower($name);
  774. if (is_callable($callable)) {
  775. static::$_detectors[$name] = $callable;
  776. return;
  777. }
  778. if (isset(static::$_detectors[$name], $callable['options'])) {
  779. $callable = Hash::merge(static::$_detectors[$name], $callable);
  780. }
  781. static::$_detectors[$name] = $callable;
  782. }
  783. /**
  784. * Add parameters to the request's parsed parameter set. This will overwrite any existing parameters.
  785. * This modifies the parameters available through `$request->params`.
  786. *
  787. * @param array $params Array of parameters to merge in
  788. * @return $this The current object, you can chain this method.
  789. */
  790. public function addParams(array $params)
  791. {
  792. $this->params = array_merge($this->params, $params);
  793. return $this;
  794. }
  795. /**
  796. * Add paths to the requests' paths vars. This will overwrite any existing paths.
  797. * Provides an easy way to modify, here, webroot and base.
  798. *
  799. * @param array $paths Array of paths to merge in
  800. * @return $this The current object, you can chain this method.
  801. */
  802. public function addPaths(array $paths)
  803. {
  804. foreach (['webroot', 'here', 'base'] as $element) {
  805. if (isset($paths[$element])) {
  806. $this->{$element} = $paths[$element];
  807. }
  808. }
  809. return $this;
  810. }
  811. /**
  812. * Get the value of the current requests URL. Will include querystring arguments.
  813. *
  814. * @param bool $base Include the base path, set to false to trim the base path off.
  815. * @return string The current request URL including query string args.
  816. */
  817. public function here($base = true)
  818. {
  819. $url = $this->here;
  820. if (!empty($this->query)) {
  821. $url .= '?' . http_build_query($this->query, null, '&');
  822. }
  823. if (!$base) {
  824. $url = preg_replace('/^' . preg_quote($this->base, '/') . '/', '', $url, 1);
  825. }
  826. return $url;
  827. }
  828. /**
  829. * Read an HTTP header from the Request information.
  830. *
  831. * @param string $name Name of the header you want.
  832. * @return string|null Either null on no header being set or the value of the header.
  833. */
  834. public function header($name)
  835. {
  836. $name = str_replace('-', '_', $name);
  837. if (!in_array(strtoupper($name), ['CONTENT_LENGTH', 'CONTENT_TYPE'])) {
  838. $name = 'HTTP_' . $name;
  839. }
  840. return $this->env($name);
  841. }
  842. /**
  843. * Get the HTTP method used for this request.
  844. * There are a few ways to specify a method.
  845. *
  846. * - If your client supports it you can use native HTTP methods.
  847. * - You can set the HTTP-X-Method-Override header.
  848. * - You can submit an input with the name `_method`
  849. *
  850. * Any of these 3 approaches can be used to set the HTTP method used
  851. * by CakePHP internally, and will effect the result of this method.
  852. *
  853. * @return string The name of the HTTP method used.
  854. */
  855. public function method()
  856. {
  857. return $this->env('REQUEST_METHOD');
  858. }
  859. /**
  860. * Get the host that the request was handled on.
  861. *
  862. * @return string
  863. */
  864. public function host()
  865. {
  866. if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_HOST')) {
  867. return $this->env('HTTP_X_FORWARDED_HOST');
  868. }
  869. return $this->env('HTTP_HOST');
  870. }
  871. /**
  872. * Get the port the request was handled on.
  873. *
  874. * @return string
  875. */
  876. public function port()
  877. {
  878. if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_PORT')) {
  879. return $this->env('HTTP_X_FORWARDED_PORT');
  880. }
  881. return $this->env('SERVER_PORT');
  882. }
  883. /**
  884. * Get the current url scheme used for the request.
  885. *
  886. * e.g. 'http', or 'https'
  887. *
  888. * @return string The scheme used for the request.
  889. */
  890. public function scheme()
  891. {
  892. if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_PROTO')) {
  893. return $this->env('HTTP_X_FORWARDED_PROTO');
  894. }
  895. return $this->env('HTTPS') ? 'https' : 'http';
  896. }
  897. /**
  898. * Get the domain name and include $tldLength segments of the tld.
  899. *
  900. * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
  901. * While `example.co.uk` contains 2.
  902. * @return string Domain name without subdomains.
  903. */
  904. public function domain($tldLength = 1)
  905. {
  906. $segments = explode('.', $this->host());
  907. $domain = array_slice($segments, -1 * ($tldLength + 1));
  908. return implode('.', $domain);
  909. }
  910. /**
  911. * Get the subdomains for a host.
  912. *
  913. * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
  914. * While `example.co.uk` contains 2.
  915. * @return array An array of subdomains.
  916. */
  917. public function subdomains($tldLength = 1)
  918. {
  919. $segments = explode('.', $this->host());
  920. return array_slice($segments, 0, -1 * ($tldLength + 1));
  921. }
  922. /**
  923. * Find out which content types the client accepts or check if they accept a
  924. * particular type of content.
  925. *
  926. * #### Get all types:
  927. *
  928. * ```
  929. * $this->request->accepts();
  930. * ```
  931. *
  932. * #### Check for a single type:
  933. *
  934. * ```
  935. * $this->request->accepts('application/json');
  936. * ```
  937. *
  938. * This method will order the returned content types by the preference values indicated
  939. * by the client.
  940. *
  941. * @param string|null $type The content type to check for. Leave null to get all types a client accepts.
  942. * @return array|bool Either an array of all the types the client accepts or a boolean if they accept the
  943. * provided type.
  944. */
  945. public function accepts($type = null)
  946. {
  947. $raw = $this->parseAccept();
  948. $accept = [];
  949. foreach ($raw as $types) {
  950. $accept = array_merge($accept, $types);
  951. }
  952. if ($type === null) {
  953. return $accept;
  954. }
  955. return in_array($type, $accept);
  956. }
  957. /**
  958. * Parse the HTTP_ACCEPT header and return a sorted array with content types
  959. * as the keys, and pref values as the values.
  960. *
  961. * Generally you want to use Cake\Network\Request::accept() to get a simple list
  962. * of the accepted content types.
  963. *
  964. * @return array An array of prefValue => [content/types]
  965. */
  966. public function parseAccept()
  967. {
  968. return $this->_parseAcceptWithQualifier($this->header('accept'));
  969. }
  970. /**
  971. * Get the languages accepted by the client, or check if a specific language is accepted.
  972. *
  973. * Get the list of accepted languages:
  974. *
  975. * ``` \Cake\Network\Request::acceptLanguage(); ```
  976. *
  977. * Check if a specific language is accepted:
  978. *
  979. * ``` \Cake\Network\Request::acceptLanguage('es-es'); ```
  980. *
  981. * @param string|null $language The language to test.
  982. * @return array|bool If a $language is provided, a boolean. Otherwise the array of accepted languages.
  983. */
  984. public function acceptLanguage($language = null)
  985. {
  986. $raw = $this->_parseAcceptWithQualifier($this->header('Accept-Language'));
  987. $accept = [];
  988. foreach ($raw as $languages) {
  989. foreach ($languages as &$lang) {
  990. if (strpos($lang, '_')) {
  991. $lang = str_replace('_', '-', $lang);
  992. }
  993. $lang = strtolower($lang);
  994. }
  995. $accept = array_merge($accept, $languages);
  996. }
  997. if ($language === null) {
  998. return $accept;
  999. }
  1000. return in_array(strtolower($language), $accept);
  1001. }
  1002. /**
  1003. * Parse Accept* headers with qualifier options.
  1004. *
  1005. * Only qualifiers will be extracted, any other accept extensions will be
  1006. * discarded as they are not frequently used.
  1007. *
  1008. * @param string $header Header to parse.
  1009. * @return array
  1010. */
  1011. protected function _parseAcceptWithQualifier($header)
  1012. {
  1013. $accept = [];
  1014. $header = explode(',', $header);
  1015. foreach (array_filter($header) as $value) {
  1016. $prefValue = '1.0';
  1017. $value = trim($value);
  1018. $semiPos = strpos($value, ';');
  1019. if ($semiPos !== false) {
  1020. $params = explode(';', $value);
  1021. $value = trim($params[0]);
  1022. foreach ($params as $param) {
  1023. $qPos = strpos($param, 'q=');
  1024. if ($qPos !== false) {
  1025. $prefValue = substr($param, $qPos + 2);
  1026. }
  1027. }
  1028. }
  1029. if (!isset($accept[$prefValue])) {
  1030. $accept[$prefValue] = [];
  1031. }
  1032. if ($prefValue) {
  1033. $accept[$prefValue][] = $value;
  1034. }
  1035. }
  1036. krsort($accept);
  1037. return $accept;
  1038. }
  1039. /**
  1040. * Provides a read accessor for `$this->query`. Allows you
  1041. * to use a syntax similar to `CakeSession` for reading URL query data.
  1042. *
  1043. * @param string|null $name Query string variable name or null to read all.
  1044. * @return mixed The value being read
  1045. */
  1046. public function query($name = null)
  1047. {
  1048. if ($name === null) {
  1049. return $this->query;
  1050. }
  1051. return Hash::get($this->query, $name);
  1052. }
  1053. /**
  1054. * Provides a read/write accessor for `$this->data`. Allows you
  1055. * to use a syntax similar to `Cake\Model\Datasource\Session` for reading post data.
  1056. *
  1057. * ### Reading values.
  1058. *
  1059. * ```
  1060. * $request->data('Post.title');
  1061. * ```
  1062. *
  1063. * When reading values you will get `null` for keys/values that do not exist.
  1064. *
  1065. * ### Writing values
  1066. *
  1067. * ```
  1068. * $request->data('Post.title', 'New post!');
  1069. * ```
  1070. *
  1071. * You can write to any value, even paths/keys that do not exist, and the arrays
  1072. * will be created for you.
  1073. *
  1074. * @param string|null $name Dot separated name of the value to read/write
  1075. * @return mixed|$this Either the value being read, or this so you can chain consecutive writes.
  1076. */
  1077. public function data($name = null)
  1078. {
  1079. $args = func_get_args();
  1080. if (count($args) === 2) {
  1081. $this->data = Hash::insert($this->data, $name, $args[1]);
  1082. return $this;
  1083. }
  1084. if ($name !== null) {
  1085. return Hash::get($this->data, $name);
  1086. }
  1087. return $this->data;
  1088. }
  1089. /**
  1090. * Safely access the values in $this->params.
  1091. *
  1092. * @param string $name The name of the parameter to get.
  1093. * @return mixed|$this The value of the provided parameter. Will
  1094. * return false if the parameter doesn't exist or is falsey.
  1095. */
  1096. public function param($name)
  1097. {
  1098. $args = func_get_args();
  1099. if (count($args) === 2) {
  1100. $this->params = Hash::insert($this->params, $name, $args[1]);
  1101. return $this;
  1102. }
  1103. if (!isset($this->params[$name])) {
  1104. return Hash::get($this->params, $name, false);
  1105. }
  1106. return $this->params[$name];
  1107. }
  1108. /**
  1109. * Read data from `php://input`. Useful when interacting with XML or JSON
  1110. * request body content.
  1111. *
  1112. * Getting input with a decoding function:
  1113. *
  1114. * ```
  1115. * $this->request->input('json_decode');
  1116. * ```
  1117. *
  1118. * Getting input using a decoding function, and additional params:
  1119. *
  1120. * ```
  1121. * $this->request->input('Xml::build', ['return' => 'DOMDocument']);
  1122. * ```
  1123. *
  1124. * Any additional parameters are applied to the callback in the order they are given.
  1125. *
  1126. * @param string|null $callback A decoding callback that will convert the string data to another
  1127. * representation. Leave empty to access the raw input data. You can also
  1128. * supply additional parameters for the decoding callback using var args, see above.
  1129. * @return string The decoded/processed request data.
  1130. */
  1131. public function input($callback = null)
  1132. {
  1133. $input = $this->_readInput();
  1134. $args = func_get_args();
  1135. if (!empty($args)) {
  1136. $callback = array_shift($args);
  1137. array_unshift($args, $input);
  1138. return call_user_func_array($callback, $args);
  1139. }
  1140. return $input;
  1141. }
  1142. /**
  1143. * Read cookie data from the request's cookie data.
  1144. *
  1145. * @param string $key The key you want to read.
  1146. * @return null|string Either the cookie value, or null if the value doesn't exist.
  1147. */
  1148. public function cookie($key)
  1149. {
  1150. if (isset($this->cookies[$key])) {
  1151. return $this->cookies[$key];
  1152. }
  1153. return null;
  1154. }
  1155. /**
  1156. * Get/Set value from the request's environment data.
  1157. * Fallback to using env() if key not set in $environment property.
  1158. *
  1159. * @param string $key The key you want to read/write from/to.
  1160. * @param string|null $value Value to set. Default null.
  1161. * @param string|null $default Default value when trying to retrieve an environment
  1162. * variable's value that does not exist. The value parameter must be null.
  1163. * @return $this|string|null This instance if used as setter,
  1164. * if used as getter either the environment value, or null if the value doesn't exist.
  1165. */
  1166. public function env($key, $value = null, $default = null)
  1167. {
  1168. if ($value !== null) {
  1169. $this->_environment[$key] = $value;
  1170. $this->clearDetectorCache();
  1171. return $this;
  1172. }
  1173. $key = strtoupper($key);
  1174. if (!array_key_exists($key, $this->_environment)) {
  1175. $this->_environment[$key] = env($key);
  1176. }
  1177. return $this->_environment[$key] !== null ? $this->_environment[$key] : $default;
  1178. }
  1179. /**
  1180. * Allow only certain HTTP request methods, if the request method does not match
  1181. * a 405 error will be shown and the required "Allow" response header will be set.
  1182. *
  1183. * Example:
  1184. *
  1185. * $this->request->allowMethod('post');
  1186. * or
  1187. * $this->request->allowMethod(['post', 'delete']);
  1188. *
  1189. * If the request would be GET, response header "Allow: POST, DELETE" will be set
  1190. * and a 405 error will be returned.
  1191. *
  1192. * @param string|array $methods Allowed HTTP request methods.
  1193. * @return bool true
  1194. * @throws \Cake\Network\Exception\MethodNotAllowedException
  1195. */
  1196. public function allowMethod($methods)
  1197. {
  1198. $methods = (array)$methods;
  1199. foreach ($methods as $method) {
  1200. if ($this->is($method)) {
  1201. return true;
  1202. }
  1203. }
  1204. $allowed = strtoupper(implode(', ', $methods));
  1205. $e = new MethodNotAllowedException();
  1206. $e->responseHeader('Allow', $allowed);
  1207. throw $e;
  1208. }
  1209. /**
  1210. * Read data from php://input, mocked in tests.
  1211. *
  1212. * @return string contents of php://input
  1213. */
  1214. protected function _readInput()
  1215. {
  1216. if (empty($this->_input)) {
  1217. $fh = fopen('php://input', 'r');
  1218. $content = stream_get_contents($fh);
  1219. fclose($fh);
  1220. $this->_input = $content;
  1221. }
  1222. return $this->_input;
  1223. }
  1224. /**
  1225. * Modify data originally from `php://input`. Useful for altering json/xml data
  1226. * in middleware or DispatcherFilters before it gets to RequestHandlerComponent
  1227. *
  1228. * @param string $input A string to replace original parsed data from input()
  1229. * @return void
  1230. */
  1231. public function setInput($input)
  1232. {
  1233. $this->_input = $input;
  1234. }
  1235. /**
  1236. * Array access read implementation
  1237. *
  1238. * @param string $name Name of the key being accessed.
  1239. * @return mixed
  1240. */
  1241. public function offsetGet($name)
  1242. {
  1243. if (isset($this->params[$name])) {
  1244. return $this->params[$name];
  1245. }
  1246. if ($name === 'url') {
  1247. return $this->query;
  1248. }
  1249. if ($name === 'data') {
  1250. return $this->data;
  1251. }
  1252. return null;
  1253. }
  1254. /**
  1255. * Array access write implementation
  1256. *
  1257. * @param string $name Name of the key being written
  1258. * @param mixed $value The value being written.
  1259. * @return void
  1260. */
  1261. public function offsetSet($name, $value)
  1262. {
  1263. $this->params[$name] = $value;
  1264. }
  1265. /**
  1266. * Array access isset() implementation
  1267. *
  1268. * @param string $name thing to check.
  1269. * @return bool
  1270. */
  1271. public function offsetExists($name)
  1272. {
  1273. if ($name === 'url' || $name === 'data') {
  1274. return true;
  1275. }
  1276. return isset($this->params[$name]);
  1277. }
  1278. /**
  1279. * Array access unset() implementation
  1280. *
  1281. * @param string $name Name to unset.
  1282. * @return void
  1283. */
  1284. public function offsetUnset($name)
  1285. {
  1286. unset($this->params[$name]);
  1287. }
  1288. }