Kohana v3.3.5

This commit is contained in:
Deon George
2016-05-01 20:50:24 +10:00
parent 8888719653
commit 68c7f4f159
170 changed files with 4565 additions and 1176 deletions

View File

@@ -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))
{

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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))

View File

@@ -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));
}
}

View File

@@ -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
*/

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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())

View File

@@ -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)
{

View File

@@ -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
{

View File

@@ -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;
/**

View File

@@ -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.

View File

@@ -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);

View File

@@ -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).'&nbsp;'.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&nbsp;$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];

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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)
{