ServerRequest.php 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 2.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Http;
  17. use BadMethodCallException;
  18. use Cake\Core\Configure;
  19. use Cake\Core\Exception\CakeException;
  20. use Cake\Http\Cookie\CookieCollection;
  21. use Cake\Http\Exception\MethodNotAllowedException;
  22. use Cake\Utility\Hash;
  23. use Closure;
  24. use InvalidArgumentException;
  25. use Laminas\Diactoros\PhpInputStream;
  26. use Laminas\Diactoros\Stream;
  27. use Laminas\Diactoros\UploadedFile;
  28. use Psr\Http\Message\ServerRequestInterface;
  29. use Psr\Http\Message\StreamInterface;
  30. use Psr\Http\Message\UploadedFileInterface;
  31. use Psr\Http\Message\UriInterface;
  32. /**
  33. * A class that helps wrap Request information and particulars about a single request.
  34. * Provides methods commonly used to introspect on the request headers and request body.
  35. */
  36. class ServerRequest implements ServerRequestInterface
  37. {
  38. /**
  39. * Array of parameters parsed from the URL.
  40. *
  41. * @var array
  42. */
  43. protected array $params = [
  44. 'plugin' => null,
  45. 'controller' => null,
  46. 'action' => null,
  47. '_ext' => null,
  48. 'pass' => [],
  49. ];
  50. /**
  51. * Array of POST data. Will contain form data as well as uploaded files.
  52. * In PUT/PATCH/DELETE requests this property will contain the form-urlencoded
  53. * data.
  54. *
  55. * @var object|array|null
  56. */
  57. protected object|array|null $data = [];
  58. /**
  59. * Array of query string arguments
  60. *
  61. * @var array
  62. */
  63. protected array $query = [];
  64. /**
  65. * Array of cookie data.
  66. *
  67. * @var array
  68. */
  69. protected array $cookies = [];
  70. /**
  71. * Array of environment data.
  72. *
  73. * @var array
  74. */
  75. protected array $_environment = [];
  76. /**
  77. * Base URL path.
  78. *
  79. * @var string
  80. */
  81. protected string $base;
  82. /**
  83. * webroot path segment for the request.
  84. *
  85. * @var string
  86. */
  87. protected string $webroot = '/';
  88. /**
  89. * Whether to trust HTTP_X headers set by most load balancers.
  90. * Only set to true if your application runs behind load balancers/proxies
  91. * that you control.
  92. *
  93. * @var bool
  94. */
  95. public bool $trustProxy = false;
  96. /**
  97. * Trusted proxies list
  98. *
  99. * @var array<string>
  100. */
  101. protected array $trustedProxies = [];
  102. /**
  103. * The built in detectors used with `is()` can be modified with `addDetector()`.
  104. *
  105. * There are several ways to specify a detector, see \Cake\Http\ServerRequest::addDetector() for the
  106. * various formats and ways to define detectors.
  107. *
  108. * @var array<\Closure|array>
  109. */
  110. protected static array $_detectors = [
  111. 'get' => ['env' => 'REQUEST_METHOD', 'value' => 'GET'],
  112. 'post' => ['env' => 'REQUEST_METHOD', 'value' => 'POST'],
  113. 'put' => ['env' => 'REQUEST_METHOD', 'value' => 'PUT'],
  114. 'patch' => ['env' => 'REQUEST_METHOD', 'value' => 'PATCH'],
  115. 'delete' => ['env' => 'REQUEST_METHOD', 'value' => 'DELETE'],
  116. 'head' => ['env' => 'REQUEST_METHOD', 'value' => 'HEAD'],
  117. 'options' => ['env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'],
  118. 'ssl' => ['env' => 'HTTPS', 'options' => [1, 'on']],
  119. 'ajax' => ['env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'],
  120. 'json' => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'],
  121. 'xml' => ['accept' => ['application/xml', 'text/xml'], 'param' => '_ext', 'value' => 'xml'],
  122. ];
  123. /**
  124. * Instance cache for results of is(something) calls
  125. *
  126. * @var array
  127. */
  128. protected array $_detectorCache = [];
  129. /**
  130. * Request body stream. Contains php://input unless `input` constructor option is used.
  131. *
  132. * @var \Psr\Http\Message\StreamInterface
  133. */
  134. protected StreamInterface $stream;
  135. /**
  136. * Uri instance
  137. *
  138. * @var \Psr\Http\Message\UriInterface
  139. */
  140. protected UriInterface $uri;
  141. /**
  142. * Instance of a Session object relative to this request
  143. *
  144. * @var \Cake\Http\Session
  145. */
  146. protected Session $session;
  147. /**
  148. * Instance of a FlashMessage object relative to this request
  149. *
  150. * @var \Cake\Http\FlashMessage
  151. */
  152. protected FlashMessage $flash;
  153. /**
  154. * Store the additional attributes attached to the request.
  155. *
  156. * @var array
  157. */
  158. protected array $attributes = [];
  159. /**
  160. * A list of properties that emulated by the PSR7 attribute methods.
  161. *
  162. * @var array<string>
  163. */
  164. protected array $emulatedAttributes = ['session', 'flash', 'webroot', 'base', 'params', 'here'];
  165. /**
  166. * Array of Psr\Http\Message\UploadedFileInterface objects.
  167. *
  168. * @var array
  169. */
  170. protected array $uploadedFiles = [];
  171. /**
  172. * The HTTP protocol version used.
  173. *
  174. * @var string|null
  175. */
  176. protected ?string $protocol = null;
  177. /**
  178. * The request target if overridden
  179. *
  180. * @var string|null
  181. */
  182. protected ?string $requestTarget = null;
  183. /**
  184. * Create a new request object.
  185. *
  186. * You can supply the data as either an array or as a string. If you use
  187. * a string you can only supply the URL for the request. Using an array will
  188. * let you provide the following keys:
  189. *
  190. * - `post` POST data or non query string data
  191. * - `query` Additional data from the query string.
  192. * - `files` Uploaded files in a normalized structure, with each leaf an instance of UploadedFileInterface.
  193. * - `cookies` Cookies for this request.
  194. * - `environment` $_SERVER and $_ENV data.
  195. * - `url` The URL without the base path for the request.
  196. * - `uri` The PSR7 UriInterface object. If null, one will be created from `url` or `environment`.
  197. * - `base` The base URL for the request.
  198. * - `webroot` The webroot directory for the request.
  199. * - `input` The data that would come from php://input this is useful for simulating
  200. * requests with put, patch or delete data.
  201. * - `session` An instance of a Session object
  202. *
  203. * @param array<string, mixed> $config An array of request data to create a request with.
  204. */
  205. public function __construct(array $config = [])
  206. {
  207. $config += [
  208. 'params' => $this->params,
  209. 'query' => [],
  210. 'post' => [],
  211. 'files' => [],
  212. 'cookies' => [],
  213. 'environment' => [],
  214. 'url' => '',
  215. 'uri' => null,
  216. 'base' => '',
  217. 'webroot' => '',
  218. 'input' => null,
  219. ];
  220. $this->_setConfig($config);
  221. }
  222. /**
  223. * Process the config/settings data into properties.
  224. *
  225. * @param array<string, mixed> $config The config data to use.
  226. * @return void
  227. */
  228. protected function _setConfig(array $config): void
  229. {
  230. if (empty($config['session'])) {
  231. $config['session'] = new Session([
  232. 'cookiePath' => $config['base'],
  233. ]);
  234. }
  235. if (empty($config['environment']['REQUEST_METHOD'])) {
  236. $config['environment']['REQUEST_METHOD'] = 'GET';
  237. }
  238. $this->cookies = $config['cookies'];
  239. if (isset($config['uri'])) {
  240. if (!$config['uri'] instanceof UriInterface) {
  241. throw new CakeException('The `uri` key must be an instance of ' . UriInterface::class);
  242. }
  243. $uri = $config['uri'];
  244. } else {
  245. if ($config['url'] !== '') {
  246. $config = $this->processUrlOption($config);
  247. }
  248. $uri = ServerRequestFactory::createUri($config['environment']);
  249. }
  250. $this->_environment = $config['environment'];
  251. $this->uri = $uri;
  252. $this->base = $config['base'];
  253. $this->webroot = $config['webroot'];
  254. if (isset($config['input'])) {
  255. $stream = new Stream('php://memory', 'rw');
  256. $stream->write($config['input']);
  257. $stream->rewind();
  258. } else {
  259. $stream = new PhpInputStream();
  260. }
  261. $this->stream = $stream;
  262. $post = $config['post'];
  263. if (!(is_array($post) || is_object($post) || $post === null)) {
  264. throw new InvalidArgumentException(sprintf(
  265. '`post` key must be an array, object or null.'
  266. . ' Got `%s` instead.',
  267. get_debug_type($post)
  268. ));
  269. }
  270. $this->data = $post;
  271. $this->uploadedFiles = $config['files'];
  272. $this->query = $config['query'];
  273. $this->params = $config['params'];
  274. $this->session = $config['session'];
  275. $this->flash = new FlashMessage($this->session);
  276. }
  277. /**
  278. * Set environment vars based on `url` option to facilitate UriInterface instance generation.
  279. *
  280. * `query` option is also updated based on URL's querystring.
  281. *
  282. * @param array<string, mixed> $config Config array.
  283. * @return array<string, mixed> Update config.
  284. */
  285. protected function processUrlOption(array $config): array
  286. {
  287. if ($config['url'][0] !== '/') {
  288. $config['url'] = '/' . $config['url'];
  289. }
  290. if (str_contains($config['url'], '?')) {
  291. [$config['url'], $config['environment']['QUERY_STRING']] = explode('?', $config['url']);
  292. parse_str($config['environment']['QUERY_STRING'], $queryArgs);
  293. $config['query'] += $queryArgs;
  294. }
  295. $config['environment']['REQUEST_URI'] = $config['url'];
  296. return $config;
  297. }
  298. /**
  299. * Get the content type used in this request.
  300. *
  301. * @return string|null
  302. */
  303. public function contentType(): ?string
  304. {
  305. $type = $this->getEnv('CONTENT_TYPE');
  306. if ($type) {
  307. return $type;
  308. }
  309. return $this->getEnv('HTTP_CONTENT_TYPE');
  310. }
  311. /**
  312. * Returns the instance of the Session object for this request
  313. *
  314. * @return \Cake\Http\Session
  315. */
  316. public function getSession(): Session
  317. {
  318. return $this->session;
  319. }
  320. /**
  321. * Returns the instance of the FlashMessage object for this request
  322. *
  323. * @return \Cake\Http\FlashMessage
  324. */
  325. public function getFlash(): FlashMessage
  326. {
  327. return $this->flash;
  328. }
  329. /**
  330. * Get the IP the client is using, or says they are using.
  331. *
  332. * @return string The client IP.
  333. */
  334. public function clientIp(): string
  335. {
  336. if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_FOR')) {
  337. $addresses = array_map('trim', explode(',', (string)$this->getEnv('HTTP_X_FORWARDED_FOR')));
  338. $trusted = (count($this->trustedProxies) > 0);
  339. $n = count($addresses);
  340. if ($trusted) {
  341. $trusted = array_diff($addresses, $this->trustedProxies);
  342. $trusted = (count($trusted) === 1);
  343. }
  344. if ($trusted) {
  345. return $addresses[0];
  346. }
  347. return $addresses[$n - 1];
  348. }
  349. if ($this->trustProxy && $this->getEnv('HTTP_X_REAL_IP')) {
  350. $ipaddr = $this->getEnv('HTTP_X_REAL_IP');
  351. } elseif ($this->trustProxy && $this->getEnv('HTTP_CLIENT_IP')) {
  352. $ipaddr = $this->getEnv('HTTP_CLIENT_IP');
  353. } else {
  354. $ipaddr = $this->getEnv('REMOTE_ADDR');
  355. }
  356. return trim((string)$ipaddr);
  357. }
  358. /**
  359. * register trusted proxies
  360. *
  361. * @param array<string> $proxies ips list of trusted proxies
  362. * @return void
  363. */
  364. public function setTrustedProxies(array $proxies): void
  365. {
  366. $this->trustedProxies = $proxies;
  367. $this->trustProxy = true;
  368. }
  369. /**
  370. * Get trusted proxies
  371. *
  372. * @return array<string>
  373. */
  374. public function getTrustedProxies(): array
  375. {
  376. return $this->trustedProxies;
  377. }
  378. /**
  379. * Returns the referer that referred this request.
  380. *
  381. * @param bool $local Attempt to return a local address.
  382. * Local addresses do not contain hostnames.
  383. * @return string|null The referring address for this request or null.
  384. */
  385. public function referer(bool $local = true): ?string
  386. {
  387. $ref = $this->getEnv('HTTP_REFERER');
  388. $base = Configure::read('App.fullBaseUrl') . $this->webroot;
  389. if (empty($ref) || empty($base)) {
  390. return null;
  391. }
  392. if ($local && str_starts_with($ref, $base)) {
  393. $ref = substr($ref, strlen($base));
  394. if ($ref === '' || str_starts_with($ref, '//')) {
  395. $ref = '/';
  396. }
  397. if ($ref[0] !== '/') {
  398. $ref = '/' . $ref;
  399. }
  400. return $ref;
  401. }
  402. if ($local) {
  403. return null;
  404. }
  405. return $ref;
  406. }
  407. /**
  408. * Missing method handler, handles wrapping older style isAjax() type methods
  409. *
  410. * @param string $name The method called
  411. * @param array $params Array of parameters for the method call
  412. * @return bool
  413. * @throws \BadMethodCallException when an invalid method is called.
  414. */
  415. public function __call(string $name, array $params): mixed
  416. {
  417. if (str_starts_with($name, 'is')) {
  418. $type = strtolower(substr($name, 2));
  419. array_unshift($params, $type);
  420. return $this->is(...$params);
  421. }
  422. throw new BadMethodCallException(sprintf('Method "%s()" does not exist', $name));
  423. }
  424. /**
  425. * Check whether a Request is a certain type.
  426. *
  427. * Uses the built-in detection rules as well as additional rules
  428. * defined with {@link \Cake\Http\ServerRequest::addDetector()}. Any detector can be called
  429. * as `is($type)` or `is$Type()`.
  430. *
  431. * @param array<string>|string $type The type of request you want to check. If an array
  432. * this method will return true if the request matches any type.
  433. * @param mixed ...$args List of arguments
  434. * @return bool Whether the request is the type you are checking.
  435. */
  436. public function is(array|string $type, mixed ...$args): bool
  437. {
  438. if (is_array($type)) {
  439. foreach ($type as $_type) {
  440. if ($this->is($_type)) {
  441. return true;
  442. }
  443. }
  444. return false;
  445. }
  446. $type = strtolower($type);
  447. if (!isset(static::$_detectors[$type])) {
  448. return false;
  449. }
  450. if ($args) {
  451. return $this->_is($type, $args);
  452. }
  453. return $this->_detectorCache[$type] = $this->_detectorCache[$type] ?? $this->_is($type, $args);
  454. }
  455. /**
  456. * Clears the instance detector cache, used by the is() function
  457. *
  458. * @return void
  459. */
  460. public function clearDetectorCache(): void
  461. {
  462. $this->_detectorCache = [];
  463. }
  464. /**
  465. * Worker for the public is() function
  466. *
  467. * @param string $type The type of request you want to check.
  468. * @param array $args Array of custom detector arguments.
  469. * @return bool Whether the request is the type you are checking.
  470. */
  471. protected function _is(string $type, array $args): bool
  472. {
  473. $detect = static::$_detectors[$type];
  474. if ($detect instanceof Closure) {
  475. array_unshift($args, $this);
  476. return $detect(...$args);
  477. }
  478. if (isset($detect['env']) && $this->_environmentDetector($detect)) {
  479. return true;
  480. }
  481. if (isset($detect['header']) && $this->_headerDetector($detect)) {
  482. return true;
  483. }
  484. if (isset($detect['accept']) && $this->_acceptHeaderDetector($detect)) {
  485. return true;
  486. }
  487. if (isset($detect['param']) && $this->_paramDetector($detect)) {
  488. return true;
  489. }
  490. return false;
  491. }
  492. /**
  493. * Detects if a specific accept header is present.
  494. *
  495. * @param array $detect Detector options array.
  496. * @return bool Whether the request is the type you are checking.
  497. */
  498. protected function _acceptHeaderDetector(array $detect): bool
  499. {
  500. $acceptHeaders = explode(',', (string)$this->getEnv('HTTP_ACCEPT'));
  501. foreach ($detect['accept'] as $header) {
  502. if (in_array($header, $acceptHeaders, true)) {
  503. return true;
  504. }
  505. }
  506. return false;
  507. }
  508. /**
  509. * Detects if a specific header is present.
  510. *
  511. * @param array $detect Detector options array.
  512. * @return bool Whether the request is the type you are checking.
  513. */
  514. protected function _headerDetector(array $detect): bool
  515. {
  516. foreach ($detect['header'] as $header => $value) {
  517. $header = $this->getEnv('http_' . $header);
  518. if ($header !== null) {
  519. if ($value instanceof Closure) {
  520. return $value($header);
  521. }
  522. return $header === $value;
  523. }
  524. }
  525. return false;
  526. }
  527. /**
  528. * Detects if a specific request parameter is present.
  529. *
  530. * @param array $detect Detector options array.
  531. * @return bool Whether the request is the type you are checking.
  532. */
  533. protected function _paramDetector(array $detect): bool
  534. {
  535. $key = $detect['param'];
  536. if (isset($detect['value'])) {
  537. $value = $detect['value'];
  538. return isset($this->params[$key]) ? $this->params[$key] == $value : false;
  539. }
  540. if (isset($detect['options'])) {
  541. return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false;
  542. }
  543. return false;
  544. }
  545. /**
  546. * Detects if a specific environment variable is present.
  547. *
  548. * @param array $detect Detector options array.
  549. * @return bool Whether the request is the type you are checking.
  550. */
  551. protected function _environmentDetector(array $detect): bool
  552. {
  553. if (isset($detect['env'])) {
  554. if (isset($detect['value'])) {
  555. return $this->getEnv($detect['env']) == $detect['value'];
  556. }
  557. if (isset($detect['pattern'])) {
  558. return (bool)preg_match($detect['pattern'], (string)$this->getEnv($detect['env']));
  559. }
  560. if (isset($detect['options'])) {
  561. $pattern = '/' . implode('|', $detect['options']) . '/i';
  562. return (bool)preg_match($pattern, (string)$this->getEnv($detect['env']));
  563. }
  564. }
  565. return false;
  566. }
  567. /**
  568. * Check that a request matches all the given types.
  569. *
  570. * Allows you to test multiple types and union the results.
  571. * See Request::is() for how to add additional types and the
  572. * built-in types.
  573. *
  574. * @param array<string> $types The types to check.
  575. * @return bool Success.
  576. * @see \Cake\Http\ServerRequest::is()
  577. */
  578. public function isAll(array $types): bool
  579. {
  580. foreach ($types as $type) {
  581. if (!$this->is($type)) {
  582. return false;
  583. }
  584. }
  585. return true;
  586. }
  587. /**
  588. * Add a new detector to the list of detectors that a request can use.
  589. * There are several different types of detectors that can be set.
  590. *
  591. * ### Callback comparison
  592. *
  593. * Callback detectors allow you to provide a closure to handle the check.
  594. * The closure will receive the request object as its only parameter.
  595. *
  596. * ```
  597. * addDetector('custom', function ($request) { //Return a boolean });
  598. * ```
  599. *
  600. * ### Environment value comparison
  601. *
  602. * An environment value comparison, compares a value fetched from `env()` to a known value
  603. * the environment value is equality checked against the provided value.
  604. *
  605. * ```
  606. * addDetector('post', ['env' => 'REQUEST_METHOD', 'value' => 'POST']);
  607. * ```
  608. *
  609. * ### Request parameter comparison
  610. *
  611. * Allows for custom detectors on the request parameters.
  612. *
  613. * ```
  614. * addDetector('admin', ['param' => 'prefix', 'value' => 'admin']);
  615. * ```
  616. *
  617. * ### Accept comparison
  618. *
  619. * Allows for detector to compare against Accept header value.
  620. *
  621. * ```
  622. * addDetector('csv', ['accept' => 'text/csv']);
  623. * ```
  624. *
  625. * ### Header comparison
  626. *
  627. * Allows for one or more headers to be compared.
  628. *
  629. * ```
  630. * addDetector('fancy', ['header' => ['X-Fancy' => 1]);
  631. * ```
  632. *
  633. * The `param`, `env` and comparison types allow the following
  634. * value comparison options:
  635. *
  636. * ### Pattern value comparison
  637. *
  638. * Pattern value comparison allows you to compare a value fetched from `env()` to a regular expression.
  639. *
  640. * ```
  641. * addDetector('iphone', ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i']);
  642. * ```
  643. *
  644. * ### Option based comparison
  645. *
  646. * Option based comparisons use a list of options to create a regular expression. Subsequent calls
  647. * to add an already defined options detector will merge the options.
  648. *
  649. * ```
  650. * addDetector('mobile', ['env' => 'HTTP_USER_AGENT', 'options' => ['Fennec']]);
  651. * ```
  652. *
  653. * You can also make compare against multiple values
  654. * using the `options` key. This is useful when you want to check
  655. * if a request value is in a list of options.
  656. *
  657. * `addDetector('extension', ['param' => '_ext', 'options' => ['pdf', 'csv']]`
  658. *
  659. * @param string $name The name of the detector.
  660. * @param \Closure|array $detector A Closure or options array for the detector definition.
  661. * @return void
  662. */
  663. public static function addDetector(string $name, Closure|array $detector): void
  664. {
  665. $name = strtolower($name);
  666. if ($detector instanceof Closure) {
  667. static::$_detectors[$name] = $detector;
  668. return;
  669. }
  670. if (isset(static::$_detectors[$name], $detector['options'])) {
  671. /** @psalm-suppress PossiblyInvalidArgument */
  672. $detector = Hash::merge(static::$_detectors[$name], $detector);
  673. }
  674. static::$_detectors[$name] = $detector;
  675. }
  676. /**
  677. * Normalize a header name into the SERVER version.
  678. *
  679. * @param string $name The header name.
  680. * @return string The normalized header name.
  681. */
  682. protected function normalizeHeaderName(string $name): string
  683. {
  684. $name = str_replace('-', '_', strtoupper($name));
  685. if (!in_array($name, ['CONTENT_LENGTH', 'CONTENT_TYPE'], true)) {
  686. $name = 'HTTP_' . $name;
  687. }
  688. return $name;
  689. }
  690. /**
  691. * Get all headers in the request.
  692. *
  693. * Returns an associative array where the header names are
  694. * the keys and the values are a list of header values.
  695. *
  696. * While header names are not case-sensitive, getHeaders() will normalize
  697. * the headers.
  698. *
  699. * @return array<string[]> An associative array of headers and their values.
  700. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  701. */
  702. public function getHeaders(): array
  703. {
  704. $headers = [];
  705. foreach ($this->_environment as $key => $value) {
  706. $name = null;
  707. if (str_starts_with($key, 'HTTP_')) {
  708. $name = substr($key, 5);
  709. }
  710. if (str_starts_with($key, 'CONTENT_')) {
  711. $name = $key;
  712. }
  713. if ($name !== null) {
  714. $name = str_replace('_', ' ', strtolower($name));
  715. $name = str_replace(' ', '-', ucwords($name));
  716. $headers[$name] = (array)$value;
  717. }
  718. }
  719. return $headers;
  720. }
  721. /**
  722. * Check if a header is set in the request.
  723. *
  724. * @param string $name The header you want to get (case-insensitive)
  725. * @return bool Whether the header is defined.
  726. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  727. */
  728. public function hasHeader($name): bool
  729. {
  730. $name = $this->normalizeHeaderName($name);
  731. return isset($this->_environment[$name]);
  732. }
  733. /**
  734. * Get a single header from the request.
  735. *
  736. * Return the header value as an array. If the header
  737. * is not present an empty array will be returned.
  738. *
  739. * @param string $name The header you want to get (case-insensitive)
  740. * @return array<string> An associative array of headers and their values.
  741. * If the header doesn't exist, an empty array will be returned.
  742. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  743. */
  744. public function getHeader($name): array
  745. {
  746. $name = $this->normalizeHeaderName($name);
  747. if (isset($this->_environment[$name])) {
  748. return (array)$this->_environment[$name];
  749. }
  750. return [];
  751. }
  752. /**
  753. * Get a single header as a string from the request.
  754. *
  755. * @param string $name The header you want to get (case-insensitive)
  756. * @return string Header values collapsed into a comma separated string.
  757. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  758. */
  759. public function getHeaderLine($name): string
  760. {
  761. $value = $this->getHeader($name);
  762. return implode(', ', $value);
  763. }
  764. /**
  765. * Get a modified request with the provided header.
  766. *
  767. * @param string $name The header name.
  768. * @param array|string $value The header value
  769. * @return static
  770. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  771. */
  772. public function withHeader($name, $value): static
  773. {
  774. $new = clone $this;
  775. $name = $this->normalizeHeaderName($name);
  776. $new->_environment[$name] = $value;
  777. return $new;
  778. }
  779. /**
  780. * Get a modified request with the provided header.
  781. *
  782. * Existing header values will be retained. The provided value
  783. * will be appended into the existing values.
  784. *
  785. * @param string $name The header name.
  786. * @param array|string $value The header value
  787. * @return static
  788. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  789. */
  790. public function withAddedHeader($name, $value): static
  791. {
  792. $new = clone $this;
  793. $name = $this->normalizeHeaderName($name);
  794. $existing = [];
  795. if (isset($new->_environment[$name])) {
  796. $existing = (array)$new->_environment[$name];
  797. }
  798. $existing = array_merge($existing, (array)$value);
  799. $new->_environment[$name] = $existing;
  800. return $new;
  801. }
  802. /**
  803. * Get a modified request without a provided header.
  804. *
  805. * @param string $name The header name to remove.
  806. * @return static
  807. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  808. */
  809. public function withoutHeader($name): static
  810. {
  811. $new = clone $this;
  812. $name = $this->normalizeHeaderName($name);
  813. unset($new->_environment[$name]);
  814. return $new;
  815. }
  816. /**
  817. * Get the HTTP method used for this request.
  818. * There are a few ways to specify a method.
  819. *
  820. * - If your client supports it you can use native HTTP methods.
  821. * - You can set the HTTP-X-Method-Override header.
  822. * - You can submit an input with the name `_method`
  823. *
  824. * Any of these 3 approaches can be used to set the HTTP method used
  825. * by CakePHP internally, and will effect the result of this method.
  826. *
  827. * @return string The name of the HTTP method used.
  828. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  829. */
  830. public function getMethod(): string
  831. {
  832. return (string)$this->getEnv('REQUEST_METHOD');
  833. }
  834. /**
  835. * Update the request method and get a new instance.
  836. *
  837. * @param string $method The HTTP method to use.
  838. * @return static A new instance with the updated method.
  839. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  840. */
  841. public function withMethod($method): static
  842. {
  843. $new = clone $this;
  844. if (
  845. !is_string($method) ||
  846. !preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method)
  847. ) {
  848. throw new InvalidArgumentException(sprintf(
  849. 'Unsupported HTTP method "%s" provided',
  850. $method
  851. ));
  852. }
  853. $new->_environment['REQUEST_METHOD'] = $method;
  854. return $new;
  855. }
  856. /**
  857. * Get all the server environment parameters.
  858. *
  859. * Read all of the 'environment' or 'server' data that was
  860. * used to create this request.
  861. *
  862. * @return array
  863. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  864. */
  865. public function getServerParams(): array
  866. {
  867. return $this->_environment;
  868. }
  869. /**
  870. * Get all the query parameters in accordance to the PSR-7 specifications. To read specific query values
  871. * use the alternative getQuery() method.
  872. *
  873. * @return array
  874. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  875. */
  876. public function getQueryParams(): array
  877. {
  878. return $this->query;
  879. }
  880. /**
  881. * Update the query string data and get a new instance.
  882. *
  883. * @param array $query The query string data to use
  884. * @return static A new instance with the updated query string data.
  885. * @link http://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface.
  886. */
  887. public function withQueryParams(array $query): static
  888. {
  889. $new = clone $this;
  890. $new->query = $query;
  891. return $new;
  892. }
  893. /**
  894. * Get the host that the request was handled on.
  895. *
  896. * @return string|null
  897. */
  898. public function host(): ?string
  899. {
  900. if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_HOST')) {
  901. return $this->getEnv('HTTP_X_FORWARDED_HOST');
  902. }
  903. return $this->getEnv('HTTP_HOST');
  904. }
  905. /**
  906. * Get the port the request was handled on.
  907. *
  908. * @return string|null
  909. */
  910. public function port(): ?string
  911. {
  912. if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_PORT')) {
  913. return $this->getEnv('HTTP_X_FORWARDED_PORT');
  914. }
  915. return $this->getEnv('SERVER_PORT');
  916. }
  917. /**
  918. * Get the current url scheme used for the request.
  919. *
  920. * e.g. 'http', or 'https'
  921. *
  922. * @return string|null The scheme used for the request.
  923. */
  924. public function scheme(): ?string
  925. {
  926. if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_PROTO')) {
  927. return $this->getEnv('HTTP_X_FORWARDED_PROTO');
  928. }
  929. return $this->getEnv('HTTPS') ? 'https' : 'http';
  930. }
  931. /**
  932. * Get the domain name and include $tldLength segments of the tld.
  933. *
  934. * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
  935. * While `example.co.uk` contains 2.
  936. * @return string Domain name without subdomains.
  937. */
  938. public function domain(int $tldLength = 1): string
  939. {
  940. $host = $this->host();
  941. if (empty($host)) {
  942. return '';
  943. }
  944. $segments = explode('.', $host);
  945. $domain = array_slice($segments, -1 * ($tldLength + 1));
  946. return implode('.', $domain);
  947. }
  948. /**
  949. * Get the subdomains for a host.
  950. *
  951. * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
  952. * While `example.co.uk` contains 2.
  953. * @return array<string> An array of subdomains.
  954. */
  955. public function subdomains(int $tldLength = 1): array
  956. {
  957. $host = $this->host();
  958. if (empty($host)) {
  959. return [];
  960. }
  961. $segments = explode('.', $host);
  962. return array_slice($segments, 0, -1 * ($tldLength + 1));
  963. }
  964. /**
  965. * Find out which content types the client accepts or check if they accept a
  966. * particular type of content.
  967. *
  968. * #### Get all types:
  969. *
  970. * ```
  971. * $this->request->accepts();
  972. * ```
  973. *
  974. * #### Check for a single type:
  975. *
  976. * ```
  977. * $this->request->accepts('application/json');
  978. * ```
  979. *
  980. * This method will order the returned content types by the preference values indicated
  981. * by the client.
  982. *
  983. * @param string|null $type The content type to check for. Leave null to get all types a client accepts.
  984. * @return array<string>|bool Either an array of all the types the client accepts or a boolean if they accept the
  985. * provided type.
  986. */
  987. public function accepts(?string $type = null): array|bool
  988. {
  989. $raw = $this->parseAccept();
  990. $accept = [];
  991. foreach ($raw as $types) {
  992. $accept = array_merge($accept, $types);
  993. }
  994. if ($type === null) {
  995. return $accept;
  996. }
  997. return in_array($type, $accept, true);
  998. }
  999. /**
  1000. * Parse the HTTP_ACCEPT header and return a sorted array with content types
  1001. * as the keys, and pref values as the values.
  1002. *
  1003. * Generally you want to use {@link \Cake\Http\ServerRequest::accepts()} to get a simple list
  1004. * of the accepted content types.
  1005. *
  1006. * @return array An array of `prefValue => [content/types]`
  1007. */
  1008. public function parseAccept(): array
  1009. {
  1010. return $this->_parseAcceptWithQualifier($this->getHeaderLine('Accept'));
  1011. }
  1012. /**
  1013. * Get the languages accepted by the client, or check if a specific language is accepted.
  1014. *
  1015. * Get the list of accepted languages:
  1016. *
  1017. * ``` \Cake\Http\ServerRequest::acceptLanguage(); ```
  1018. *
  1019. * Check if a specific language is accepted:
  1020. *
  1021. * ``` \Cake\Http\ServerRequest::acceptLanguage('es-es'); ```
  1022. *
  1023. * @param string|null $language The language to test.
  1024. * @return array|bool If a $language is provided, a boolean. Otherwise the array of accepted languages.
  1025. */
  1026. public function acceptLanguage(?string $language = null): array|bool
  1027. {
  1028. $raw = $this->_parseAcceptWithQualifier($this->getHeaderLine('Accept-Language'));
  1029. $accept = [];
  1030. foreach ($raw as $languages) {
  1031. foreach ($languages as &$lang) {
  1032. if (str_contains($lang, '_')) {
  1033. $lang = str_replace('_', '-', $lang);
  1034. }
  1035. $lang = strtolower($lang);
  1036. }
  1037. $accept = array_merge($accept, $languages);
  1038. }
  1039. if ($language === null) {
  1040. return $accept;
  1041. }
  1042. return in_array(strtolower($language), $accept, true);
  1043. }
  1044. /**
  1045. * Parse Accept* headers with qualifier options.
  1046. *
  1047. * Only qualifiers will be extracted, any other accept extensions will be
  1048. * discarded as they are not frequently used.
  1049. *
  1050. * @param string $header Header to parse.
  1051. * @return array
  1052. */
  1053. protected function _parseAcceptWithQualifier(string $header): array
  1054. {
  1055. $accept = [];
  1056. $headers = explode(',', $header);
  1057. foreach (array_filter($headers) as $value) {
  1058. $prefValue = '1.0';
  1059. $value = trim($value);
  1060. if (str_contains($value, ';')) {
  1061. $params = explode(';', $value);
  1062. $value = trim($params[0]);
  1063. foreach ($params as $param) {
  1064. $qPos = strpos($param, 'q=');
  1065. if ($qPos !== false) {
  1066. $prefValue = substr($param, $qPos + 2);
  1067. }
  1068. }
  1069. }
  1070. if (!isset($accept[$prefValue])) {
  1071. $accept[$prefValue] = [];
  1072. }
  1073. if ($prefValue) {
  1074. $accept[$prefValue][] = $value;
  1075. }
  1076. }
  1077. krsort($accept);
  1078. return $accept;
  1079. }
  1080. /**
  1081. * Read a specific query value or dotted path.
  1082. *
  1083. * Developers are encouraged to use getQueryParams() if they need the whole query array,
  1084. * as it is PSR-7 compliant, and this method is not. Using Hash::get() you can also get single params.
  1085. *
  1086. * ### PSR-7 Alternative
  1087. *
  1088. * ```
  1089. * $value = Hash::get($request->getQueryParams(), 'Post.id');
  1090. * ```
  1091. *
  1092. * @param string|null $name The name or dotted path to the query param or null to read all.
  1093. * @param mixed $default The default value if the named parameter is not set, and $name is not null.
  1094. * @return mixed Query data.
  1095. * @see ServerRequest::getQueryParams()
  1096. */
  1097. public function getQuery(?string $name = null, mixed $default = null): mixed
  1098. {
  1099. if ($name === null) {
  1100. return $this->query;
  1101. }
  1102. return Hash::get($this->query, $name, $default);
  1103. }
  1104. /**
  1105. * Provides a safe accessor for request data. Allows
  1106. * you to use Hash::get() compatible paths.
  1107. *
  1108. * ### Reading values.
  1109. *
  1110. * ```
  1111. * // get all data
  1112. * $request->getData();
  1113. *
  1114. * // Read a specific field.
  1115. * $request->getData('Post.title');
  1116. *
  1117. * // With a default value.
  1118. * $request->getData('Post.not there', 'default value');
  1119. * ```
  1120. *
  1121. * When reading values you will get `null` for keys/values that do not exist.
  1122. *
  1123. * Developers are encouraged to use getParsedBody() if they need the whole data array,
  1124. * as it is PSR-7 compliant, and this method is not. Using Hash::get() you can also get single params.
  1125. *
  1126. * ### PSR-7 Alternative
  1127. *
  1128. * ```
  1129. * $value = Hash::get($request->getParsedBody(), 'Post.id');
  1130. * ```
  1131. *
  1132. * @param string|null $name Dot separated name of the value to read. Or null to read all data.
  1133. * @param mixed $default The default data.
  1134. * @return mixed The value being read.
  1135. */
  1136. public function getData(?string $name = null, mixed $default = null): mixed
  1137. {
  1138. if ($name === null) {
  1139. return $this->data;
  1140. }
  1141. if (!is_array($this->data)) {
  1142. return $default;
  1143. }
  1144. return Hash::get($this->data, $name, $default);
  1145. }
  1146. /**
  1147. * Read cookie data from the request's cookie data.
  1148. *
  1149. * @param string $key The key or dotted path you want to read.
  1150. * @param array|string|null $default The default value if the cookie is not set.
  1151. * @return array|string|null Either the cookie value, or null if the value doesn't exist.
  1152. */
  1153. public function getCookie(string $key, array|string|null $default = null): array|string|null
  1154. {
  1155. return Hash::get($this->cookies, $key, $default);
  1156. }
  1157. /**
  1158. * Get a cookie collection based on the request's cookies
  1159. *
  1160. * The CookieCollection lets you interact with request cookies using
  1161. * `\Cake\Http\Cookie\Cookie` objects and can make converting request cookies
  1162. * into response cookies easier.
  1163. *
  1164. * This method will create a new cookie collection each time it is called.
  1165. * This is an optimization that allows fewer objects to be allocated until
  1166. * the more complex CookieCollection is needed. In general you should prefer
  1167. * `getCookie()` and `getCookieParams()` over this method. Using a CookieCollection
  1168. * is ideal if your cookies contain complex JSON encoded data.
  1169. *
  1170. * @return \Cake\Http\Cookie\CookieCollection
  1171. */
  1172. public function getCookieCollection(): CookieCollection
  1173. {
  1174. return CookieCollection::createFromServerRequest($this);
  1175. }
  1176. /**
  1177. * Replace the cookies in the request with those contained in
  1178. * the provided CookieCollection.
  1179. *
  1180. * @param \Cake\Http\Cookie\CookieCollection $cookies The cookie collection
  1181. * @return static
  1182. */
  1183. public function withCookieCollection(CookieCollection $cookies): static
  1184. {
  1185. $new = clone $this;
  1186. $values = [];
  1187. foreach ($cookies as $cookie) {
  1188. $values[$cookie->getName()] = $cookie->getValue();
  1189. }
  1190. $new->cookies = $values;
  1191. return $new;
  1192. }
  1193. /**
  1194. * Get all the cookie data from the request.
  1195. *
  1196. * @return array An array of cookie data.
  1197. */
  1198. public function getCookieParams(): array
  1199. {
  1200. return $this->cookies;
  1201. }
  1202. /**
  1203. * Replace the cookies and get a new request instance.
  1204. *
  1205. * @param array $cookies The new cookie data to use.
  1206. * @return static
  1207. */
  1208. public function withCookieParams(array $cookies): static
  1209. {
  1210. $new = clone $this;
  1211. $new->cookies = $cookies;
  1212. return $new;
  1213. }
  1214. /**
  1215. * Get the parsed request body data.
  1216. *
  1217. * If the request Content-Type is either application/x-www-form-urlencoded
  1218. * or multipart/form-data, and the request method is POST, this will be the
  1219. * post data. For other content types, it may be the deserialized request
  1220. * body.
  1221. *
  1222. * @return object|array|null The deserialized body parameters, if any.
  1223. * These will typically be an array.
  1224. */
  1225. public function getParsedBody(): object|array|null
  1226. {
  1227. return $this->data;
  1228. }
  1229. /**
  1230. * Update the parsed body and get a new instance.
  1231. *
  1232. * @param object|array|null $data The deserialized body data. This will
  1233. * typically be in an array or object.
  1234. * @return static
  1235. */
  1236. public function withParsedBody($data): static
  1237. {
  1238. $new = clone $this;
  1239. $new->data = $data;
  1240. return $new;
  1241. }
  1242. /**
  1243. * Retrieves the HTTP protocol version as a string.
  1244. *
  1245. * @return string HTTP protocol version.
  1246. */
  1247. public function getProtocolVersion(): string
  1248. {
  1249. if ($this->protocol) {
  1250. return $this->protocol;
  1251. }
  1252. // Lazily populate this data as it is generally not used.
  1253. preg_match('/^HTTP\/([\d.]+)$/', (string)$this->getEnv('SERVER_PROTOCOL'), $match);
  1254. $protocol = '1.1';
  1255. if (isset($match[1])) {
  1256. $protocol = $match[1];
  1257. }
  1258. $this->protocol = $protocol;
  1259. return $this->protocol;
  1260. }
  1261. /**
  1262. * Return an instance with the specified HTTP protocol version.
  1263. *
  1264. * The version string MUST contain only the HTTP version number (e.g.,
  1265. * "1.1", "1.0").
  1266. *
  1267. * @param string $version HTTP protocol version
  1268. * @return static
  1269. */
  1270. public function withProtocolVersion($version): static
  1271. {
  1272. if (!preg_match('/^(1\.[01]|2)$/', $version)) {
  1273. throw new InvalidArgumentException("Unsupported protocol version '{$version}' provided");
  1274. }
  1275. $new = clone $this;
  1276. $new->protocol = $version;
  1277. return $new;
  1278. }
  1279. /**
  1280. * Get a value from the request's environment data.
  1281. * Fallback to using env() if the key is not set in the $environment property.
  1282. *
  1283. * @param string $key The key you want to read from.
  1284. * @param string|null $default Default value when trying to retrieve an environment
  1285. * variable's value that does not exist.
  1286. * @return string|null Either the environment value, or null if the value doesn't exist.
  1287. */
  1288. public function getEnv(string $key, ?string $default = null): ?string
  1289. {
  1290. $key = strtoupper($key);
  1291. if (!array_key_exists($key, $this->_environment)) {
  1292. $this->_environment[$key] = env($key);
  1293. }
  1294. return $this->_environment[$key] !== null ? (string)$this->_environment[$key] : $default;
  1295. }
  1296. /**
  1297. * Update the request with a new environment data element.
  1298. *
  1299. * Returns an updated request object. This method returns
  1300. * a *new* request object and does not mutate the request in-place.
  1301. *
  1302. * @param string $key The key you want to write to.
  1303. * @param string $value Value to set
  1304. * @return static
  1305. */
  1306. public function withEnv(string $key, string $value): static
  1307. {
  1308. $new = clone $this;
  1309. $new->_environment[$key] = $value;
  1310. $new->clearDetectorCache();
  1311. return $new;
  1312. }
  1313. /**
  1314. * Allow only certain HTTP request methods, if the request method does not match
  1315. * a 405 error will be shown and the required "Allow" response header will be set.
  1316. *
  1317. * Example:
  1318. *
  1319. * $this->request->allowMethod('post');
  1320. * or
  1321. * $this->request->allowMethod(['post', 'delete']);
  1322. *
  1323. * If the request would be GET, response header "Allow: POST, DELETE" will be set
  1324. * and a 405 error will be returned.
  1325. *
  1326. * @param array<string>|string $methods Allowed HTTP request methods.
  1327. * @return true
  1328. * @throws \Cake\Http\Exception\MethodNotAllowedException
  1329. */
  1330. public function allowMethod(array|string $methods): bool
  1331. {
  1332. $methods = (array)$methods;
  1333. foreach ($methods as $method) {
  1334. if ($this->is($method)) {
  1335. return true;
  1336. }
  1337. }
  1338. $allowed = strtoupper(implode(', ', $methods));
  1339. $e = new MethodNotAllowedException();
  1340. $e->setHeader('Allow', $allowed);
  1341. throw $e;
  1342. }
  1343. /**
  1344. * Update the request with a new request data element.
  1345. *
  1346. * Returns an updated request object. This method returns
  1347. * a *new* request object and does not mutate the request in-place.
  1348. *
  1349. * Use `withParsedBody()` if you need to replace the all request data.
  1350. *
  1351. * @param string $name The dot separated path to insert $value at.
  1352. * @param mixed $value The value to insert into the request data.
  1353. * @return static
  1354. */
  1355. public function withData(string $name, mixed $value): static
  1356. {
  1357. $copy = clone $this;
  1358. if (is_array($copy->data)) {
  1359. $copy->data = Hash::insert($copy->data, $name, $value);
  1360. }
  1361. return $copy;
  1362. }
  1363. /**
  1364. * Update the request removing a data element.
  1365. *
  1366. * Returns an updated request object. This method returns
  1367. * a *new* request object and does not mutate the request in-place.
  1368. *
  1369. * @param string $name The dot separated path to remove.
  1370. * @return static
  1371. */
  1372. public function withoutData(string $name): static
  1373. {
  1374. $copy = clone $this;
  1375. if (is_array($copy->data)) {
  1376. $copy->data = Hash::remove($copy->data, $name);
  1377. }
  1378. return $copy;
  1379. }
  1380. /**
  1381. * Update the request with a new routing parameter
  1382. *
  1383. * Returns an updated request object. This method returns
  1384. * a *new* request object and does not mutate the request in-place.
  1385. *
  1386. * @param string $name The dot separated path to insert $value at.
  1387. * @param mixed $value The value to insert into the the request parameters.
  1388. * @return static
  1389. */
  1390. public function withParam(string $name, mixed $value): static
  1391. {
  1392. $copy = clone $this;
  1393. $copy->params = Hash::insert($copy->params, $name, $value);
  1394. return $copy;
  1395. }
  1396. /**
  1397. * Safely access the values in $this->params.
  1398. *
  1399. * @param string $name The name or dotted path to parameter.
  1400. * @param mixed $default The default value if `$name` is not set. Default `null`.
  1401. * @return mixed
  1402. */
  1403. public function getParam(string $name, mixed $default = null): mixed
  1404. {
  1405. return Hash::get($this->params, $name, $default);
  1406. }
  1407. /**
  1408. * Return an instance with the specified request attribute.
  1409. *
  1410. * @param string $name The attribute name.
  1411. * @param mixed $value The value of the attribute.
  1412. * @return static
  1413. */
  1414. public function withAttribute($name, $value): static
  1415. {
  1416. $new = clone $this;
  1417. if (in_array($name, $this->emulatedAttributes, true)) {
  1418. $new->{$name} = $value;
  1419. } else {
  1420. $new->attributes[$name] = $value;
  1421. }
  1422. return $new;
  1423. }
  1424. /**
  1425. * Return an instance without the specified request attribute.
  1426. *
  1427. * @param string $name The attribute name.
  1428. * @return static
  1429. * @throws \InvalidArgumentException
  1430. */
  1431. public function withoutAttribute($name): static
  1432. {
  1433. $new = clone $this;
  1434. if (in_array($name, $this->emulatedAttributes, true)) {
  1435. throw new InvalidArgumentException(
  1436. "You cannot unset '$name'. It is a required CakePHP attribute."
  1437. );
  1438. }
  1439. unset($new->attributes[$name]);
  1440. return $new;
  1441. }
  1442. /**
  1443. * Read an attribute from the request, or get the default
  1444. *
  1445. * @param string $name The attribute name.
  1446. * @param mixed|null $default The default value if the attribute has not been set.
  1447. * @return mixed
  1448. */
  1449. public function getAttribute($name, $default = null)
  1450. {
  1451. if (in_array($name, $this->emulatedAttributes, true)) {
  1452. if ($name === 'here') {
  1453. return $this->base . $this->uri->getPath();
  1454. }
  1455. return $this->{$name};
  1456. }
  1457. if (array_key_exists($name, $this->attributes)) {
  1458. return $this->attributes[$name];
  1459. }
  1460. return $default;
  1461. }
  1462. /**
  1463. * Get all the attributes in the request.
  1464. *
  1465. * This will include the params, webroot, base, and here attributes that CakePHP
  1466. * provides.
  1467. *
  1468. * @return array
  1469. */
  1470. public function getAttributes(): array
  1471. {
  1472. $emulated = [
  1473. 'params' => $this->params,
  1474. 'webroot' => $this->webroot,
  1475. 'base' => $this->base,
  1476. 'here' => $this->base . $this->uri->getPath(),
  1477. ];
  1478. return $this->attributes + $emulated;
  1479. }
  1480. /**
  1481. * Get the uploaded file from a dotted path.
  1482. *
  1483. * @param string $path The dot separated path to the file you want.
  1484. * @return \Psr\Http\Message\UploadedFileInterface|null
  1485. */
  1486. public function getUploadedFile(string $path): ?UploadedFileInterface
  1487. {
  1488. $file = Hash::get($this->uploadedFiles, $path);
  1489. if (!$file instanceof UploadedFile) {
  1490. return null;
  1491. }
  1492. return $file;
  1493. }
  1494. /**
  1495. * Get the array of uploaded files from the request.
  1496. *
  1497. * @return array
  1498. */
  1499. public function getUploadedFiles(): array
  1500. {
  1501. return $this->uploadedFiles;
  1502. }
  1503. /**
  1504. * Update the request replacing the files, and creating a new instance.
  1505. *
  1506. * @param array $uploadedFiles An array of uploaded file objects.
  1507. * @return static
  1508. * @throws \InvalidArgumentException when $files contains an invalid object.
  1509. */
  1510. public function withUploadedFiles(array $uploadedFiles): static
  1511. {
  1512. $this->validateUploadedFiles($uploadedFiles, '');
  1513. $new = clone $this;
  1514. $new->uploadedFiles = $uploadedFiles;
  1515. return $new;
  1516. }
  1517. /**
  1518. * Recursively validate uploaded file data.
  1519. *
  1520. * @param array $uploadedFiles The new files array to validate.
  1521. * @param string $path The path thus far.
  1522. * @return void
  1523. * @throws \InvalidArgumentException If any leaf elements are not valid files.
  1524. */
  1525. protected function validateUploadedFiles(array $uploadedFiles, string $path): void
  1526. {
  1527. foreach ($uploadedFiles as $key => $file) {
  1528. if (is_array($file)) {
  1529. $this->validateUploadedFiles($file, $key . '.');
  1530. continue;
  1531. }
  1532. if (!$file instanceof UploadedFileInterface) {
  1533. throw new InvalidArgumentException("Invalid file at '{$path}{$key}'");
  1534. }
  1535. }
  1536. }
  1537. /**
  1538. * Gets the body of the message.
  1539. *
  1540. * @return \Psr\Http\Message\StreamInterface Returns the body as a stream.
  1541. */
  1542. public function getBody(): StreamInterface
  1543. {
  1544. return $this->stream;
  1545. }
  1546. /**
  1547. * Return an instance with the specified message body.
  1548. *
  1549. * @param \Psr\Http\Message\StreamInterface $body The new request body
  1550. * @return static
  1551. */
  1552. public function withBody(StreamInterface $body): static
  1553. {
  1554. $new = clone $this;
  1555. $new->stream = $body;
  1556. return $new;
  1557. }
  1558. /**
  1559. * Retrieves the URI instance.
  1560. *
  1561. * @return \Psr\Http\Message\UriInterface Returns a UriInterface instance
  1562. * representing the URI of the request.
  1563. */
  1564. public function getUri(): UriInterface
  1565. {
  1566. return $this->uri;
  1567. }
  1568. /**
  1569. * Return an instance with the specified uri
  1570. *
  1571. * *Warning* Replacing the Uri will not update the `base`, `webroot`,
  1572. * and `url` attributes.
  1573. *
  1574. * @param \Psr\Http\Message\UriInterface $uri The new request uri
  1575. * @param bool $preserveHost Whether the host should be retained.
  1576. * @return static
  1577. */
  1578. public function withUri($uri, $preserveHost = false): static
  1579. {
  1580. $new = clone $this;
  1581. $new->uri = $uri;
  1582. if ($preserveHost && $this->hasHeader('Host')) {
  1583. return $new;
  1584. }
  1585. $host = $uri->getHost();
  1586. if (!$host) {
  1587. return $new;
  1588. }
  1589. $port = $uri->getPort();
  1590. if ($port) {
  1591. $host .= ':' . $port;
  1592. }
  1593. $new->_environment['HTTP_HOST'] = $host;
  1594. return $new;
  1595. }
  1596. /**
  1597. * Create a new instance with a specific request-target.
  1598. *
  1599. * You can use this method to overwrite the request target that is
  1600. * inferred from the request's Uri. This also lets you change the request
  1601. * target's form to an absolute-form, authority-form or asterisk-form
  1602. *
  1603. * @link https://tools.ietf.org/html/rfc7230#section-2.7 (for the various
  1604. * request-target forms allowed in request messages)
  1605. * @param string $requestTarget The request target.
  1606. * @return static
  1607. * @psalm-suppress MoreSpecificImplementedParamType
  1608. */
  1609. public function withRequestTarget($requestTarget): static
  1610. {
  1611. $new = clone $this;
  1612. $new->requestTarget = $requestTarget;
  1613. return $new;
  1614. }
  1615. /**
  1616. * Retrieves the request's target.
  1617. *
  1618. * Retrieves the message's request-target either as it was requested,
  1619. * or as set with `withRequestTarget()`. By default this will return the
  1620. * application relative path without base directory, and the query string
  1621. * defined in the SERVER environment.
  1622. *
  1623. * @return string
  1624. */
  1625. public function getRequestTarget(): string
  1626. {
  1627. if ($this->requestTarget !== null) {
  1628. return $this->requestTarget;
  1629. }
  1630. $target = $this->uri->getPath();
  1631. if ($this->uri->getQuery()) {
  1632. $target .= '?' . $this->uri->getQuery();
  1633. }
  1634. if (empty($target)) {
  1635. $target = '/';
  1636. }
  1637. return $target;
  1638. }
  1639. /**
  1640. * Get the path of current request.
  1641. *
  1642. * @return string
  1643. * @since 3.6.1
  1644. */
  1645. public function getPath(): string
  1646. {
  1647. if ($this->requestTarget === null) {
  1648. return $this->uri->getPath();
  1649. }
  1650. [$path] = explode('?', $this->requestTarget);
  1651. return $path;
  1652. }
  1653. }