Request.php 43 KB

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