Kohana v3.3.5
This commit is contained in:
@@ -279,7 +279,13 @@ class Kohana_Arr {
|
||||
*/
|
||||
public static function get($array, $key, $default = NULL)
|
||||
{
|
||||
return isset($array[$key]) ? $array[$key] : $default;
|
||||
if ($array instanceof ArrayObject) {
|
||||
// This is a workaround for inconsistent implementation of isset between PHP and HHVM
|
||||
// See https://github.com/facebook/hhvm/issues/3437
|
||||
return $array->offsetExists($key) ? $array->offsetGet($key) : $default;
|
||||
} else {
|
||||
return isset($array[$key]) ? $array[$key] : $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,7 +393,7 @@ class Kohana_Arr {
|
||||
{
|
||||
if (is_array($val))
|
||||
{
|
||||
$array[$key] = Arr::map($callbacks, $array[$key]);
|
||||
$array[$key] = Arr::map($callbacks, $array[$key], $keys);
|
||||
}
|
||||
elseif ( ! is_array($keys) OR in_array($key, $keys))
|
||||
{
|
||||
|
@@ -71,14 +71,14 @@ class Kohana_Cookie {
|
||||
// Separate the salt and the value
|
||||
list ($hash, $value) = explode('~', $cookie, 2);
|
||||
|
||||
if (Cookie::salt($key, $value) === $hash)
|
||||
if (Security::slow_equals(Cookie::salt($key, $value), $hash))
|
||||
{
|
||||
// Cookie signature is valid
|
||||
return $value;
|
||||
}
|
||||
|
||||
// The cookie signature is invalid, delete it
|
||||
Cookie::delete($key);
|
||||
static::delete($key);
|
||||
}
|
||||
|
||||
return $default;
|
||||
@@ -88,33 +88,38 @@ class Kohana_Cookie {
|
||||
* Sets a signed cookie. Note that all cookie values must be strings and no
|
||||
* automatic serialization will be performed!
|
||||
*
|
||||
* [!!] By default, Cookie::$expiration is 0 - if you skip/pass NULL for the optional
|
||||
* lifetime argument your cookies will expire immediately unless you have separately
|
||||
* configured Cookie::$expiration.
|
||||
*
|
||||
*
|
||||
* // Set the "theme" cookie
|
||||
* Cookie::set('theme', 'red');
|
||||
*
|
||||
* @param string $name name of cookie
|
||||
* @param string $value value of cookie
|
||||
* @param integer $expiration lifetime in seconds
|
||||
* @param integer $lifetime lifetime in seconds
|
||||
* @return boolean
|
||||
* @uses Cookie::salt
|
||||
*/
|
||||
public static function set($name, $value, $expiration = NULL)
|
||||
public static function set($name, $value, $lifetime = NULL)
|
||||
{
|
||||
if ($expiration === NULL)
|
||||
if ($lifetime === NULL)
|
||||
{
|
||||
// Use the default expiration
|
||||
$expiration = Cookie::$expiration;
|
||||
$lifetime = Cookie::$expiration;
|
||||
}
|
||||
|
||||
if ($expiration !== 0)
|
||||
if ($lifetime !== 0)
|
||||
{
|
||||
// The expiration is expected to be a UNIX timestamp
|
||||
$expiration += time();
|
||||
$lifetime += static::_time();
|
||||
}
|
||||
|
||||
// Add the salt to the cookie value
|
||||
$value = Cookie::salt($name, $value).'~'.$value;
|
||||
|
||||
return setcookie($name, $value, $expiration, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
|
||||
return static::_setcookie($name, $value, $lifetime, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +136,7 @@ class Kohana_Cookie {
|
||||
unset($_COOKIE[$name]);
|
||||
|
||||
// Nullify the cookie and make it expire
|
||||
return setcookie($name, NULL, -86400, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
|
||||
return static::_setcookie($name, NULL, -86400, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,8 +144,10 @@ class Kohana_Cookie {
|
||||
*
|
||||
* $salt = Cookie::salt('theme', 'red');
|
||||
*
|
||||
* @param string $name name of cookie
|
||||
* @param string $value value of cookie
|
||||
* @param string $name name of cookie
|
||||
* @param string $value value of cookie
|
||||
*
|
||||
* @throws Kohana_Exception if Cookie::$salt is not configured
|
||||
* @return string
|
||||
*/
|
||||
public static function salt($name, $value)
|
||||
@@ -154,7 +161,38 @@ class Kohana_Cookie {
|
||||
// Determine the user agent
|
||||
$agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : 'unknown';
|
||||
|
||||
return sha1($agent.$name.$value.Cookie::$salt);
|
||||
return hash_hmac('sha1', $agent.$name.$value.Cookie::$salt, Cookie::$salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy for the native setcookie function - to allow mocking in unit tests so that they do not fail when headers
|
||||
* have been sent.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param integer $expire
|
||||
* @param string $path
|
||||
* @param string $domain
|
||||
* @param boolean $secure
|
||||
* @param boolean $httponly
|
||||
*
|
||||
* @return bool
|
||||
* @see setcookie
|
||||
*/
|
||||
protected static function _setcookie($name, $value, $expire, $path, $domain, $secure, $httponly)
|
||||
{
|
||||
return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy for the native time function - to allow mocking of time-related logic in unit tests
|
||||
*
|
||||
* @return int
|
||||
* @see time
|
||||
*/
|
||||
protected static function _time()
|
||||
{
|
||||
return time();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -16,8 +16,8 @@
|
||||
class Kohana_Core {
|
||||
|
||||
// Release version and codename
|
||||
const VERSION = '3.3.1';
|
||||
const CODENAME = 'peregrinus';
|
||||
const VERSION = '3.3.5';
|
||||
const CODENAME = 'pharrell';
|
||||
|
||||
// Common environment type constants for consistency and convenience
|
||||
const PRODUCTION = 10;
|
||||
@@ -322,7 +322,7 @@ class Kohana_Core {
|
||||
}
|
||||
|
||||
// Determine if the extremely evil magic quotes are enabled
|
||||
Kohana::$magic_quotes = (version_compare(PHP_VERSION, '5.4') < 0 AND get_magic_quotes_gpc());
|
||||
Kohana::$magic_quotes = (bool) get_magic_quotes_gpc();
|
||||
|
||||
// Sanitize all request variables
|
||||
$_GET = Kohana::sanitize($_GET);
|
||||
|
@@ -592,10 +592,10 @@ class Kohana_Date {
|
||||
$tz = new DateTimeZone($timezone ? $timezone : date_default_timezone_get());
|
||||
$time = new DateTime($datetime_str, $tz);
|
||||
|
||||
if ($time->getTimeZone()->getName() !== $tz->getName())
|
||||
{
|
||||
$time->setTimeZone($tz);
|
||||
}
|
||||
// Convert the time back to the expected timezone if required (in case the datetime_str provided a timezone,
|
||||
// offset or unix timestamp. This also ensures that the timezone reported by the object is correct on HHVM
|
||||
// (see https://github.com/facebook/hhvm/issues/2302).
|
||||
$time->setTimeZone($tz);
|
||||
|
||||
return $time->format($timestamp_format);
|
||||
}
|
||||
|
@@ -133,8 +133,8 @@ class Kohana_Debug {
|
||||
|
||||
if ($marker === NULL)
|
||||
{
|
||||
// Make a unique marker
|
||||
$marker = uniqid("\x00");
|
||||
// Make a unique marker - force it to be alphanumeric so that it is always treated as a string array key
|
||||
$marker = uniqid("\x00")."x";
|
||||
}
|
||||
|
||||
if (empty($var))
|
||||
|
@@ -36,10 +36,33 @@ class Kohana_Encrypt {
|
||||
public static $instances = array();
|
||||
|
||||
/**
|
||||
* @var string OS-dependent RAND type to use
|
||||
* @var string RAND type to use
|
||||
*
|
||||
* Only MCRYPT_DEV_URANDOM and MCRYPT_DEV_RANDOM are considered safe.
|
||||
* Using MCRYPT_RAND will silently revert to MCRYPT_DEV_URANDOM
|
||||
*/
|
||||
protected static $_rand;
|
||||
protected static $_rand = MCRYPT_DEV_URANDOM;
|
||||
|
||||
/**
|
||||
* @var string Encryption key
|
||||
*/
|
||||
protected $_key;
|
||||
|
||||
/**
|
||||
* @var string mcrypt mode
|
||||
*/
|
||||
protected $_mode;
|
||||
|
||||
/**
|
||||
* @var string mcrypt cipher
|
||||
*/
|
||||
protected $_cipher;
|
||||
|
||||
/**
|
||||
* @var int the size of the Initialization Vector (IV) in bytes
|
||||
*/
|
||||
protected $_iv_size;
|
||||
|
||||
/**
|
||||
* Returns a singleton instance of Encrypt. An encryption key must be
|
||||
* provided in your "encrypt" configuration file.
|
||||
@@ -105,6 +128,10 @@ class Kohana_Encrypt {
|
||||
// Shorten the key to the maximum size
|
||||
$key = substr($key, 0, $size);
|
||||
}
|
||||
else if (version_compare(PHP_VERSION, '5.6.0', '>='))
|
||||
{
|
||||
$key = $this->_normalize_key($key, $cipher, $mode);
|
||||
}
|
||||
|
||||
// Store the key, mode, and cipher
|
||||
$this->_key = $key;
|
||||
@@ -129,43 +156,8 @@ class Kohana_Encrypt {
|
||||
*/
|
||||
public function encode($data)
|
||||
{
|
||||
// Set the rand type if it has not already been set
|
||||
if (Encrypt::$_rand === NULL)
|
||||
{
|
||||
if (Kohana::$is_windows)
|
||||
{
|
||||
// Windows only supports the system random number generator
|
||||
Encrypt::$_rand = MCRYPT_RAND;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (defined('MCRYPT_DEV_URANDOM'))
|
||||
{
|
||||
// Use /dev/urandom
|
||||
Encrypt::$_rand = MCRYPT_DEV_URANDOM;
|
||||
}
|
||||
elseif (defined('MCRYPT_DEV_RANDOM'))
|
||||
{
|
||||
// Use /dev/random
|
||||
Encrypt::$_rand = MCRYPT_DEV_RANDOM;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the system random number generator
|
||||
Encrypt::$_rand = MCRYPT_RAND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Encrypt::$_rand === MCRYPT_RAND)
|
||||
{
|
||||
// The system random number generator must always be seeded each
|
||||
// time it is used, or it will not produce true random results
|
||||
mt_srand();
|
||||
}
|
||||
|
||||
// Create a random initialization vector of the proper size for the current cipher
|
||||
$iv = mcrypt_create_iv($this->_iv_size, Encrypt::$_rand);
|
||||
// Get an initialization vector
|
||||
$iv = $this->_create_iv();
|
||||
|
||||
// Encrypt the data using the configured options and generated iv
|
||||
$data = mcrypt_encrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv);
|
||||
@@ -210,4 +202,54 @@ class Kohana_Encrypt {
|
||||
return rtrim(mcrypt_decrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv), "\0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy for the mcrypt_create_iv function - to allow mocking and testing against KAT vectors
|
||||
*
|
||||
* @return string the initialization vector or FALSE on error
|
||||
*/
|
||||
protected function _create_iv()
|
||||
{
|
||||
/*
|
||||
* Silently use MCRYPT_DEV_URANDOM when the chosen random number generator
|
||||
* is not one of those that are considered secure.
|
||||
*
|
||||
* Also sets Encrypt::$_rand to MCRYPT_DEV_URANDOM when it's not already set
|
||||
*/
|
||||
if ((Encrypt::$_rand !== MCRYPT_DEV_URANDOM) AND ( Encrypt::$_rand !== MCRYPT_DEV_RANDOM))
|
||||
{
|
||||
Encrypt::$_rand = MCRYPT_DEV_URANDOM;
|
||||
}
|
||||
|
||||
// Create a random initialization vector of the proper size for the current cipher
|
||||
return mcrypt_create_iv($this->_iv_size, Encrypt::$_rand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize key for PHP 5.6 for backwards compatibility
|
||||
*
|
||||
* This method is a shim to make PHP 5.6 behave in a B/C way for
|
||||
* legacy key padding when shorter-than-supported keys are used
|
||||
*
|
||||
* @param string $key encryption key
|
||||
* @param string $cipher mcrypt cipher
|
||||
* @param string $mode mcrypt mode
|
||||
*/
|
||||
protected function _normalize_key($key, $cipher, $mode)
|
||||
{
|
||||
// open the cipher
|
||||
$td = mcrypt_module_open($cipher, '', $mode, '');
|
||||
|
||||
// loop through the supported key sizes
|
||||
foreach (mcrypt_enc_get_supported_key_sizes($td) as $supported) {
|
||||
// if key is short, needs padding
|
||||
if (strlen($key) <= $supported)
|
||||
{
|
||||
return str_pad($key, $supported, "\0");
|
||||
}
|
||||
}
|
||||
|
||||
// at this point key must be greater than max supported size, shorten it
|
||||
return substr($key, 0, mcrypt_get_key_size($cipher, $mode));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ class Kohana_Form {
|
||||
* @param mixed $action form action, defaults to the current request URI, or [Request] class to use
|
||||
* @param array $attributes html attributes
|
||||
* @return string
|
||||
* @uses Request::instance
|
||||
* @uses Request
|
||||
* @uses URL::site
|
||||
* @uses HTML::attributes
|
||||
*/
|
||||
|
@@ -126,9 +126,9 @@ class Kohana_HTML {
|
||||
$attributes['target'] = '_blank';
|
||||
}
|
||||
}
|
||||
elseif ($uri[0] !== '#')
|
||||
elseif ($uri[0] !== '#' AND $uri[0] !== '?')
|
||||
{
|
||||
// Make the URI absolute for non-id anchors
|
||||
// Make the URI absolute for non-fragment and non-query anchors
|
||||
$uri = URL::site($uri, $protocol, $index);
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@ class Kohana_HTML {
|
||||
*/
|
||||
public static function style($file, array $attributes = NULL, $protocol = NULL, $index = FALSE)
|
||||
{
|
||||
if (strpos($file, '://') === FALSE)
|
||||
if (strpos($file, '://') === FALSE AND strpos($file, '//') !== 0)
|
||||
{
|
||||
// Add the base URL
|
||||
$file = URL::site($file, $protocol, $index);
|
||||
@@ -239,7 +239,7 @@ class Kohana_HTML {
|
||||
*/
|
||||
public static function script($file, array $attributes = NULL, $protocol = NULL, $index = FALSE)
|
||||
{
|
||||
if (strpos($file, '://') === FALSE)
|
||||
if (strpos($file, '://') === FALSE AND strpos($file, '//') !== 0)
|
||||
{
|
||||
// Add the base URL
|
||||
$file = URL::site($file, $protocol, $index);
|
||||
|
@@ -95,7 +95,10 @@ abstract class Kohana_HTTP {
|
||||
if (extension_loaded('http'))
|
||||
{
|
||||
// Use the fast method to parse header string
|
||||
return new HTTP_Header(http_parse_headers($header_string));
|
||||
$headers = version_compare(phpversion('http'), '2.0.0', '>=') ?
|
||||
\http\Header::parse($header_string) :
|
||||
http_parse_headers($header_string);
|
||||
return new HTTP_Header($headers);
|
||||
}
|
||||
|
||||
// Otherwise we use the slower PHP parsing
|
||||
@@ -160,7 +163,10 @@ abstract class Kohana_HTTP {
|
||||
elseif (extension_loaded('http'))
|
||||
{
|
||||
// Return the much faster method
|
||||
return new HTTP_Header(http_get_request_headers());
|
||||
$headers = version_compare(phpversion('http'), '2.0.0', '>=') ?
|
||||
\http\Env::getRequestHeader() :
|
||||
http_get_request_headers();
|
||||
return new HTTP_Header($headers);
|
||||
}
|
||||
|
||||
// Setup the output
|
||||
@@ -186,8 +192,8 @@ abstract class Kohana_HTTP {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is a dirty hack to ensure HTTP_X_FOO_BAR becomes x-foo-bar
|
||||
$headers[str_replace(array('HTTP_', '_'), array('', '-'), $key)] = $value;
|
||||
// This is a dirty hack to ensure HTTP_X_FOO_BAR becomes X-FOO-BAR
|
||||
$headers[str_replace('_', '-', substr($key, 5))] = $value;
|
||||
}
|
||||
|
||||
return new HTTP_Header($headers);
|
||||
|
@@ -217,6 +217,16 @@ class Kohana_Kohana_Exception extends Exception {
|
||||
$frame['type'] = '??';
|
||||
}
|
||||
|
||||
// Xdebug returns the words 'dynamic' and 'static' instead of using '->' and '::' symbols
|
||||
if ('dynamic' === $frame['type'])
|
||||
{
|
||||
$frame['type'] = '->';
|
||||
}
|
||||
elseif ('static' === $frame['type'])
|
||||
{
|
||||
$frame['type'] = '::';
|
||||
}
|
||||
|
||||
// XDebug also has a different name for the parameters array
|
||||
if (isset($frame['params']) AND ! isset($frame['args']))
|
||||
{
|
||||
@@ -238,7 +248,13 @@ class Kohana_Kohana_Exception extends Exception {
|
||||
* The error view ends up several GB in size, taking
|
||||
* serveral minutes to render.
|
||||
*/
|
||||
if (defined('PHPUnit_MAIN_METHOD'))
|
||||
if (
|
||||
defined('PHPUnit_MAIN_METHOD')
|
||||
OR
|
||||
defined('PHPUNIT_COMPOSER_INSTALL')
|
||||
OR
|
||||
defined('__PHPUNIT_PHAR__')
|
||||
)
|
||||
{
|
||||
$trace = array_slice($trace, 0, 2);
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ class Kohana_Request implements HTTP_Request {
|
||||
|
||||
/**
|
||||
* Creates a new request object for the given URI. New requests should be
|
||||
* created using the [Request::instance] or [Request::factory] methods.
|
||||
* Created using the [Request::factory] method.
|
||||
*
|
||||
* $request = Request::factory($uri);
|
||||
*
|
||||
@@ -462,6 +462,12 @@ class Kohana_Request implements HTTP_Request {
|
||||
|
||||
foreach ($routes as $name => $route)
|
||||
{
|
||||
// Use external routes for reverse routing only
|
||||
if ($route->is_external())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We found something suitable
|
||||
if ($params = $route->matches($request))
|
||||
{
|
||||
@@ -631,7 +637,7 @@ class Kohana_Request implements HTTP_Request {
|
||||
|
||||
/**
|
||||
* Creates a new request object for the given URI. New requests should be
|
||||
* created using the [Request::instance] or [Request::factory] methods.
|
||||
* Created using the [Request::factory] method.
|
||||
*
|
||||
* $request = new Request($uri);
|
||||
*
|
||||
@@ -662,7 +668,7 @@ class Kohana_Request implements HTTP_Request {
|
||||
$uri = array_shift($split_uri);
|
||||
|
||||
// Initial request has global $_GET already applied
|
||||
if (Request::$initial !== NULL)
|
||||
if (Request::$initial === NULL)
|
||||
{
|
||||
if ($split_uri)
|
||||
{
|
||||
@@ -675,7 +681,7 @@ class Kohana_Request implements HTTP_Request {
|
||||
// being able to proxy external pages.
|
||||
if ( ! $allow_external OR strpos($uri, '://') === FALSE)
|
||||
{
|
||||
// Remove trailing slashes from the URI
|
||||
// Remove leading and trailing slashes from the URI
|
||||
$this->_uri = trim($uri, '/');
|
||||
|
||||
// Apply the client
|
||||
@@ -726,7 +732,7 @@ class Kohana_Request implements HTTP_Request {
|
||||
if ($uri === NULL)
|
||||
{
|
||||
// Act as a getter
|
||||
return empty($this->_uri) ? '/' : $this->_uri;
|
||||
return ($this->_uri === '') ? '/' : $this->_uri;
|
||||
}
|
||||
|
||||
// Act as a setter
|
||||
@@ -740,7 +746,6 @@ class Kohana_Request implements HTTP_Request {
|
||||
*
|
||||
* echo URL::site($this->request->uri(), $protocol);
|
||||
*
|
||||
* @param array $params URI parameters
|
||||
* @param mixed $protocol protocol string or Request object
|
||||
* @return string
|
||||
* @since 3.0.7
|
||||
@@ -748,7 +753,13 @@ class Kohana_Request implements HTTP_Request {
|
||||
*/
|
||||
public function url($protocol = NULL)
|
||||
{
|
||||
// Create a URI with the current route and convert it to a URL
|
||||
if ($this->is_external())
|
||||
{
|
||||
// If it's an external request return the URI
|
||||
return $this->uri();
|
||||
}
|
||||
|
||||
// Create a URI with the current route, convert to a URL and returns
|
||||
return URL::site($this->uri(), $protocol);
|
||||
}
|
||||
|
||||
@@ -1219,9 +1230,9 @@ class Kohana_Request implements HTTP_Request {
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->headers('content-type',
|
||||
'application/x-www-form-urlencoded; charset='.Kohana::$charset);
|
||||
$body = http_build_query($post, NULL, '&');
|
||||
$this->body($body)
|
||||
->headers('content-type', 'application/x-www-form-urlencoded; charset='.Kohana::$charset);
|
||||
}
|
||||
|
||||
// Set the content length
|
||||
|
@@ -26,7 +26,7 @@ abstract class Kohana_Request_Client {
|
||||
/**
|
||||
* @var array Headers to preserve when following a redirect
|
||||
*/
|
||||
protected $_follow_headers = array('Authorization');
|
||||
protected $_follow_headers = array('authorization');
|
||||
|
||||
/**
|
||||
* @var bool Follow 302 redirect with original request method?
|
||||
@@ -205,7 +205,7 @@ abstract class Kohana_Request_Client {
|
||||
if ($follow_headers === NULL)
|
||||
return $this->_follow_headers;
|
||||
|
||||
$this->_follow_headers = $follow_headers;
|
||||
$this->_follow_headers = array_map('strtolower', $follow_headers);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -407,7 +407,8 @@ abstract class Kohana_Request_Client {
|
||||
|
||||
// Prepare the additional request, copying any follow_headers that were present on the original request
|
||||
$orig_headers = $request->headers()->getArrayCopy();
|
||||
$follow_headers = array_intersect_assoc($orig_headers, array_fill_keys($client->follow_headers(), TRUE));
|
||||
$follow_header_keys = array_intersect(array_keys($orig_headers), $client->follow_headers());
|
||||
$follow_headers = \Arr::extract($orig_headers, $follow_header_keys);
|
||||
|
||||
$follow_request = Request::factory($response->headers('Location'))
|
||||
->method($follow_method)
|
||||
|
@@ -34,7 +34,10 @@ class Kohana_Request_Client_Curl extends Request_Client_External {
|
||||
// if using a request other than POST. PUT does support this method
|
||||
// and DOES NOT require writing data to disk before putting it, if
|
||||
// reading the PHP docs you may have got that impression. SdF
|
||||
$options[CURLOPT_POSTFIELDS] = $request->body();
|
||||
// This will also add a Content-Type: application/x-www-form-urlencoded header unless you override it
|
||||
if ($body = $request->body()) {
|
||||
$options[CURLOPT_POSTFIELDS] = $body;
|
||||
}
|
||||
|
||||
// Process headers
|
||||
if ($headers = $request->headers())
|
||||
|
@@ -127,6 +127,8 @@ abstract class Kohana_Request_Client_External extends Request_Client {
|
||||
->headers('content-type', 'application/x-www-form-urlencoded; charset='.Kohana::$charset);
|
||||
}
|
||||
|
||||
$request->headers('content-length', (string) $request->content_length());
|
||||
|
||||
// If Kohana expose, set the user-agent
|
||||
if (Kohana::$expose)
|
||||
{
|
||||
|
@@ -604,7 +604,10 @@ class Kohana_Response implements HTTP_Response {
|
||||
{
|
||||
if (extension_loaded('http'))
|
||||
{
|
||||
$this->_header['set-cookie'] = http_build_cookie($this->_cookies);
|
||||
$cookies = version_compare(phpversion('http'), '2.0.0', '>=') ?
|
||||
(string) new \http\Cookie($this->_cookies) :
|
||||
http_build_cookie($this->_cookies);
|
||||
$this->_header['set-cookie'] = $cookies;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -509,6 +509,14 @@ class Kohana_Route {
|
||||
*/
|
||||
public function uri(array $params = NULL)
|
||||
{
|
||||
if ($params)
|
||||
{
|
||||
// @issue #4079 rawurlencode parameters
|
||||
$params = array_map('rawurlencode', $params);
|
||||
// decode slashes back, see Apache docs about AllowEncodedSlashes and AcceptPathInfo
|
||||
$params = str_replace(array('%2F', '%5C'), array('/', '\\'), $params);
|
||||
}
|
||||
|
||||
$defaults = $this->_defaults;
|
||||
|
||||
/**
|
||||
|
@@ -28,8 +28,8 @@ class Kohana_Security {
|
||||
* And then check it when using [Validation]:
|
||||
*
|
||||
* $array->rules('csrf', array(
|
||||
* 'not_empty' => NULL,
|
||||
* 'Security::check' => NULL,
|
||||
* array('not_empty'),
|
||||
* array('Security::check'),
|
||||
* ));
|
||||
*
|
||||
* This provides a basic, but effective, method of preventing CSRF attacks.
|
||||
@@ -81,8 +81,29 @@ class Kohana_Security {
|
||||
*/
|
||||
public static function check($token)
|
||||
{
|
||||
return Security::token() === $token;
|
||||
return Security::slow_equals(Security::token(), $token);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Compare two hashes in a time-invariant manner.
|
||||
* Prevents cryptographic side-channel attacks (timing attacks, specifically)
|
||||
*
|
||||
* @param string $a cryptographic hash
|
||||
* @param string $b cryptographic hash
|
||||
* @return boolean
|
||||
*/
|
||||
public static function slow_equals($a, $b)
|
||||
{
|
||||
$diff = strlen($a) ^ strlen($b);
|
||||
for($i = 0; $i < strlen($a) AND $i < strlen($b); $i++)
|
||||
{
|
||||
$diff |= ord($a[$i]) ^ ord($b[$i]);
|
||||
}
|
||||
return $diff === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove image tags from a string.
|
||||
|
@@ -24,8 +24,31 @@ class Kohana_Session_Native extends Session {
|
||||
*/
|
||||
protected function _read($id = NULL)
|
||||
{
|
||||
/**
|
||||
* session_set_cookie_params will override php ini settings
|
||||
* If Cookie::$domain is NULL or empty and is passed, PHP
|
||||
* will override ini and sent cookies with the host name
|
||||
* of the server which generated the cookie
|
||||
*
|
||||
* see issue #3604
|
||||
*
|
||||
* see http://www.php.net/manual/en/function.session-set-cookie-params.php
|
||||
* see http://www.php.net/manual/en/session.configuration.php#ini.session.cookie-domain
|
||||
*
|
||||
* set to Cookie::$domain if available, otherwise default to ini setting
|
||||
*/
|
||||
$session_cookie_domain = empty(Cookie::$domain)
|
||||
? ini_get('session.cookie_domain')
|
||||
: Cookie::$domain;
|
||||
|
||||
// Sync up the session cookie with Cookie parameters
|
||||
session_set_cookie_params($this->_lifetime, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
|
||||
session_set_cookie_params(
|
||||
$this->_lifetime,
|
||||
Cookie::$path,
|
||||
$session_cookie_domain,
|
||||
Cookie::$secure,
|
||||
Cookie::$httponly
|
||||
);
|
||||
|
||||
// Do not allow PHP to send Cache-Control headers
|
||||
session_cache_limiter(FALSE);
|
||||
|
@@ -240,12 +240,13 @@ class Kohana_Text {
|
||||
*
|
||||
* @param string $string string to transform
|
||||
* @param string $delimiter delimiter to use
|
||||
* @uses UTF8::ucfirst
|
||||
* @return string
|
||||
*/
|
||||
public static function ucfirst($string, $delimiter = '-')
|
||||
{
|
||||
// Put the keys back the Case-Convention expected
|
||||
return implode($delimiter, array_map('ucfirst', explode($delimiter, $string)));
|
||||
return implode($delimiter, array_map('UTF8::ucfirst', explode($delimiter, $string)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,12 +294,15 @@ class Kohana_Text {
|
||||
|
||||
$regex = '!'.$regex.'!ui';
|
||||
|
||||
// if $replacement is a single character: replace each of the characters of the badword with $replacement
|
||||
if (UTF8::strlen($replacement) == 1)
|
||||
{
|
||||
$regex .= 'e';
|
||||
return preg_replace($regex, 'str_repeat($replacement, UTF8::strlen(\'$1\'))', $str);
|
||||
return preg_replace_callback($regex, function($matches) use ($replacement) {
|
||||
return str_repeat($replacement, UTF8::strlen($matches[1]));
|
||||
}, $str);
|
||||
}
|
||||
|
||||
// if $replacement is not a single character, fully replace the badword with $replacement
|
||||
return preg_replace($regex, $replacement, $str);
|
||||
}
|
||||
|
||||
@@ -587,35 +591,40 @@ class Kohana_Text {
|
||||
*
|
||||
* echo Text::widont($text);
|
||||
*
|
||||
* regex courtesy of the Typogrify project
|
||||
* @link http://code.google.com/p/typogrify/
|
||||
*
|
||||
* @param string $str text to remove widows from
|
||||
* @return string
|
||||
*/
|
||||
public static function widont($str)
|
||||
{
|
||||
$str = rtrim($str);
|
||||
$space = strrpos($str, ' ');
|
||||
|
||||
if ($space !== FALSE)
|
||||
{
|
||||
$str = substr($str, 0, $space).' '.substr($str, $space + 1);
|
||||
}
|
||||
|
||||
return $str;
|
||||
// use '%' as delimiter and 'x' as modifier
|
||||
$widont_regex = "%
|
||||
((?:</?(?:a|em|span|strong|i|b)[^>]*>)|[^<>\s]) # must be proceeded by an approved inline opening or closing tag or a nontag/nonspace
|
||||
\s+ # the space to replace
|
||||
([^<>\s]+ # must be flollowed by non-tag non-space characters
|
||||
\s* # optional white space!
|
||||
(</(a|em|span|strong|i|b)>\s*)* # optional closing inline tags with optional white space after each
|
||||
((</(p|h[1-6]|li|dt|dd)>)|$)) # end with a closing p, h1-6, li or the end of the string
|
||||
%x";
|
||||
return preg_replace($widont_regex, '$1 $2', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the client user agent.
|
||||
*
|
||||
* // Returns "Chrome" when using Google Chrome
|
||||
* $browser = Text::user_agent('browser');
|
||||
* $browser = Text::user_agent($agent, 'browser');
|
||||
*
|
||||
* Multiple values can be returned at once by using an array:
|
||||
*
|
||||
* // Get the browser and platform with a single call
|
||||
* $info = Text::user_agent(array('browser', 'platform'));
|
||||
* $info = Text::user_agent($agent, array('browser', 'platform'));
|
||||
*
|
||||
* When using an array for the value, an associative array will be returned.
|
||||
*
|
||||
* @param string $agent user_agent
|
||||
* @param mixed $value array or string to return: browser, version, robot, mobile, platform
|
||||
* @return mixed requested information, FALSE if nothing is found
|
||||
* @uses Kohana::$config
|
||||
@@ -649,7 +658,7 @@ class Kohana_Text {
|
||||
// Set the browser name
|
||||
$info['browser'] = $name;
|
||||
|
||||
if (preg_match('#'.preg_quote($search).'[^0-9.]*+([0-9.][0-9.a-z]*)#i', Request::$user_agent, $matches))
|
||||
if (preg_match('#'.preg_quote($search).'[^0-9.]*+([0-9.][0-9.a-z]*)#i', $agent, $matches))
|
||||
{
|
||||
// Set the version number
|
||||
$info['version'] = $matches[1];
|
||||
|
@@ -2,6 +2,8 @@
|
||||
/**
|
||||
* URL helper class.
|
||||
*
|
||||
* [!!] You need to setup the list of trusted hosts in the `url.php` config file, before starting using this helper class.
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Helpers
|
||||
* @author Kohana Team
|
||||
@@ -14,7 +16,9 @@ class Kohana_URL {
|
||||
* Gets the base URL to the application.
|
||||
* To specify a protocol, provide the protocol as a string or request object.
|
||||
* If a protocol is used, a complete URL will be generated using the
|
||||
* `$_SERVER['HTTP_HOST']` variable.
|
||||
* `$_SERVER['HTTP_HOST']` variable, which will be validated against RFC 952
|
||||
* and RFC 2181, as well as against the list of trusted hosts you have set
|
||||
* in the `url.php` config file.
|
||||
*
|
||||
* // Absolute URL path with no host or protocol
|
||||
* echo URL::base();
|
||||
@@ -75,7 +79,7 @@ class Kohana_URL {
|
||||
$port = ':'.$port;
|
||||
}
|
||||
|
||||
if ($domain = parse_url($base_url, PHP_URL_HOST))
|
||||
if ($host = parse_url($base_url, PHP_URL_HOST))
|
||||
{
|
||||
// Remove everything but the path from the URL
|
||||
$base_url = parse_url($base_url, PHP_URL_PATH);
|
||||
@@ -83,11 +87,32 @@ class Kohana_URL {
|
||||
else
|
||||
{
|
||||
// Attempt to use HTTP_HOST and fallback to SERVER_NAME
|
||||
$domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
|
||||
|
||||
// make $host lowercase
|
||||
$host = strtolower($host);
|
||||
|
||||
// check that host does not contain forbidden characters (see RFC 952 and RFC 2181)
|
||||
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
|
||||
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
|
||||
throw new Kohana_Exception(
|
||||
'Invalid host :host',
|
||||
array(':host' => $host)
|
||||
);
|
||||
}
|
||||
|
||||
// Validate $host, see if it matches trusted hosts
|
||||
if ( ! static::is_trusted_host($host))
|
||||
{
|
||||
throw new Kohana_Exception(
|
||||
'Untrusted host :host. If you trust :host, add it to the trusted hosts in the `url` config file.',
|
||||
array(':host' => $host)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the protocol and domain to the base URL
|
||||
$base_url = $protocol.'://'.$domain.$port.$base_url;
|
||||
$base_url = $protocol.'://'.$host.$port.$base_url;
|
||||
}
|
||||
|
||||
return $base_url;
|
||||
@@ -210,4 +235,41 @@ class Kohana_URL {
|
||||
return trim($title, $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given $host should be trusted.
|
||||
*
|
||||
* Tests against given $trusted_hosts
|
||||
* or looks for key `trusted_hosts` in `url` config
|
||||
*
|
||||
* @param string $host
|
||||
* @param array $trusted_hosts
|
||||
* @return boolean TRUE if $host is trustworthy
|
||||
*/
|
||||
public static function is_trusted_host($host, array $trusted_hosts = NULL)
|
||||
{
|
||||
|
||||
// If list of trusted hosts is not directly provided read from config
|
||||
if (empty($trusted_hosts))
|
||||
{
|
||||
$trusted_hosts = (array) Kohana::$config->load('url')->get('trusted_hosts');
|
||||
}
|
||||
|
||||
// loop through the $trusted_hosts array for a match
|
||||
foreach ($trusted_hosts as $trusted_host)
|
||||
{
|
||||
|
||||
// make sure we fully match the trusted hosts
|
||||
$pattern = '#^'.$trusted_host.'$#uD';
|
||||
|
||||
// return TRUE if there is match
|
||||
if (preg_match($pattern, $host)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return FALSE as nothing is matched
|
||||
return FALSE;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -70,13 +70,17 @@ class Kohana_UTF8 {
|
||||
|
||||
if ( ! UTF8::is_ascii($var))
|
||||
{
|
||||
// Disable notices
|
||||
$error_reporting = error_reporting(~E_NOTICE);
|
||||
// Temporarily save the mb_substitute_character() value into a variable
|
||||
$mb_substitute_character = mb_substitute_character();
|
||||
|
||||
// Disable substituting illegal characters with the default '?' character
|
||||
mb_substitute_character('none');
|
||||
|
||||
// convert encoding, this is expensive, used when $var is not ASCII
|
||||
$var = mb_convert_encoding($var, $charset, $charset);
|
||||
|
||||
// Turn notices back on
|
||||
error_reporting($error_reporting);
|
||||
// Reset mb_substitute_character() value back to the original setting
|
||||
mb_substitute_character($mb_substitute_character);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -219,7 +219,7 @@ class Kohana_Validation implements ArrayAccess {
|
||||
if ($field !== TRUE AND ! isset($this->_labels[$field]))
|
||||
{
|
||||
// Set the field label to the field name
|
||||
$this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
|
||||
$this->_labels[$field] = $field;
|
||||
}
|
||||
|
||||
// Store the rule and params for this rule
|
||||
@@ -430,6 +430,13 @@ class Kohana_Validation implements ArrayAccess {
|
||||
}
|
||||
}
|
||||
|
||||
// Unbind all the automatic bindings to avoid memory leaks.
|
||||
unset($this->_bound[':validation']);
|
||||
unset($this->_bound[':data']);
|
||||
unset($this->_bound[':field']);
|
||||
unset($this->_bound[':value']);
|
||||
|
||||
|
||||
// Restore the data to its original form
|
||||
$this->_data = $original;
|
||||
|
||||
|
@@ -40,6 +40,7 @@ class Kohana_View {
|
||||
* @param string $kohana_view_filename filename
|
||||
* @param array $kohana_view_data variables
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function capture($kohana_view_filename, array $kohana_view_data)
|
||||
{
|
||||
@@ -79,17 +80,25 @@ class Kohana_View {
|
||||
*
|
||||
* View::set_global($name, $value);
|
||||
*
|
||||
* @param string $key variable name or an array of variables
|
||||
* @param mixed $value value
|
||||
* You can also use an array or Traversable object to set several values at once:
|
||||
*
|
||||
* // Create the values $food and $beverage in the view
|
||||
* View::set_global(array('food' => 'bread', 'beverage' => 'water'));
|
||||
*
|
||||
* [!!] Note: When setting with using Traversable object we're not attaching the whole object to the view,
|
||||
* i.e. the object's standard properties will not be available in the view context.
|
||||
*
|
||||
* @param string|array|Traversable $key variable name or an array of variables
|
||||
* @param mixed $value value
|
||||
* @return void
|
||||
*/
|
||||
public static function set_global($key, $value = NULL)
|
||||
{
|
||||
if (is_array($key))
|
||||
if (is_array($key) OR $key instanceof Traversable)
|
||||
{
|
||||
foreach ($key as $key2 => $value)
|
||||
foreach ($key as $name => $value)
|
||||
{
|
||||
View::$_global_data[$key2] = $value;
|
||||
View::$_global_data[$name] = $value;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -127,7 +136,6 @@ class Kohana_View {
|
||||
*
|
||||
* @param string $file view filename
|
||||
* @param array $data array of values
|
||||
* @return void
|
||||
* @uses View::set_filename
|
||||
*/
|
||||
public function __construct($file = NULL, array $data = NULL)
|
||||
@@ -272,18 +280,21 @@ class Kohana_View {
|
||||
* // This value can be accessed as $foo within the view
|
||||
* $view->set('foo', 'my value');
|
||||
*
|
||||
* You can also use an array to set several values at once:
|
||||
* You can also use an array or Traversable object to set several values at once:
|
||||
*
|
||||
* // Create the values $food and $beverage in the view
|
||||
* $view->set(array('food' => 'bread', 'beverage' => 'water'));
|
||||
*
|
||||
* @param string $key variable name or an array of variables
|
||||
* @param mixed $value value
|
||||
* [!!] Note: When setting with using Traversable object we're not attaching the whole object to the view,
|
||||
* i.e. the object's standard properties will not be available in the view context.
|
||||
*
|
||||
* @param string|array|Traversable $key variable name or an array of variables
|
||||
* @param mixed $value value
|
||||
* @return $this
|
||||
*/
|
||||
public function set($key, $value = NULL)
|
||||
{
|
||||
if (is_array($key))
|
||||
if (is_array($key) OR $key instanceof Traversable)
|
||||
{
|
||||
foreach ($key as $name => $value)
|
||||
{
|
||||
|
Reference in New Issue
Block a user