<?php
/**
*
* Class for gathering details about the request environment.
*
* To be safe, treat everything in the superglobals as tainted.
*
* @category Solar
*
* @package Solar
*
* @author Paul M. Jones <hide@address.com>
*
* @author Clay Loveless <hide@address.com>
*
* @license http://opensource.org/licenses/bsd-license.php BSD
*
* @version $Id: Request.php 4589 2010-06-11 13:33:01Z pmjones $
*
*/
class Solar_Request extends Solar_Base
{
/**
*
* Imported $_ENV values.
*
* @var array
*
*/
public $env;
/**
*
* Imported $_GET values.
*
* @var array
*
*/
public $get;
/**
*
* Imported $_POST values.
*
* @var array
*
*/
public $post;
/**
*
* Imported $_COOKIE values.
*
* @var array
*
*/
public $cookie;
/**
*
* Imported $_SERVER values.
*
* @var array
*
*/
public $server;
/**
*
* Imported $_FILES values.
*
* @var array
*
*/
public $files;
/**
*
* Imported $_SERVER['HTTP_*'] values.
*
* Header keys are normalized and lower-cased; keys and values are
* filtered for control characters.
*
* @var array
*
*/
public $http;
/**
*
* Imported $_SERVER['argv'] values.
*
* @var array
*
*/
public $argv;
/**
*
* Is this GET request after a POST/PUT redirect?
*
* @var bool
*
*/
protected $_is_gap = null;
/**
*
* Cross-site request forgery detector.
*
* @var Solar_Csrf
*
*/
protected $_csrf;
/**
*
* Post-construction tasks to complete object construction.
*
* @return void
*
*/
protected function _postConstruct()
{
parent::_postConstruct();
$this->reset();
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$get | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $get key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $get[$key], or the alternate default
* value.
*
*/
public function get($key = null, $alt = null)
{
return $this->_getValue('get', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$post | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $post key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $post[$key], or the alternate default
* value.
*
*/
public function post($key = null, $alt = null)
{
return $this->_getValue('post', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$cookie | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $cookie key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $cookie[$key], or the alternate default
* value.
*
*/
public function cookie($key = null, $alt = null)
{
return $this->_getValue('cookie', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$env | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $env key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $env[$key], or the alternate default
* value.
*
*/
public function env($key = null, $alt = null)
{
return $this->_getValue('env', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$server | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $server key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $server[$key], or the alternate default
* value.
*
*/
public function server($key = null, $alt = null)
{
return $this->_getValue('server', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$files | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $files key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $files[$key], or the alternate default
* value.
*
*/
public function files($key = null, $alt = null)
{
return $this->_getValue('files', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$argv | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $argv key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $argv[$key], or the alternate default
* value.
*
*/
public function argv($key = null, $alt = null)
{
return $this->_getValue('argv', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$http | ]] property,
* or an alternate default value if that key does not exist.
*
* @param string $key The $http key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist.
*
* @return mixed The value of $http[$key], or the alternate default
* value.
*
*/
public function http($key = null, $alt = null)
{
if ($key !== null) {
$key = strtolower($key);
}
return $this->_getValue('http', $key, $alt);
}
/**
*
* Retrieves an **unfiltered** value by key from the [[Solar_Request::$post | ]] *and*
* [[Solar_Request::$files | ]] properties, or an alternate default value if that key does
* not exist in either location. Files takes precedence over post.
*
* @param string $key The $post and $files key to retrieve the value of.
*
* @param string $alt The value to return if the key does not exist in
* either $post or $files.
*
* @return mixed The value of $post[$key] combined with $files[$key], or
* the alternate default value.
*
*/
public function postAndFiles($key = null, $alt = null)
{
$post = $this->_getValue('post', $key, false);
$files = $this->_getValue('files', $key, false);
// no matches in post or files
if (! $post && ! $files) {
return $alt;
}
// match in post, not in files
if ($post && ! $files) {
return $post;
}
// match in files, not in post
if (! $post && $files) {
return $files;
}
// are either or both arrays?
$post_array = is_array($post);
$files_array = is_array($files);
// both are arrays, merge them
if ($post_array && $files_array) {
return array_merge($post, $files);
}
// post array but single files, append to post
if ($post_array && ! $files_array) {
array_push($post, $files);
return $post;
}
// files array but single post, append to files
if (! $post_array && $files_array) {
array_push($files, $post);
return $files;
}
// now what?
throw $this->_exception('ERR_POST_AND_FILES', array(
'key' => $key,
));
}
/**
*
* Is this a secure SSL request?
*
* @return bool
*
*/
public function isSsl()
{
return $this->server('HTTPS') == 'on'
|| $this->server('SERVER_PORT') == 443;
}
/**
*
* Is this a command-line request?
*
* @return bool
*
*/
public function isCli()
{
return PHP_SAPI == 'cli';
}
/**
*
* Is the current request a cross-site forgery?
*
* @return bool
*
*/
public function isCsrf()
{
if (! $this->_csrf) {
$this->_csrf = Solar::factory('Solar_Csrf');
}
return $this->_csrf->isForgery();
}
/**
*
* Is this a GET-after-POST request?
*
* @return bool
*
*/
public function isGap()
{
if ($this->_is_gap === null) {
$session = Solar::factory('Solar_Session', array(
'class' => get_class($this),
));
$this->_is_gap = (bool) $session->getFlash('is_gap');
}
return $this->_is_gap;
}
/**
*
* Is this a 'GET' request?
*
* @return bool
*
*/
public function isGet()
{
return $this->server('REQUEST_METHOD') == 'GET';
}
/**
*
* Is this a 'POST' request?
*
* @return bool
*
*/
public function isPost()
{
return $this->server('REQUEST_METHOD') == 'POST';
}
/**
*
* Is this a 'PUT' request? Supports Google's X-HTTP-Method-Override
* solution to languages like PHP not fully honoring the HTTP PUT method.
*
* @return bool
*
*/
public function isPut()
{
$is_put = $this->server('REQUEST_METHOD') == 'PUT';
$is_override = $this->server('REQUEST_METHOD') == 'POST' &&
$this->http('X-HTTP-Method-Override') == 'PUT';
return ($is_put || $is_override);
}
/**
*
* Is this a 'DELETE' request? Supports Google's X-HTTP-Method-Override
* solution to languages like PHP not fully honoring the HTTP DELETE
* method.
*
* @return bool
*
*/
public function isDelete()
{
$is_delete = $this->server('REQUEST_METHOD') == 'DELETE';
$is_override = $this->server('REQUEST_METHOD') == 'POST' &&
$this->http('X-HTTP-Method-Override') == 'DELETE';
return ($is_delete || $is_override);
}
/**
*
* Is this an XmlHttpRequest?
*
* Checks if the `X-Requested-With` HTTP header is `XMLHttpRequest`.
* Generally used in addition to the [[Solar_Request::isPost() | ]],
* [[Solar_Request::isGet() | ]], etc. methods to identify Ajax-style
* HTTP requests.
*
* @return bool
*
*/
public function isXhr()
{
return strtolower($this->http('X-Requested-With')) == 'xmlhttprequest';
}
/**
*
* Reloads properties from the superglobal arrays.
*
* Normalizes HTTP header keys, dispels magic quotes.
*
* @return void
*
*/
public function reset()
{
// load the "real" request vars
$vars = array('env', 'get', 'post', 'cookie', 'server', 'files');
foreach ($vars as $key) {
$var = '_' . strtoupper($key);
if (isset($GLOBALS[$var])) {
$this->$key = $GLOBALS[$var];
} else {
$this->$key = array();
}
}
// dispel magic quotes if they are enabled.
// http://talks.php.net/show/php-best-practices/26
if (get_magic_quotes_gpc()) {
$in = array(&$this->get, &$this->post, &$this->cookie);
while (list($k, $v) = each($in)) {
foreach ($v as $key => $val) {
if (! is_array($val)) {
$in[$k][$key] = stripslashes($val);
continue;
}
$in[] =& $in[$k][$key];
}
}
unset($in);
}
// load the "fake" argv request var
$this->argv = (array) $this->server('argv');
// load the "fake" http request var
$this->http = array();
foreach ($this->server as $key => $val) {
// only retain HTTP headers
if (substr($key, 0, 5) == 'HTTP_') {
// normalize the header key to lower-case
$nicekey = strtolower(
str_replace('_', '-', substr($key, 5))
);
// strip control characters from keys and values
$nicekey = preg_replace('/[\x00-\x1F]/', '', $nicekey);
$this->http[$nicekey] = preg_replace('/[\x00-\x1F]/', '', $val);
// no control characters wanted in $this->server for these
$this->server[$key] = $this->http[$nicekey];
// disallow external setting of X-JSON headers.
if ($nicekey == 'x-json') {
unset($this->http[$nicekey]);
unset($this->server[$key]);
}
}
}
// rebuild the files array to make it look more like POST
if ($this->files) {
$files = $this->files;
$this->files = array();
$this->_rebuildFiles($files, $this->files);
}
}
/**
*
* Recursive method to rebuild $_FILES structure to be more like $_POST.
*
* @param array $src The source $_FILES array, perhaps from a sub-
* element of that array/
*
* @param array &$tgt Where we will store the restructured data when we
* find it.
*
* @return void
*
*/
protected function _rebuildFiles($src, &$tgt)
{
// an array with these keys is a "target" for us (pre-sorted)
$tgtkeys = array('error', 'name', 'size', 'tmp_name', 'type');
// the keys of the source array (sorted so that comparisons work
// regardless of original order)
$srckeys = array_keys((array) $src);
sort($srckeys);
// is the source array a target?
if ($srckeys == $tgtkeys) {
// get error, name, size, etc
foreach ($srckeys as $key) {
if (is_array($src[$key])) {
// multiple file field names for each error, name, size, etc.
foreach ((array) $src[$key] as $field => $value) {
$tgt[$field][$key] = $value;
}
} else {
// the key itself is error, name, size, etc., and the
// target is already the file field name
$tgt[$key] = $src[$key];
}
}
} else {
// not a target, create sub-elements and rebuild them too
foreach ($src as $key => $val) {
$tgt[$key] = array();
$this->_rebuildFiles($val, $tgt[$key], $key);
}
}
}
/**
*
* Common method to get a request value and return it.
*
* @param string $var The request variable to fetch from: get, post,
* etc.
*
* @param string $key The array key, if any, to get the value of.
*
* @param string $alt The alternative default value to return if the
* requested key does not exist.
*
* @return mixed The requested value, or the alternative default
* value.
*
*/
protected function _getValue($var, $key, $alt)
{
// get the whole property, or just one key?
if ($key === null) {
// no key selected, return the whole array
return $this->$var;
} elseif (array_key_exists($key, $this->$var)) {
// found the requested key.
// need the funny {} becuase $var[$key] will try to find a
// property named for that element value, not for $var.
return $this->{$var}[$key];
} else {
// requested key does not exist
return $alt;
}
}
}