Request.php 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  1. <?php
  2. /**
  3. * Request. Uses the [Route] class to determine what
  4. * [Controller] to send the request to.
  5. *
  6. * @package KO7
  7. * @category Base
  8. *
  9. * @copyright (c) 2007-2016 Kohana Team
  10. * @copyright (c) since 2016 Koseven Team
  11. * @license https://koseven.dev/LICENSE
  12. */
  13. class KO7_Request implements HTTP_Request {
  14. /**
  15. * @var string client user agent
  16. */
  17. public static $user_agent = '';
  18. /**
  19. * @var string client IP address
  20. */
  21. public static $client_ip = '0.0.0.0';
  22. /**
  23. * @var string trusted proxy server IPs
  24. */
  25. public static $trusted_proxies = ['127.0.0.1', 'localhost', 'localhost.localdomain'];
  26. /**
  27. * @var Request main request instance
  28. */
  29. public static $initial;
  30. /**
  31. * @var Request currently executing request instance
  32. */
  33. public static $current;
  34. /**
  35. * Creates a new request object for the given URI. New requests should be
  36. * Created using the [Request::factory] method.
  37. *
  38. * $request = Request::factory($uri);
  39. *
  40. * If $cache parameter is set, the response for the request will attempt to
  41. * be retrieved from the cache.
  42. *
  43. * @param string $uri URI of the request
  44. * @param array $client_params An array of params to pass to the request client
  45. * @param bool $allow_external Allow external requests? (deprecated in 3.3)
  46. * @param array $injected_routes An array of routes to use, for testing
  47. * @return void|Request
  48. * @throws Request_Exception
  49. * @uses Route::all
  50. * @uses Route::matches
  51. */
  52. public static function factory($uri = TRUE, $client_params = [], $allow_external = TRUE, $injected_routes = [])
  53. {
  54. // If this is the initial request
  55. if ( ! Request::$initial)
  56. {
  57. $protocol = HTTP::$protocol;
  58. if (isset($_SERVER['REQUEST_METHOD']))
  59. {
  60. // Use the server request method
  61. $method = $_SERVER['REQUEST_METHOD'];
  62. }
  63. else
  64. {
  65. // Default to GET requests
  66. $method = HTTP_Request::GET;
  67. }
  68. if (( ! empty($_SERVER['HTTPS']) AND filter_var($_SERVER['HTTPS'], FILTER_VALIDATE_BOOLEAN))
  69. OR (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
  70. AND $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
  71. AND in_array($_SERVER['REMOTE_ADDR'], Request::$trusted_proxies))
  72. {
  73. // This request is secure
  74. $secure = TRUE;
  75. }
  76. if (isset($_SERVER['HTTP_REFERER']))
  77. {
  78. // There is a referrer for this request
  79. $referrer = $_SERVER['HTTP_REFERER'];
  80. }
  81. if (isset($_SERVER['HTTP_USER_AGENT']))
  82. {
  83. // Browser type
  84. Request::$user_agent = $_SERVER['HTTP_USER_AGENT'];
  85. }
  86. if (isset($_SERVER['HTTP_X_REQUESTED_WITH']))
  87. {
  88. // Typically used to denote AJAX requests
  89. $requested_with = $_SERVER['HTTP_X_REQUESTED_WITH'];
  90. }
  91. if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])
  92. AND isset($_SERVER['REMOTE_ADDR'])
  93. AND in_array($_SERVER['REMOTE_ADDR'], Request::$trusted_proxies)) {
  94. // If using CloudFlare, client IP address is sent with this header
  95. Request::$client_ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
  96. }
  97. elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])
  98. AND isset($_SERVER['REMOTE_ADDR'])
  99. AND in_array($_SERVER['REMOTE_ADDR'], Request::$trusted_proxies))
  100. {
  101. // Use the forwarded IP address, typically set when the
  102. // client is using a proxy server.
  103. // Format: "X-Forwarded-For: client1, proxy1, proxy2"
  104. $client_ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
  105. Request::$client_ip = array_shift($client_ips);
  106. unset($client_ips);
  107. }
  108. elseif (isset($_SERVER['HTTP_CLIENT_IP'])
  109. AND isset($_SERVER['REMOTE_ADDR'])
  110. AND in_array($_SERVER['REMOTE_ADDR'], Request::$trusted_proxies))
  111. {
  112. // Use the forwarded IP address, typically set when the
  113. // client is using a proxy server.
  114. $client_ips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
  115. Request::$client_ip = trim(end($client_ips));
  116. unset($client_ips);
  117. }
  118. elseif (isset($_SERVER['REMOTE_ADDR']))
  119. {
  120. // The remote IP address
  121. Request::$client_ip = $_SERVER['REMOTE_ADDR'];
  122. }
  123. if ($method !== HTTP_Request::GET)
  124. {
  125. // Ensure the raw body is saved for future use
  126. $body = file_get_contents('php://input');
  127. }
  128. if ($uri === TRUE)
  129. {
  130. // Attempt to guess the proper URI
  131. $uri = Request::detect_uri();
  132. }
  133. $cookies = [];
  134. if (($cookie_keys = array_keys($_COOKIE)))
  135. {
  136. foreach ($cookie_keys as $key)
  137. {
  138. $cookies[$key] = Cookie::get($key);
  139. }
  140. }
  141. // Create the instance singleton
  142. Request::$initial = $request = new Request($uri, $client_params, $allow_external, $injected_routes);
  143. // Store global GET and POST data in the initial request only
  144. $request->protocol($protocol)
  145. ->query($_GET)
  146. ->post($_POST);
  147. if (isset($secure))
  148. {
  149. // Set the request security
  150. $request->secure($secure);
  151. }
  152. if (isset($method))
  153. {
  154. // Set the request method
  155. $request->method($method);
  156. }
  157. if (isset($referrer))
  158. {
  159. // Set the referrer
  160. $request->referrer($referrer);
  161. }
  162. if (isset($requested_with))
  163. {
  164. // Apply the requested with variable
  165. $request->requested_with($requested_with);
  166. }
  167. if (isset($body))
  168. {
  169. // Set the request body (probably a PUT type)
  170. $request->body($body);
  171. }
  172. if (isset($cookies))
  173. {
  174. $request->cookie($cookies);
  175. }
  176. }
  177. else
  178. {
  179. $request = new Request($uri, $client_params, $allow_external, $injected_routes);
  180. }
  181. return $request;
  182. }
  183. /**
  184. * Automatically detects the URI of the main request using PATH_INFO,
  185. * REQUEST_URI, PHP_SELF or REDIRECT_URL.
  186. *
  187. * $uri = Request::detect_uri();
  188. *
  189. * @return string URI of the main request
  190. * @throws KO7_Exception
  191. * @since 3.0.8
  192. */
  193. public static function detect_uri()
  194. {
  195. if ( ! empty($_SERVER['PATH_INFO']))
  196. {
  197. // PATH_INFO does not contain the docroot or index
  198. $uri = $_SERVER['PATH_INFO'];
  199. }
  200. else
  201. {
  202. // REQUEST_URI and PHP_SELF include the docroot and index
  203. if (isset($_SERVER['REQUEST_URI']))
  204. {
  205. /**
  206. * We use REQUEST_URI as the fallback value. The reason
  207. * for this is we might have a malformed URL such as:
  208. *
  209. * http://localhost/http://example.com/judge.php
  210. *
  211. * which parse_url can't handle. So rather than leave empty
  212. * handed, we'll use this.
  213. */
  214. $uri = $_SERVER['REQUEST_URI'];
  215. if ($request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))
  216. {
  217. // Valid URL path found, set it.
  218. $uri = $request_uri;
  219. }
  220. // Decode the request URI
  221. $uri = rawurldecode($uri);
  222. }
  223. elseif (isset($_SERVER['PHP_SELF']))
  224. {
  225. $uri = $_SERVER['PHP_SELF'];
  226. }
  227. elseif (isset($_SERVER['REDIRECT_URL']))
  228. {
  229. $uri = $_SERVER['REDIRECT_URL'];
  230. }
  231. else
  232. {
  233. // If you ever see this error, please report an issue at http://koseven.dev/projects/KO7/issues
  234. // along with any relevant information about your web server setup. Thanks!
  235. throw new KO7_Exception('Unable to detect the URI using PATH_INFO, REQUEST_URI, PHP_SELF or REDIRECT_URL');
  236. }
  237. // Get the path from the base URL, including the index file
  238. $base_url = parse_url(KO7::$base_url, PHP_URL_PATH);
  239. if (strpos($uri, $base_url) === 0)
  240. {
  241. // Remove the base URL from the URI
  242. $uri = (string) substr($uri, strlen($base_url));
  243. }
  244. if (KO7::$index_file AND strpos($uri, KO7::$index_file) === 0)
  245. {
  246. // Remove the index file from the URI
  247. $uri = (string) substr($uri, strlen(KO7::$index_file));
  248. }
  249. }
  250. return $uri;
  251. }
  252. /**
  253. * Return the currently executing request. This is changed to the current
  254. * request when [Request::execute] is called and restored when the request
  255. * is completed.
  256. *
  257. * $request = Request::current();
  258. *
  259. * @return Request
  260. * @since 3.0.5
  261. */
  262. public static function current()
  263. {
  264. return Request::$current;
  265. }
  266. /**
  267. * Returns the first request encountered by this framework. This will should
  268. * only be set once during the first [Request::factory] invocation.
  269. *
  270. * // Get the first request
  271. * $request = Request::initial();
  272. *
  273. * // Test whether the current request is the first request
  274. * if (Request::initial() === Request::current())
  275. * // Do something useful
  276. *
  277. * @return Request
  278. * @since 3.1.0
  279. */
  280. public static function initial()
  281. {
  282. return Request::$initial;
  283. }
  284. /**
  285. * Returns information about the initial user agent.
  286. *
  287. * @param mixed $value array or string to return: browser, version, robot, mobile, platform
  288. * @return mixed requested information, FALSE if nothing is found
  289. * @uses Request::$user_agent
  290. * @uses Text::user_agent
  291. */
  292. public static function user_agent($value)
  293. {
  294. return Text::user_agent(Request::$user_agent, $value);
  295. }
  296. /**
  297. * Determines if a file larger than the post_max_size has been uploaded. PHP
  298. * does not handle this situation gracefully on its own, so this method
  299. * helps to solve that problem.
  300. *
  301. * @return boolean
  302. * @uses Num::bytes
  303. * @uses Arr::get
  304. */
  305. public static function post_max_size_exceeded()
  306. {
  307. // Make sure the request method is POST
  308. if (Request::$initial->method() !== HTTP_Request::POST)
  309. return FALSE;
  310. // Get the post_max_size in bytes
  311. $max_bytes = Num::bytes(ini_get('post_max_size'));
  312. // Error occurred if method is POST, and content length is too long
  313. return (Arr::get($_SERVER, 'CONTENT_LENGTH') > $max_bytes);
  314. }
  315. /**
  316. * Process a request to find a matching route
  317. *
  318. * @param object $request Request
  319. * @param array $routes Route
  320. * @return array
  321. */
  322. public static function process(Request $request, $routes = NULL)
  323. {
  324. // Load routes
  325. $routes = (empty($routes)) ? Route::all() : $routes;
  326. $params = NULL;
  327. foreach ($routes as $route)
  328. {
  329. // Use external routes for reverse routing only
  330. if ($route->is_external())
  331. {
  332. continue;
  333. }
  334. // We found something suitable
  335. if ($params = $route->matches($request))
  336. {
  337. return [
  338. 'params' => $params,
  339. 'route' => $route,
  340. ];
  341. }
  342. }
  343. return NULL;
  344. }
  345. /**
  346. * Parses an accept header and returns an array (type => quality) of the
  347. * accepted types, ordered by quality.
  348. *
  349. * $accept = Request::_parse_accept($header, $defaults);
  350. *
  351. * @param string $header Header to parse
  352. * @param array $accepts Default values
  353. * @return array
  354. */
  355. protected static function _parse_accept( & $header, array $accepts = NULL)
  356. {
  357. if ( ! empty($header))
  358. {
  359. // Get all of the types
  360. $types = explode(',', $header);
  361. foreach ($types as $type)
  362. {
  363. // Split the type into parts
  364. $parts = explode(';', $type);
  365. // Make the type only the MIME
  366. $type = trim(array_shift($parts));
  367. // Default quality is 1.0
  368. $quality = 1.0;
  369. foreach ($parts as $part)
  370. {
  371. // Prevent undefined $value notice below
  372. if (strpos($part, '=') === FALSE)
  373. continue;
  374. // Separate the key and value
  375. list ($key, $value) = explode('=', trim($part));
  376. if ($key === 'q')
  377. {
  378. // There is a quality for this type
  379. $quality = (float) trim($value);
  380. }
  381. }
  382. // Add the accept type and quality
  383. $accepts[$type] = $quality;
  384. }
  385. }
  386. // Make sure that accepts is an array
  387. $accepts = (array) $accepts;
  388. // Order by quality
  389. arsort($accepts);
  390. return $accepts;
  391. }
  392. /**
  393. * @var string the x-requested-with header which most likely
  394. * will be xmlhttprequest
  395. */
  396. protected $_requested_with;
  397. /**
  398. * @var string method: GET, POST, PUT, DELETE, HEAD, etc
  399. */
  400. protected $_method = 'GET';
  401. /**
  402. * @var string protocol: HTTP/1.1, FTP, CLI, etc
  403. */
  404. protected $_protocol;
  405. /**
  406. * @var boolean
  407. */
  408. protected $_secure = FALSE;
  409. /**
  410. * @var string referring URL
  411. */
  412. protected $_referrer;
  413. /**
  414. * @var Route route matched for this request
  415. */
  416. protected $_route;
  417. /**
  418. * @var Route array of routes to manually look at instead of the global namespace
  419. */
  420. protected $_routes;
  421. /**
  422. * @var KO7_HTTP_Header headers to sent as part of the request
  423. */
  424. protected $_header;
  425. /**
  426. * @var string the body
  427. */
  428. protected $_body;
  429. /**
  430. * @var string controller directory
  431. */
  432. protected $_directory = '';
  433. /**
  434. * @var string controller to be executed
  435. */
  436. protected $_controller;
  437. /**
  438. * Requested Format (json, xml, html)
  439. * @var string
  440. */
  441. protected $_format;
  442. /**
  443. * @var string action to be executed in the controller
  444. */
  445. protected $_action;
  446. /**
  447. * @var string the URI of the request
  448. */
  449. protected $_uri;
  450. /**
  451. * @var boolean external request
  452. */
  453. protected $_external = FALSE;
  454. /**
  455. * @var array parameters from the route
  456. */
  457. protected $_params = [];
  458. /**
  459. * @var array query parameters
  460. */
  461. protected $_get = [];
  462. /**
  463. * @var array post parameters
  464. */
  465. protected $_post = [];
  466. /**
  467. * @var array cookies to send with the request
  468. */
  469. protected $_cookies = [];
  470. /**
  471. * @var KO7_Request_Client
  472. */
  473. protected $_client;
  474. /**
  475. * Creates a new request object for the given URI. New requests should be
  476. * Created using the [Request::factory] method.
  477. *
  478. * $request = new Request($uri);
  479. *
  480. * If $cache parameter is set, the response for the request will attempt to
  481. * be retrieved from the cache.
  482. *
  483. * @param string $uri URI of the request
  484. * @param array $client_params Array of params to pass to the request client
  485. * @param bool $allow_external Allow external requests? (deprecated in 3.3)
  486. * @param array $injected_routes An array of routes to use, for testing
  487. * @return void
  488. * @throws Request_Exception
  489. * @uses Route::all
  490. * @uses Route::matches
  491. */
  492. public function __construct($uri, $client_params = [], $allow_external = TRUE, $injected_routes = [])
  493. {
  494. $client_params = is_array($client_params) ? $client_params : [];
  495. // Initialise the header
  496. $this->_header = new HTTP_Header([]);
  497. // Assign injected routes
  498. $this->_routes = $injected_routes;
  499. // Cleanse query parameters from URI (faster that parse_url())
  500. $split_uri = explode('?', $uri);
  501. $uri = array_shift($split_uri);
  502. if ($split_uri)
  503. {
  504. parse_str($split_uri[0], $this->_get);
  505. }
  506. // Detect protocol (if present)
  507. // $allow_external = FALSE prevents the default index.php from
  508. // being able to proxy external pages.
  509. if ( ! $allow_external OR (strpos($uri, '://') === FALSE AND strncmp($uri, '//', 2)))
  510. {
  511. // Remove leading and trailing slashes from the URI
  512. $this->_uri = trim($uri, '/');
  513. // Apply the client
  514. $this->_client = new Request_Client_Internal($client_params);
  515. }
  516. else
  517. {
  518. // Create a route
  519. $this->_route = new Route($uri);
  520. // Store the URI
  521. $this->_uri = $uri;
  522. // Set the security setting if required
  523. if (strpos($uri, 'https://') === 0)
  524. {
  525. $this->secure(TRUE);
  526. }
  527. // Set external state
  528. $this->_external = TRUE;
  529. // Setup the client
  530. $this->_client = Request_Client_External::factory($client_params);
  531. }
  532. }
  533. /**
  534. * Returns the response as the string representation of a request.
  535. *
  536. * echo $request;
  537. *
  538. * @return string
  539. */
  540. public function __toString()
  541. {
  542. return $this->render();
  543. }
  544. /**
  545. * Sets and gets the uri from the request.
  546. *
  547. * @param string $uri
  548. * @return mixed
  549. */
  550. public function uri($uri = NULL)
  551. {
  552. if ($uri === NULL)
  553. {
  554. // Act as a getter
  555. return ($this->_uri === '') ? '/' : $this->_uri;
  556. }
  557. // Act as a setter
  558. $this->_uri = $uri;
  559. return $this;
  560. }
  561. /**
  562. * Create a URL string from the current request. This is a shortcut for:
  563. *
  564. * echo URL::site($this->request->uri(), $protocol);
  565. *
  566. * @param mixed $protocol protocol string or Request object
  567. * @return string
  568. * @since 3.0.7
  569. * @uses URL::site
  570. */
  571. public function url($protocol = NULL)
  572. {
  573. if ($this->is_external())
  574. {
  575. // If it's an external request return the URI
  576. return $this->uri();
  577. }
  578. // Create a URI with the current route, convert to a URL and returns
  579. return URL::site($this->uri(), $protocol);
  580. }
  581. /**
  582. * Retrieves a value from the route parameters.
  583. *
  584. * $id = $request->param('id');
  585. *
  586. * @param string $key Key of the value
  587. * @param mixed $default Default value if the key is not set
  588. * @return mixed
  589. */
  590. public function param($key = NULL, $default = NULL)
  591. {
  592. if ($key === NULL)
  593. {
  594. // Return the full array
  595. return $this->_params;
  596. }
  597. return isset($this->_params[$key]) ? $this->_params[$key] : $default;
  598. }
  599. /**
  600. * Get / Set requested format
  601. *
  602. * @param string|null $format e.g JSON, XML, etc...
  603. *
  604. * @return $this|string
  605. */
  606. public function format(?string $format = NULL)
  607. {
  608. if ($format === NULL)
  609. {
  610. return $this->_format;
  611. }
  612. $this->_format = $format;
  613. return $this;
  614. }
  615. /**
  616. * Sets and gets the referrer from the request.
  617. *
  618. * @param string $referrer
  619. * @return mixed
  620. */
  621. public function referrer($referrer = NULL)
  622. {
  623. if ($referrer === NULL)
  624. {
  625. // Act as a getter
  626. return $this->_referrer;
  627. }
  628. // Act as a setter
  629. $this->_referrer = (string) $referrer;
  630. return $this;
  631. }
  632. /**
  633. * Sets and gets the route from the request.
  634. *
  635. * @param string $route
  636. * @return mixed
  637. */
  638. public function route(Route $route = NULL)
  639. {
  640. if ($route === NULL)
  641. {
  642. // Act as a getter
  643. return $this->_route;
  644. }
  645. // Act as a setter
  646. $this->_route = $route;
  647. return $this;
  648. }
  649. /**
  650. * Sets and gets the directory for the controller.
  651. *
  652. * @param string $directory Directory to execute the controller from
  653. * @return mixed
  654. */
  655. public function directory($directory = NULL)
  656. {
  657. if ($directory === NULL)
  658. {
  659. // Act as a getter
  660. return $this->_directory;
  661. }
  662. // Act as a setter
  663. $this->_directory = (string) $directory;
  664. return $this;
  665. }
  666. /**
  667. * Sets and gets the controller for the matched route.
  668. *
  669. * @param string $controller Controller to execute the action
  670. * @return mixed
  671. */
  672. public function controller($controller = NULL)
  673. {
  674. if ($controller === NULL)
  675. {
  676. // Act as a getter
  677. return $this->_controller;
  678. }
  679. // Act as a setter
  680. $this->_controller = (string) $controller;
  681. return $this;
  682. }
  683. /**
  684. * Sets and gets the action for the controller.
  685. *
  686. * @param string $action Action to execute the controller from
  687. * @return mixed
  688. */
  689. public function action($action = NULL)
  690. {
  691. if ($action === NULL)
  692. {
  693. // Act as a getter
  694. return $this->_action;
  695. }
  696. // Act as a setter
  697. $this->_action = (string) $action;
  698. return $this;
  699. }
  700. /**
  701. * Provides access to the [Request_Client].
  702. *
  703. * @return Request_Client
  704. * @return self
  705. */
  706. public function client(Request_Client $client = NULL)
  707. {
  708. if ($client === NULL)
  709. return $this->_client;
  710. else
  711. {
  712. $this->_client = $client;
  713. return $this;
  714. }
  715. }
  716. /**
  717. * Gets and sets the requested with property, which should
  718. * be relative to the x-requested-with pseudo header.
  719. *
  720. * @param string $requested_with Requested with value
  721. * @return mixed
  722. */
  723. public function requested_with($requested_with = NULL)
  724. {
  725. if ($requested_with === NULL)
  726. {
  727. // Act as a getter
  728. return $this->_requested_with;
  729. }
  730. // Act as a setter
  731. $this->_requested_with = strtolower($requested_with);
  732. return $this;
  733. }
  734. /**
  735. * Processes the request, executing the controller action that handles this
  736. * request, determined by the [Route].
  737. *
  738. * 1. Before the controller action is called, the [Controller::before] method
  739. * will be called.
  740. * 2. Next the controller action will be called.
  741. * 3. After the controller action is called, the [Controller::after] method
  742. * will be called.
  743. *
  744. * By default, the output from the controller is captured and returned, and
  745. * no headers are sent.
  746. *
  747. * $request->execute();
  748. *
  749. * @return Response
  750. * @throws Request_Exception
  751. * @throws HTTP_Exception_404
  752. * @uses [KO7::$profiling]
  753. * @uses [Profiler]
  754. */
  755. public function execute()
  756. {
  757. if ( ! $this->_external)
  758. {
  759. $processed = Request::process($this, $this->_routes);
  760. if ($processed)
  761. {
  762. // Store the matching route
  763. $this->_route = $processed['route'];
  764. $params = $processed['params'];
  765. // Is this route external?
  766. $this->_external = $this->_route->is_external();
  767. if (isset($params['directory']))
  768. {
  769. // Controllers are in a sub-directory
  770. $this->_directory = $params['directory'];
  771. }
  772. // Requested format e.g XML, JSON, etc..
  773. if (isset($params['format']))
  774. {
  775. $this->_format = $params['format'];
  776. }
  777. // Store the controller
  778. $this->_controller = $params['controller'];
  779. // Store the action
  780. $this->_action = (isset($params['action']))
  781. ? $params['action']
  782. : Route::$default_action;
  783. // These are accessible as public vars and can be overloaded
  784. unset($params['controller'], $params['action'], $params['directory']);
  785. // Params cannot be changed once matched
  786. $this->_params = $params;
  787. }
  788. }
  789. if ( ! $this->_route instanceof Route)
  790. {
  791. return HTTP_Exception::factory(404, 'Unable to find a route to match the URI: :uri', [
  792. ':uri' => $this->_uri,
  793. ])->request($this)
  794. ->get_response();
  795. }
  796. if ( ! $this->_client instanceof Request_Client)
  797. {
  798. throw new Request_Exception('Unable to execute :uri without a KO7_Request_Client', [
  799. ':uri' => $this->_uri,
  800. ]);
  801. }
  802. return $this->_client->execute($this);
  803. }
  804. /**
  805. * Returns whether this request is the initial request KO7 received.
  806. * Can be used to test for sub requests.
  807. *
  808. * if ( ! $request->is_initial())
  809. * // This is a sub request
  810. *
  811. * @return boolean
  812. */
  813. public function is_initial()
  814. {
  815. return ($this === Request::$initial);
  816. }
  817. /**
  818. * Readonly access to the [Request::$_external] property.
  819. *
  820. * if ( ! $request->is_external())
  821. * // This is an internal request
  822. *
  823. * @return boolean
  824. */
  825. public function is_external()
  826. {
  827. return $this->_external;
  828. }
  829. /**
  830. * Returns whether this is an ajax request (as used by JS frameworks)
  831. *
  832. * @return boolean
  833. */
  834. public function is_ajax()
  835. {
  836. return ($this->requested_with() === 'xmlhttprequest');
  837. }
  838. /**
  839. * Gets or sets the HTTP method. Usually GET, POST, PUT or DELETE in
  840. * traditional CRUD applications.
  841. *
  842. * @param string $method Method to use for this request
  843. * @return mixed
  844. */
  845. public function method($method = NULL)
  846. {
  847. if ($method === NULL)
  848. {
  849. // Act as a getter
  850. return $this->_method;
  851. }
  852. // Method is always uppercase
  853. $method = strtoupper($method);
  854. // Allow overriding method
  855. $override = $this->headers('X-HTTP-Method-Override');
  856. // Act as a setter
  857. $this->_method = $override && defined('HTTP_REQUEST::' . $override) ? $override : $method;
  858. return $this;
  859. }
  860. /**
  861. * Gets or sets the HTTP protocol. If there is no current protocol set,
  862. * it will use the default set in HTTP::$protocol
  863. *
  864. * @param string $protocol Protocol to set to the request
  865. * @return mixed
  866. */
  867. public function protocol($protocol = NULL)
  868. {
  869. if ($protocol === NULL)
  870. {
  871. if ($this->_protocol)
  872. return $this->_protocol;
  873. else
  874. return $this->_protocol = HTTP::$protocol;
  875. }
  876. // Act as a setter
  877. $this->_protocol = strtoupper($protocol);
  878. return $this;
  879. }
  880. /**
  881. * Getter/Setter to the security settings for this request. This
  882. * method should be treated as immutable.
  883. *
  884. * @param boolean $secure is this request secure?
  885. * @return mixed
  886. */
  887. public function secure($secure = NULL)
  888. {
  889. if ($secure === NULL)
  890. return $this->_secure;
  891. // Act as a setter
  892. $this->_secure = (bool) $secure;
  893. return $this;
  894. }
  895. /**
  896. * Gets or sets HTTP headers oo the request. All headers
  897. * are included immediately after the HTTP protocol definition during
  898. * transmission. This method provides a simple array or key/value
  899. * interface to the headers.
  900. *
  901. * @param mixed $key Key or array of key/value pairs to set
  902. * @param string $value Value to set to the supplied key
  903. * @return mixed
  904. */
  905. public function headers($key = NULL, $value = NULL)
  906. {
  907. if ($key instanceof HTTP_Header)
  908. {
  909. // Act a setter, replace all headers
  910. $this->_header = $key;
  911. return $this;
  912. }
  913. if (is_array($key))
  914. {
  915. // Act as a setter, replace all headers
  916. $this->_header->exchangeArray($key);
  917. return $this;
  918. }
  919. if ($this->_header->count() === 0 AND $this->is_initial())
  920. {
  921. // Lazy load the request headers
  922. $this->_header = HTTP::request_headers();
  923. }
  924. if ($key === NULL)
  925. {
  926. // Act as a getter, return all headers
  927. return $this->_header;
  928. }
  929. elseif ($value === NULL)
  930. {
  931. // Act as a getter, single header
  932. return ($this->_header->offsetExists($key)) ? $this->_header->offsetGet($key) : NULL;
  933. }
  934. // Act as a setter for a single header
  935. $this->_header[$key] = $value;
  936. return $this;
  937. }
  938. /**
  939. * Set and get cookies values for this request.
  940. *
  941. * @param mixed $key Cookie name, or array of cookie values
  942. * @param string $value Value to set to cookie
  943. * @return string
  944. * @return mixed
  945. */
  946. public function cookie($key = NULL, $value = NULL)
  947. {
  948. if (is_array($key))
  949. {
  950. // Act as a setter, replace all cookies
  951. $this->_cookies = $key;
  952. return $this;
  953. }
  954. elseif ($key === NULL)
  955. {
  956. // Act as a getter, all cookies
  957. return $this->_cookies;
  958. }
  959. elseif ($value === NULL)
  960. {
  961. // Act as a getting, single cookie
  962. return isset($this->_cookies[$key]) ? $this->_cookies[$key] : NULL;
  963. }
  964. // Act as a setter for a single cookie
  965. $this->_cookies[$key] = (string) $value;
  966. return $this;
  967. }
  968. /**
  969. * Gets or sets the HTTP body of the request. The body is
  970. * included after the header, separated by a single empty new line.
  971. *
  972. * @param string $content Content to set to the object
  973. * @return mixed
  974. */
  975. public function body($content = NULL)
  976. {
  977. if ($content === NULL)
  978. {
  979. // Act as a getter
  980. return $this->_body;
  981. }
  982. // Act as a setter
  983. $this->_body = $content;
  984. return $this;
  985. }
  986. /**
  987. * Returns the length of the body for use with
  988. * content header
  989. *
  990. * @return integer
  991. */
  992. public function content_length()
  993. {
  994. return strlen($this->body());
  995. }
  996. /**
  997. * Renders the HTTP_Interaction to a string, producing
  998. *
  999. * - Protocol
  1000. * - Headers
  1001. * - Body
  1002. *
  1003. * If there are variables set to the `KO7_Request::$_post`
  1004. * they will override any values set to body.
  1005. *
  1006. * @return string
  1007. */
  1008. public function render()
  1009. {
  1010. if ( ! $post = $this->post())
  1011. {
  1012. $body = $this->body();
  1013. }
  1014. else
  1015. {
  1016. $body = http_build_query($post, NULL, '&');
  1017. $this->body($body)
  1018. ->headers('content-type', 'application/x-www-form-urlencoded; charset='.KO7::$charset);
  1019. }
  1020. // Set the content length
  1021. $this->headers('content-length', (string) $this->content_length());
  1022. // If KO7 expose, set the user-agent
  1023. if (KO7::$expose)
  1024. {
  1025. $this->headers('user-agent', KO7::version());
  1026. }
  1027. // Prepare cookies
  1028. if ($this->_cookies)
  1029. {
  1030. $cookie_string = [];
  1031. // Parse each
  1032. foreach ($this->_cookies as $key => $value)
  1033. {
  1034. $cookie_string[] = $key.'='.$value;
  1035. }
  1036. // Create the cookie string
  1037. $this->_header['cookie'] = implode('; ', $cookie_string);
  1038. }
  1039. $output = $this->method().' '.$this->uri().' '.$this->protocol()."\r\n";
  1040. $output .= (string) $this->_header;
  1041. $output .= $body;
  1042. return $output;
  1043. }
  1044. /**
  1045. * Gets or sets HTTP query string.
  1046. *
  1047. * @param mixed $key Key or key value pairs to set
  1048. * @param string $value Value to set to a key
  1049. * @return mixed
  1050. * @uses Arr::path
  1051. */
  1052. public function query($key = NULL, $value = NULL)
  1053. {
  1054. if (is_array($key))
  1055. {
  1056. // Act as a setter, replace all query strings
  1057. $this->_get = $key;
  1058. return $this;
  1059. }
  1060. if ($key === NULL)
  1061. {
  1062. // Act as a getter, all query strings
  1063. return $this->_get;
  1064. }
  1065. elseif ($value === NULL)
  1066. {
  1067. // Act as a getter, single query string
  1068. return Arr::path($this->_get, $key);
  1069. }
  1070. // Act as a setter, single query string
  1071. $this->_get[$key] = $value;
  1072. return $this;
  1073. }
  1074. /**
  1075. * Gets or sets HTTP POST parameters to the request.
  1076. *
  1077. * @param mixed $key Key or key value pairs to set
  1078. * @param string $value Value to set to a key
  1079. * @return mixed
  1080. * @uses Arr::path
  1081. */
  1082. public function post($key = NULL, $value = NULL)
  1083. {
  1084. if (is_array($key))
  1085. {
  1086. // Act as a setter, replace all fields
  1087. $this->_post = $key;
  1088. return $this;
  1089. }
  1090. if ($key === NULL)
  1091. {
  1092. // Act as a getter, all fields
  1093. return $this->_post;
  1094. }
  1095. elseif ($value === NULL)
  1096. {
  1097. // Act as a getter, single field
  1098. return Arr::path($this->_post, $key);
  1099. }
  1100. // Act as a setter, single field
  1101. $this->_post[$key] = $value;
  1102. return $this;
  1103. }
  1104. }