You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
flight-core/flight/net/Request.php

293 lines
7.3 KiB

14 years ago
<?php
/**
* Flight: An extensible micro-framework.
14 years ago
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license
14 years ago
*/
namespace flight\net;
use flight\util\Collection;
/**
* The Request class represents an HTTP request. Data from
* all the super globals $_GET, $_POST, $_COOKIE, and $_FILES
* are stored and accessible via the Request object.
*
* The default request properties are:
* url - The URL being requested
* base - The parent subdirectory of the URL
* method - The request method (GET, POST, PUT, DELETE)
* referrer - The referrer URL
* ip - IP address of the client
* ajax - Whether the request is an AJAX request
* scheme - The server protocol (http, https)
* user_agent - Browser information
* type - The content type
* length - The content length
* query - Query string parameters
* data - Post parameters
* cookies - Cookie parameters
* files - Uploaded files
* secure - Connection is secure
* accept - HTTP accept parameters
* proxy_ip - Proxy IP address of the client
*/
14 years ago
class Request {
/**
* @var string URL being requested
*/
public $url;
/**
* @var string Parent subdirectory of the URL
*/
public $base;
/**
* @var string Request method (GET, POST, PUT, DELETE)
*/
public $method;
/**
* @var string Referrer URL
*/
public $referrer;
/**
* @var string IP address of the client
*/
public $ip;
/**
* @var bool Whether the request is an AJAX request
*/
public $ajax;
/**
* @var string Server protocol (http, https)
*/
public $scheme;
/**
* @var string Browser information
*/
public $user_agent;
/**
* @var string Content type
*/
public $type;
/**
* @var int Content length
*/
public $length;
/**
* @var \flight\util\Collection Query string parameters
*/
public $query;
/**
* @var \flight\util\Collection Post parameters
*/
public $data;
/**
* @var \flight\util\Collection Cookie parameters
*/
public $cookies;
/**
* @var \flight\util\Collection Uploaded files
*/
public $files;
/**
* @var bool Whether the connection is secure
*/
public $secure;
/**
* @var string HTTP accept parameters
*/
public $accept;
/**
* @var string Proxy IP address of the client
*/
public $proxy_ip;
/**
* @var mixed Data from the request body
*/
protected static $body;
14 years ago
/**
* Constructor.
*
* @param array $config Request configuration
*/
public function __construct($config = array()) {
// Default properties
if (empty($config)) {
$config = array(
'url' => self::getVar('REQUEST_URI', '/'),
'base' => str_replace(array('\\',' '), array('/','%20'), dirname(self::getVar('SCRIPT_NAME'))),
'method' => self::getMethod(),
'referrer' => self::getVar('HTTP_REFERER'),
'ip' => self::getVar('REMOTE_ADDR'),
'ajax' => self::getVar('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest',
'scheme' => self::getVar('SERVER_PROTOCOL', 'HTTP/1.1'),
'user_agent' => self::getVar('HTTP_USER_AGENT'),
'type' => self::getVar('CONTENT_TYPE'),
'length' => self::getVar('CONTENT_LENGTH', 0),
'query' => new Collection($_GET),
'data' => new Collection($_POST),
'cookies' => new Collection($_COOKIE),
a few enhancements to request.php - added "secure" parameter to avoid boilerplate "if https && https != 'off'" - added "accept" parameter containing request's HTTP_ACCEPT value if any - Changed IP to always be REMOTE_ADDR, as this is the only "real" ip of the request. - Added 'proxy' parameter containing any proxy-forwarded IP - Changed getIpAddress to getProxyIpAddress - renamed $vars to $forwarded to be a bit more explicit - Removed REMOTE_ADDR from proxy forwarded addresses to detect - change explode() to sscanf, will explain this later. - added flags to the filter_var, will also explain later - added getMethodOverride function to detect request method overrides I did some research because I was wondering why it would be necessary to check for a comma, and apparently some proxies do send a comma separated list of IP addresses, with the originating client IP as the first one. It had a feeling that instead of using explode() where the first value would always be returned, there must be another way to get the first value of a token-delimited string, or just the whole string if there was no token, and I bumped in to my old friend sscanf. So the loop is now 3 levels. As far as the flags, see: http://php.net/manual/en/filter.filters.flags.php These ensure that any IP detected is not a useless IP behind a remote NAT. For example, a corporate proxy might send HTTP_X_FORWARDED_FOR using the internal IP of the user, but the only useful IP in this case is that of the proxy, which PHP gets as REMOTE_ADDR. If you find any of these changes acceptable but don't want to merge them all, do feel free to implement whichever you like. I was thinking of adding a getRequestMethod() function to check for `$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']` or `$_POST['_method']` to allow clients to negotiate PUT/DELETE requests, but personally I am indifferent to these, as I never use methods beyond GET and POST (I'm not very RESTful I guess), and it's not really my place to be writing an implementation of either. Other frameworks seem to just overwrite the method parameter so that it appears to be the overridden one.
12 years ago
'files' => new Collection($_FILES),
'secure' => self::getVar('HTTPS', 'off') != 'off',
'accept' => self::getVar('HTTP_ACCEPT'),
'proxy_ip' => self::getProxyIpAddress()
14 years ago
);
}
$this->init($config);
14 years ago
}
/**
* Initialize request properties.
*
* @param array $properties Array of request properties
*/
public function init($properties = array()) {
// Set all the defined properties
14 years ago
foreach ($properties as $name => $value) {
$this->$name = $value;
14 years ago
}
// Get the requested URL without the base directory
if ($this->base != '/' && strlen($this->base) > 0 && strpos($this->url, $this->base) === 0) {
14 years ago
$this->url = substr($this->url, strlen($this->base));
}
// Default url
if (empty($this->url)) {
$this->url = '/';
}
// Merge URL query parameters with $_GET
else {
$_GET += self::parseQuery($this->url);
$this->query->setData($_GET);
}
// Check for JSON input
if (strpos($this->type, 'application/json') === 0) {
$body = $this->getBody();
if ($body != '') {
$data = json_decode($body, true);
if ($data != null) {
$this->data->setData($data);
}
}
}
14 years ago
}
/**
* Gets the body of the request.
*
* @return string Raw HTTP request body
14 years ago
*/
public static function getBody()
{
if (!is_null(self::$body)) {
return self::$body;
}
$body = '';
$method = self::getMethod();
14 years ago
if ($method == 'POST' || $method == 'PUT') {
$body = file_get_contents('php://input');
14 years ago
}
return self::$body = $body;
14 years ago
}
/**
* Gets the request method.
*
* @return string
*/
public static function getMethod() {
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
return $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
}
elseif (isset($_REQUEST['_method'])) {
return $_REQUEST['_method'];
}
return self::getVar('REQUEST_METHOD', 'GET');
}
/**
* Gets the real remote IP address.
*
* @return string IP address
*/
public static function getProxyIpAddress() {
a few enhancements to request.php - added "secure" parameter to avoid boilerplate "if https && https != 'off'" - added "accept" parameter containing request's HTTP_ACCEPT value if any - Changed IP to always be REMOTE_ADDR, as this is the only "real" ip of the request. - Added 'proxy' parameter containing any proxy-forwarded IP - Changed getIpAddress to getProxyIpAddress - renamed $vars to $forwarded to be a bit more explicit - Removed REMOTE_ADDR from proxy forwarded addresses to detect - change explode() to sscanf, will explain this later. - added flags to the filter_var, will also explain later - added getMethodOverride function to detect request method overrides I did some research because I was wondering why it would be necessary to check for a comma, and apparently some proxies do send a comma separated list of IP addresses, with the originating client IP as the first one. It had a feeling that instead of using explode() where the first value would always be returned, there must be another way to get the first value of a token-delimited string, or just the whole string if there was no token, and I bumped in to my old friend sscanf. So the loop is now 3 levels. As far as the flags, see: http://php.net/manual/en/filter.filters.flags.php These ensure that any IP detected is not a useless IP behind a remote NAT. For example, a corporate proxy might send HTTP_X_FORWARDED_FOR using the internal IP of the user, but the only useful IP in this case is that of the proxy, which PHP gets as REMOTE_ADDR. If you find any of these changes acceptable but don't want to merge them all, do feel free to implement whichever you like. I was thinking of adding a getRequestMethod() function to check for `$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']` or `$_POST['_method']` to allow clients to negotiate PUT/DELETE requests, but personally I am indifferent to these, as I never use methods beyond GET and POST (I'm not very RESTful I guess), and it's not really my place to be writing an implementation of either. Other frameworks seem to just overwrite the method parameter so that it appears to be the overridden one.
12 years ago
static $forwarded = array(
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
a few enhancements to request.php - added "secure" parameter to avoid boilerplate "if https && https != 'off'" - added "accept" parameter containing request's HTTP_ACCEPT value if any - Changed IP to always be REMOTE_ADDR, as this is the only "real" ip of the request. - Added 'proxy' parameter containing any proxy-forwarded IP - Changed getIpAddress to getProxyIpAddress - renamed $vars to $forwarded to be a bit more explicit - Removed REMOTE_ADDR from proxy forwarded addresses to detect - change explode() to sscanf, will explain this later. - added flags to the filter_var, will also explain later - added getMethodOverride function to detect request method overrides I did some research because I was wondering why it would be necessary to check for a comma, and apparently some proxies do send a comma separated list of IP addresses, with the originating client IP as the first one. It had a feeling that instead of using explode() where the first value would always be returned, there must be another way to get the first value of a token-delimited string, or just the whole string if there was no token, and I bumped in to my old friend sscanf. So the loop is now 3 levels. As far as the flags, see: http://php.net/manual/en/filter.filters.flags.php These ensure that any IP detected is not a useless IP behind a remote NAT. For example, a corporate proxy might send HTTP_X_FORWARDED_FOR using the internal IP of the user, but the only useful IP in this case is that of the proxy, which PHP gets as REMOTE_ADDR. If you find any of these changes acceptable but don't want to merge them all, do feel free to implement whichever you like. I was thinking of adding a getRequestMethod() function to check for `$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']` or `$_POST['_method']` to allow clients to negotiate PUT/DELETE requests, but personally I am indifferent to these, as I never use methods beyond GET and POST (I'm not very RESTful I guess), and it's not really my place to be writing an implementation of either. Other frameworks seem to just overwrite the method parameter so that it appears to be the overridden one.
12 years ago
'HTTP_FORWARDED'
);
a few enhancements to request.php - added "secure" parameter to avoid boilerplate "if https && https != 'off'" - added "accept" parameter containing request's HTTP_ACCEPT value if any - Changed IP to always be REMOTE_ADDR, as this is the only "real" ip of the request. - Added 'proxy' parameter containing any proxy-forwarded IP - Changed getIpAddress to getProxyIpAddress - renamed $vars to $forwarded to be a bit more explicit - Removed REMOTE_ADDR from proxy forwarded addresses to detect - change explode() to sscanf, will explain this later. - added flags to the filter_var, will also explain later - added getMethodOverride function to detect request method overrides I did some research because I was wondering why it would be necessary to check for a comma, and apparently some proxies do send a comma separated list of IP addresses, with the originating client IP as the first one. It had a feeling that instead of using explode() where the first value would always be returned, there must be another way to get the first value of a token-delimited string, or just the whole string if there was no token, and I bumped in to my old friend sscanf. So the loop is now 3 levels. As far as the flags, see: http://php.net/manual/en/filter.filters.flags.php These ensure that any IP detected is not a useless IP behind a remote NAT. For example, a corporate proxy might send HTTP_X_FORWARDED_FOR using the internal IP of the user, but the only useful IP in this case is that of the proxy, which PHP gets as REMOTE_ADDR. If you find any of these changes acceptable but don't want to merge them all, do feel free to implement whichever you like. I was thinking of adding a getRequestMethod() function to check for `$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']` or `$_POST['_method']` to allow clients to negotiate PUT/DELETE requests, but personally I am indifferent to these, as I never use methods beyond GET and POST (I'm not very RESTful I guess), and it's not really my place to be writing an implementation of either. Other frameworks seem to just overwrite the method parameter so that it appears to be the overridden one.
12 years ago
$flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
a few enhancements to request.php - added "secure" parameter to avoid boilerplate "if https && https != 'off'" - added "accept" parameter containing request's HTTP_ACCEPT value if any - Changed IP to always be REMOTE_ADDR, as this is the only "real" ip of the request. - Added 'proxy' parameter containing any proxy-forwarded IP - Changed getIpAddress to getProxyIpAddress - renamed $vars to $forwarded to be a bit more explicit - Removed REMOTE_ADDR from proxy forwarded addresses to detect - change explode() to sscanf, will explain this later. - added flags to the filter_var, will also explain later - added getMethodOverride function to detect request method overrides I did some research because I was wondering why it would be necessary to check for a comma, and apparently some proxies do send a comma separated list of IP addresses, with the originating client IP as the first one. It had a feeling that instead of using explode() where the first value would always be returned, there must be another way to get the first value of a token-delimited string, or just the whole string if there was no token, and I bumped in to my old friend sscanf. So the loop is now 3 levels. As far as the flags, see: http://php.net/manual/en/filter.filters.flags.php These ensure that any IP detected is not a useless IP behind a remote NAT. For example, a corporate proxy might send HTTP_X_FORWARDED_FOR using the internal IP of the user, but the only useful IP in this case is that of the proxy, which PHP gets as REMOTE_ADDR. If you find any of these changes acceptable but don't want to merge them all, do feel free to implement whichever you like. I was thinking of adding a getRequestMethod() function to check for `$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']` or `$_POST['_method']` to allow clients to negotiate PUT/DELETE requests, but personally I am indifferent to these, as I never use methods beyond GET and POST (I'm not very RESTful I guess), and it's not really my place to be writing an implementation of either. Other frameworks seem to just overwrite the method parameter so that it appears to be the overridden one.
12 years ago
foreach ($forwarded as $key) {
if (array_key_exists($key, $_SERVER)) {
sscanf($_SERVER[$key], '%[^,]', $ip);
if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) {
return $ip;
}
}
}
return '';
}
11 years ago
/**
* Gets a variable from $_SERVER using $default if not provided.
11 years ago
*
* @param string $var Variable name
* @param string $default Default value to substitute
* @return string Server variable value
*/
public static function getVar($var, $default = '') {
11 years ago
return isset($_SERVER[$var]) ? $_SERVER[$var] : $default;
}
/**
* Parse query parameters from a URL.
*
* @param string $url URL string
* @return array Query parameters
*/
public static function parseQuery($url) {
$params = array();
$args = parse_url($url);
if (isset($args['query'])) {
parse_str($args['query'], $params);
}
return $params;
}
14 years ago
}