Request.php 30 KB

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