1 <?php
defined('SYSPATH') OR die('No direct script access.');
3 * Request. Uses the [Route] class to determine what
4 * [Controller] to send the request to.
9 * @copyright (c) 2008-2012 Kohana Team
10 * @license http://kohanaframework.org/license
12 class Kohana_Request
implements HTTP_Request
{
15 * @var string client user agent
17 public static $user_agent = '';
20 * @var string client IP address
22 public static $client_ip = '0.0.0.0';
25 * @var string trusted proxy server IPs
27 public static $trusted_proxies = array('127.0.0.1', 'localhost', 'localhost.localdomain');
30 * @var Request main request instance
32 public static $initial;
35 * @var Request currently executing request instance
37 public static $current;
40 * Creates a new request object for the given URI. New requests should be
41 * Created using the [Request::factory] method.
43 * $request = Request::factory($uri);
45 * If $cache parameter is set, the response for the request will attempt to
46 * be retrieved from the cache.
48 * @param string $uri URI of the request
49 * @param array $client_params An array of params to pass to the request client
50 * @param bool $allow_external Allow external requests? (deprecated in 3.3)
51 * @param array $injected_routes An array of routes to use, for testing
52 * @return void|Request
53 * @throws Request_Exception
55 * @uses Route::matches
57 public static function factory($uri = TRUE, $client_params = array(), $allow_external = TRUE, $injected_routes = array())
59 // If this is the initial request
60 if ( ! Request
::$initial)
62 $protocol = HTTP
::$protocol;
64 if (isset($_SERVER['REQUEST_METHOD']))
66 // Use the server request method
67 $method = $_SERVER['REQUEST_METHOD'];
71 // Default to GET requests
72 $method = HTTP_Request
::GET
;
75 if (( ! empty($_SERVER['HTTPS']) AND filter_var($_SERVER['HTTPS'], FILTER_VALIDATE_BOOLEAN
))
76 OR (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
77 AND $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
78 AND in_array($_SERVER['REMOTE_ADDR'], Request
::$trusted_proxies))
80 // This request is secure
84 if (isset($_SERVER['HTTP_REFERER']))
86 // There is a referrer for this request
87 $referrer = $_SERVER['HTTP_REFERER'];
90 if (isset($_SERVER['HTTP_USER_AGENT']))
93 Request
::$user_agent = $_SERVER['HTTP_USER_AGENT'];
96 if (isset($_SERVER['HTTP_X_REQUESTED_WITH']))
98 // Typically used to denote AJAX requests
99 $requested_with = $_SERVER['HTTP_X_REQUESTED_WITH'];
102 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])
103 AND isset($_SERVER['REMOTE_ADDR'])
104 AND in_array($_SERVER['REMOTE_ADDR'], Request
::$trusted_proxies))
106 // Use the forwarded IP address, typically set when the
107 // client is using a proxy server.
108 // Format: "X-Forwarded-For: client1, proxy1, proxy2"
109 $client_ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
111 Request
::$client_ip = array_shift($client_ips);
115 elseif (isset($_SERVER['HTTP_CLIENT_IP'])
116 AND isset($_SERVER['REMOTE_ADDR'])
117 AND in_array($_SERVER['REMOTE_ADDR'], Request
::$trusted_proxies))
119 // Use the forwarded IP address, typically set when the
120 // client is using a proxy server.
121 $client_ips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
123 Request
::$client_ip = array_shift($client_ips);
127 elseif (isset($_SERVER['REMOTE_ADDR']))
129 // The remote IP address
130 Request
::$client_ip = $_SERVER['REMOTE_ADDR'];
133 if ($method !== HTTP_Request
::GET
)
135 // Ensure the raw body is saved for future use
136 $body = file_get_contents('php://input');
141 // Attempt to guess the proper URI
142 $uri = Request
::detect_uri();
147 if (($cookie_keys = array_keys($_COOKIE)))
149 foreach ($cookie_keys as $key)
151 $cookies[$key] = Cookie
::get($key);
155 // Create the instance singleton
156 Request
::$initial = $request = new Request($uri, $client_params, $allow_external, $injected_routes);
158 // Store global GET and POST data in the initial request only
159 $request->protocol($protocol)
165 // Set the request security
166 $request->secure($secure);
171 // Set the request method
172 $request->method($method);
175 if (isset($referrer))
178 $request->referrer($referrer);
181 if (isset($requested_with))
183 // Apply the requested with variable
184 $request->requested_with($requested_with);
189 // Set the request body (probably a PUT type)
190 $request->body($body);
195 $request->cookie($cookies);
200 $request = new Request($uri, $client_params, $allow_external, $injected_routes);
207 * Automatically detects the URI of the main request using PATH_INFO,
208 * REQUEST_URI, PHP_SELF or REDIRECT_URL.
210 * $uri = Request::detect_uri();
212 * @return string URI of the main request
213 * @throws Kohana_Exception
216 public static function detect_uri()
218 if ( ! empty($_SERVER['PATH_INFO']))
220 // PATH_INFO does not contain the docroot or index
221 $uri = $_SERVER['PATH_INFO'];
225 // REQUEST_URI and PHP_SELF include the docroot and index
227 if (isset($_SERVER['REQUEST_URI']))
230 * We use REQUEST_URI as the fallback value. The reason
231 * for this is we might have a malformed URL such as:
233 * http://localhost/http://example.com/judge.php
235 * which parse_url can't handle. So rather than leave empty
236 * handed, we'll use this.
238 $uri = $_SERVER['REQUEST_URI'];
240 if ($request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH
))
242 // Valid URL path found, set it.
246 // Decode the request URI
247 $uri = rawurldecode($uri);
249 elseif (isset($_SERVER['PHP_SELF']))
251 $uri = $_SERVER['PHP_SELF'];
253 elseif (isset($_SERVER['REDIRECT_URL']))
255 $uri = $_SERVER['REDIRECT_URL'];
259 // If you ever see this error, please report an issue at http://dev.kohanaphp.com/projects/kohana3/issues
260 // along with any relevant information about your web server setup. Thanks!
261 throw new Kohana_Exception('Unable to detect the URI using PATH_INFO, REQUEST_URI, PHP_SELF or REDIRECT_URL');
264 // Get the path from the base URL, including the index file
265 $base_url = parse_url(Kohana
::$base_url, PHP_URL_PATH
);
267 if (strpos($uri, $base_url) === 0)
269 // Remove the base URL from the URI
270 $uri = (string) substr($uri, strlen($base_url));
273 if (Kohana
::$index_file AND strpos($uri, Kohana
::$index_file) === 0)
275 // Remove the index file from the URI
276 $uri = (string) substr($uri, strlen(Kohana
::$index_file));
284 * Return the currently executing request. This is changed to the current
285 * request when [Request::execute] is called and restored when the request
288 * $request = Request::current();
293 public static function current()
295 return Request
::$current;
299 * Returns the first request encountered by this framework. This will should
300 * only be set once during the first [Request::factory] invocation.
302 * // Get the first request
303 * $request = Request::initial();
305 * // Test whether the current request is the first request
306 * if (Request::initial() === Request::current())
307 * // Do something useful
312 public static function initial()
314 return Request
::$initial;
318 * Returns information about the initial user agent.
320 * @param mixed $value array or string to return: browser, version, robot, mobile, platform
321 * @return mixed requested information, FALSE if nothing is found
322 * @uses Request::$user_agent
323 * @uses Text::user_agent
325 public static function user_agent($value)
327 return Text
::user_agent(Request
::$user_agent, $value);
331 * Returns the accepted content types. If a specific type is defined,
332 * the quality of that type will be returned.
334 * $types = Request::accept_type();
336 * [!!] Deprecated in favor of using [HTTP_Header::accepts_at_quality].
338 * @deprecated since version 3.3.0
339 * @param string $type Content MIME type
340 * @return mixed An array of all types or a specific type as a string
341 * @uses Request::_parse_accept
343 public static function accept_type($type = NULL)
347 if ($accepts === NULL)
349 // Parse the HTTP_ACCEPT header
350 $accepts = Request
::_parse_accept($_SERVER['HTTP_ACCEPT'], array('*/*' => 1.0));
355 // Return the quality setting for this type
356 return isset($accepts[$type]) ?
$accepts[$type] : $accepts['*/*'];
363 * Returns the accepted languages. If a specific language is defined,
364 * the quality of that language will be returned. If the language is not
365 * accepted, FALSE will be returned.
367 * $langs = Request::accept_lang();
369 * [!!] Deprecated in favor of using [HTTP_Header::accepts_language_at_quality].
371 * @deprecated since version 3.3.0
372 * @param string $lang Language code
373 * @return mixed An array of all types or a specific type as a string
374 * @uses Request::_parse_accept
376 public static function accept_lang($lang = NULL)
380 if ($accepts === NULL)
382 // Parse the HTTP_ACCEPT_LANGUAGE header
383 $accepts = Request
::_parse_accept($_SERVER['HTTP_ACCEPT_LANGUAGE']);
388 // Return the quality setting for this lang
389 return isset($accepts[$lang]) ?
$accepts[$lang] : FALSE;
396 * Returns the accepted encodings. If a specific encoding is defined,
397 * the quality of that encoding will be returned. If the encoding is not
398 * accepted, FALSE will be returned.
400 * $encodings = Request::accept_encoding();
402 * [!!] Deprecated in favor of using [HTTP_Header::accepts_encoding_at_quality].
404 * @deprecated since version 3.3.0
405 * @param string $type Encoding type
406 * @return mixed An array of all types or a specific type as a string
407 * @uses Request::_parse_accept
409 public static function accept_encoding($type = NULL)
413 if ($accepts === NULL)
415 // Parse the HTTP_ACCEPT_LANGUAGE header
416 $accepts = Request
::_parse_accept($_SERVER['HTTP_ACCEPT_ENCODING']);
421 // Return the quality setting for this type
422 return isset($accepts[$type]) ?
$accepts[$type] : FALSE;
429 * Determines if a file larger than the post_max_size has been uploaded. PHP
430 * does not handle this situation gracefully on its own, so this method
431 * helps to solve that problem.
437 public static function post_max_size_exceeded()
439 // Make sure the request method is POST
440 if (Request
::$initial->method() !== HTTP_Request
::POST
)
443 // Get the post_max_size in bytes
444 $max_bytes = Num
::bytes(ini_get('post_max_size'));
446 // Error occurred if method is POST, and content length is too long
447 return (Arr
::get($_SERVER, 'CONTENT_LENGTH') > $max_bytes);
451 * Process a request to find a matching route
453 * @param object $request Request
454 * @param array $routes Route
457 public static function process(Request
$request, $routes = NULL)
460 $routes = (empty($routes)) ? Route
::all() : $routes;
463 foreach ($routes as $name => $route)
465 // Use external routes for reverse routing only
466 if ($route->is_external())
471 // We found something suitable
472 if ($params = $route->matches($request))
485 * Parses an accept header and returns an array (type => quality) of the
486 * accepted types, ordered by quality.
488 * $accept = Request::_parse_accept($header, $defaults);
490 * @param string $header Header to parse
491 * @param array $accepts Default values
494 protected static function _parse_accept( & $header, array $accepts = NULL)
496 if ( ! empty($header))
498 // Get all of the types
499 $types = explode(',', $header);
501 foreach ($types as $type)
503 // Split the type into parts
504 $parts = explode(';', $type);
506 // Make the type only the MIME
507 $type = trim(array_shift($parts));
509 // Default quality is 1.0
512 foreach ($parts as $part)
514 // Prevent undefined $value notice below
515 if (strpos($part, '=') === FALSE)
518 // Separate the key and value
519 list ($key, $value) = explode('=', trim($part));
523 // There is a quality for this type
524 $quality = (float) trim($value);
528 // Add the accept type and quality
529 $accepts[$type] = $quality;
533 // Make sure that accepts is an array
534 $accepts = (array) $accepts;
543 * @var string the x-requested-with header which most likely
544 * will be xmlhttprequest
546 protected $_requested_with;
549 * @var string method: GET, POST, PUT, DELETE, HEAD, etc
551 protected $_method = 'GET';
554 * @var string protocol: HTTP/1.1, FTP, CLI, etc
556 protected $_protocol;
561 protected $_secure = FALSE;
564 * @var string referring URL
566 protected $_referrer;
569 * @var Route route matched for this request
574 * @var Route array of routes to manually look at instead of the global namespace
579 * @var Kohana_HTTP_Header headers to sent as part of the request
584 * @var string the body
589 * @var string controller directory
591 protected $_directory = '';
594 * @var string controller to be executed
596 protected $_controller;
599 * @var string action to be executed in the controller
604 * @var string the URI of the request
609 * @var boolean external request
611 protected $_external = FALSE;
614 * @var array parameters from the route
616 protected $_params = array();
619 * @var array query parameters
621 protected $_get = array();
624 * @var array post parameters
626 protected $_post = array();
629 * @var array cookies to send with the request
631 protected $_cookies = array();
634 * @var Kohana_Request_Client
639 * Creates a new request object for the given URI. New requests should be
640 * Created using the [Request::factory] method.
642 * $request = new Request($uri);
644 * If $cache parameter is set, the response for the request will attempt to
645 * be retrieved from the cache.
647 * @param string $uri URI of the request
648 * @param array $client_params Array of params to pass to the request client
649 * @param bool $allow_external Allow external requests? (deprecated in 3.3)
650 * @param array $injected_routes An array of routes to use, for testing
652 * @throws Request_Exception
654 * @uses Route::matches
656 public function __construct($uri, $client_params = array(), $allow_external = TRUE, $injected_routes = array())
658 $client_params = is_array($client_params) ?
$client_params : array();
660 // Initialise the header
661 $this->_header
= new HTTP_Header(array());
663 // Assign injected routes
664 $this->_routes
= $injected_routes;
666 // Cleanse query parameters from URI (faster that parse_url())
667 $split_uri = explode('?', $uri);
668 $uri = array_shift($split_uri);
672 parse_str($split_uri[0], $this->_get
);
675 // Detect protocol (if present)
676 // $allow_external = FALSE prevents the default index.php from
677 // being able to proxy external pages.
678 if ( ! $allow_external OR strpos($uri, '://') === FALSE)
680 // Remove leading and trailing slashes from the URI
681 $this->_uri
= trim($uri, '/');
684 $this->_client
= new Request_Client_Internal($client_params);
689 $this->_route
= new Route($uri);
694 // Set the security setting if required
695 if (strpos($uri, 'https://') === 0)
700 // Set external state
701 $this->_external
= TRUE;
704 $this->_client
= Request_Client_External
::factory($client_params);
709 * Returns the response as the string representation of a request.
715 public function __toString()
717 return $this->render();
721 * Sets and gets the uri from the request.
726 public function uri($uri = NULL)
731 return ($this->_uri
=== '') ?
'/' : $this->_uri
;
741 * Create a URL string from the current request. This is a shortcut for:
743 * echo URL::site($this->request->uri(), $protocol);
745 * @param mixed $protocol protocol string or Request object
750 public function url($protocol = NULL)
752 if ($this->is_external())
754 // If it's an external request return the URI
758 // Create a URI with the current route, convert to a URL and returns
759 return URL
::site($this->uri(), $protocol);
763 * Retrieves a value from the route parameters.
765 * $id = $request->param('id');
767 * @param string $key Key of the value
768 * @param mixed $default Default value if the key is not set
771 public function param($key = NULL, $default = NULL)
775 // Return the full array
776 return $this->_params
;
779 return isset($this->_params
[$key]) ?
$this->_params
[$key] : $default;
783 * Sets and gets the referrer from the request.
785 * @param string $referrer
788 public function referrer($referrer = NULL)
790 if ($referrer === NULL)
793 return $this->_referrer
;
797 $this->_referrer
= (string) $referrer;
803 * Sets and gets the route from the request.
805 * @param string $route
808 public function route(Route
$route = NULL)
813 return $this->_route
;
817 $this->_route
= $route;
823 * Sets and gets the directory for the controller.
825 * @param string $directory Directory to execute the controller from
828 public function directory($directory = NULL)
830 if ($directory === NULL)
833 return $this->_directory
;
837 $this->_directory
= (string) $directory;
843 * Sets and gets the controller for the matched route.
845 * @param string $controller Controller to execute the action
848 public function controller($controller = NULL)
850 if ($controller === NULL)
853 return $this->_controller
;
857 $this->_controller
= (string) $controller;
863 * Sets and gets the action for the controller.
865 * @param string $action Action to execute the controller from
868 public function action($action = NULL)
870 if ($action === NULL)
873 return $this->_action
;
877 $this->_action
= (string) $action;
883 * Provides access to the [Request_Client].
885 * @return Request_Client
888 public function client(Request_Client
$client = NULL)
890 if ($client === NULL)
891 return $this->_client
;
894 $this->_client
= $client;
900 * Gets and sets the requested with property, which should
901 * be relative to the x-requested-with pseudo header.
903 * @param string $requested_with Requested with value
906 public function requested_with($requested_with = NULL)
908 if ($requested_with === NULL)
911 return $this->_requested_with
;
915 $this->_requested_with
= strtolower($requested_with);
921 * Processes the request, executing the controller action that handles this
922 * request, determined by the [Route].
924 * 1. Before the controller action is called, the [Controller::before] method
926 * 2. Next the controller action will be called.
927 * 3. After the controller action is called, the [Controller::after] method
930 * By default, the output from the controller is captured and returned, and
931 * no headers are sent.
933 * $request->execute();
936 * @throws Request_Exception
937 * @throws HTTP_Exception_404
938 * @uses [Kohana::$profiling]
941 public function execute()
943 if ( ! $this->_external
)
945 $processed = Request
::process($this, $this->_routes
);
949 // Store the matching route
950 $this->_route
= $processed['route'];
951 $params = $processed['params'];
953 // Is this route external?
954 $this->_external
= $this->_route
->is_external();
956 if (isset($params['directory']))
958 // Controllers are in a sub-directory
959 $this->_directory
= $params['directory'];
962 // Store the controller
963 $this->_controller
= $params['controller'];
966 $this->_action
= (isset($params['action']))
968 : Route
::$default_action;
970 // These are accessible as public vars and can be overloaded
971 unset($params['controller'], $params['action'], $params['directory']);
973 // Params cannot be changed once matched
974 $this->_params
= $params;
978 if ( ! $this->_route
instanceof Route
)
980 return HTTP_Exception
::factory(404, 'Unable to find a route to match the URI: :uri', array(
981 ':uri' => $this->_uri
,
986 if ( ! $this->_client
instanceof Request_Client
)
988 throw new Request_Exception('Unable to execute :uri without a Kohana_Request_Client', array(
989 ':uri' => $this->_uri
,
993 return $this->_client
->execute($this);
997 * Returns whether this request is the initial request Kohana received.
998 * Can be used to test for sub requests.
1000 * if ( ! $request->is_initial())
1001 * // This is a sub request
1005 public function is_initial()
1007 return ($this === Request
::$initial);
1011 * Readonly access to the [Request::$_external] property.
1013 * if ( ! $request->is_external())
1014 * // This is an internal request
1018 public function is_external()
1020 return $this->_external
;
1024 * Returns whether this is an ajax request (as used by JS frameworks)
1028 public function is_ajax()
1030 return ($this->requested_with() === 'xmlhttprequest');
1034 * Gets or sets the HTTP method. Usually GET, POST, PUT or DELETE in
1035 * traditional CRUD applications.
1037 * @param string $method Method to use for this request
1040 public function method($method = NULL)
1042 if ($method === NULL)
1045 return $this->_method
;
1049 $this->_method
= strtoupper($method);
1055 * Gets or sets the HTTP protocol. If there is no current protocol set,
1056 * it will use the default set in HTTP::$protocol
1058 * @param string $protocol Protocol to set to the request
1061 public function protocol($protocol = NULL)
1063 if ($protocol === NULL)
1065 if ($this->_protocol
)
1066 return $this->_protocol
;
1068 return $this->_protocol
= HTTP
::$protocol;
1072 $this->_protocol
= strtoupper($protocol);
1077 * Getter/Setter to the security settings for this request. This
1078 * method should be treated as immutable.
1080 * @param boolean $secure is this request secure?
1083 public function secure($secure = NULL)
1085 if ($secure === NULL)
1086 return $this->_secure
;
1089 $this->_secure
= (bool) $secure;
1094 * Gets or sets HTTP headers oo the request. All headers
1095 * are included immediately after the HTTP protocol definition during
1096 * transmission. This method provides a simple array or key/value
1097 * interface to the headers.
1099 * @param mixed $key Key or array of key/value pairs to set
1100 * @param string $value Value to set to the supplied key
1103 public function headers($key = NULL, $value = NULL)
1105 if ($key instanceof HTTP_Header
)
1107 // Act a setter, replace all headers
1108 $this->_header
= $key;
1115 // Act as a setter, replace all headers
1116 $this->_header
->exchangeArray($key);
1121 if ($this->_header
->count() === 0 AND $this->is_initial())
1123 // Lazy load the request headers
1124 $this->_header
= HTTP
::request_headers();
1129 // Act as a getter, return all headers
1130 return $this->_header
;
1132 elseif ($value === NULL)
1134 // Act as a getter, single header
1135 return ($this->_header
->offsetExists($key)) ?
$this->_header
->offsetGet($key) : NULL;
1138 // Act as a setter for a single header
1139 $this->_header
[$key] = $value;
1145 * Set and get cookies values for this request.
1147 * @param mixed $key Cookie name, or array of cookie values
1148 * @param string $value Value to set to cookie
1152 public function cookie($key = NULL, $value = NULL)
1156 // Act as a setter, replace all cookies
1157 $this->_cookies
= $key;
1160 elseif ($key === NULL)
1162 // Act as a getter, all cookies
1163 return $this->_cookies
;
1165 elseif ($value === NULL)
1167 // Act as a getting, single cookie
1168 return isset($this->_cookies
[$key]) ?
$this->_cookies
[$key] : NULL;
1171 // Act as a setter for a single cookie
1172 $this->_cookies
[$key] = (string) $value;
1178 * Gets or sets the HTTP body of the request. The body is
1179 * included after the header, separated by a single empty new line.
1181 * @param string $content Content to set to the object
1184 public function body($content = NULL)
1186 if ($content === NULL)
1189 return $this->_body
;
1193 $this->_body
= $content;
1199 * Returns the length of the body for use with
1204 public function content_length()
1206 return strlen($this->body());
1210 * Renders the HTTP_Interaction to a string, producing
1216 * If there are variables set to the `Kohana_Request::$_post`
1217 * they will override any values set to body.
1221 public function render()
1223 if ( ! $post = $this->post())
1225 $body = $this->body();
1229 $body = http_build_query($post, NULL, '&');
1231 ->headers('content-type', 'application/x-www-form-urlencoded; charset='.Kohana
::$charset);
1234 // Set the content length
1235 $this->headers('content-length', (string) $this->content_length());
1237 // If Kohana expose, set the user-agent
1238 if (Kohana
::$expose)
1240 $this->headers('user-agent', Kohana
::version());
1244 if ($this->_cookies
)
1246 $cookie_string = array();
1249 foreach ($this->_cookies
as $key => $value)
1251 $cookie_string[] = $key.'='.$value;
1254 // Create the cookie string
1255 $this->_header
['cookie'] = implode('; ', $cookie_string);
1258 $output = $this->method().' '.$this->uri().' '.$this->protocol()."\r\n";
1259 $output .= (string) $this->_header
;
1266 * Gets or sets HTTP query string.
1268 * @param mixed $key Key or key value pairs to set
1269 * @param string $value Value to set to a key
1273 public function query($key = NULL, $value = NULL)
1277 // Act as a setter, replace all query strings
1285 // Act as a getter, all query strings
1288 elseif ($value === NULL)
1290 // Act as a getter, single query string
1291 return Arr
::path($this->_get
, $key);
1294 // Act as a setter, single query string
1295 $this->_get
[$key] = $value;
1301 * Gets or sets HTTP POST parameters to the request.
1303 * @param mixed $key Key or key value pairs to set
1304 * @param string $value Value to set to a key
1308 public function post($key = NULL, $value = NULL)
1312 // Act as a setter, replace all fields
1313 $this->_post
= $key;
1320 // Act as a getter, all fields
1321 return $this->_post
;
1323 elseif ($value === NULL)
1325 // Act as a getter, single field
1326 return Arr
::path($this->_post
, $key);
1329 // Act as a setter, single field
1330 $this->_post
[$key] = $value;