diff --git a/system/.travis.yml b/system/.travis.yml
new file mode 100644
index 0000000..3f9e63a
--- /dev/null
+++ b/system/.travis.yml
@@ -0,0 +1,37 @@
+sudo: false
+
+language: php
+
+# Only build the main develop/master branches - feature branches will be covered by PRs
+branches:
+ only:
+ - /^[0-9\.]+\/(develop|master)$/
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - hhvm
+
+matrix:
+ include:
+ - php: 5.3
+ env: 'COMPOSER_PHPUNIT="lowest"'
+
+before_script:
+ - composer self-update
+ - COMPOSER_ROOT_VERSION=3.3.x-dev composer install --prefer-dist --no-interaction
+ - if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then COMPOSER_ROOT_VERSION=3.3.x-dev composer update --prefer-lowest --with-dependencies phpunit/phpunit; fi;
+ - vendor/bin/koharness
+
+script:
+ - cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php
+
+notifications:
+ email: false
diff --git a/system/CONTRIBUTING.md b/system/CONTRIBUTING.md
new file mode 100644
index 0000000..3abb5dd
--- /dev/null
+++ b/system/CONTRIBUTING.md
@@ -0,0 +1 @@
+Please refer to the CONTRIBUTING file located under the [kohana](https://github.com/kohana/kohana) repo.
\ No newline at end of file
diff --git a/system/README.md b/system/README.md
new file mode 100644
index 0000000..b9bb295
--- /dev/null
+++ b/system/README.md
@@ -0,0 +1,33 @@
+# Kohana PHP Framework - core
+
+| ver | Stable | Develop |
+|-------|------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
+| 3.3.x | [![Build Status - 3.3/master](https://travis-ci.org/kohana/core.svg?branch=3.3%2Fmaster)](https://travis-ci.org/kohana/core) | [![Build Status - 3.3/develop](https://travis-ci.org/kohana/core.svg?branch=3.3%2Fdevelop)](https://travis-ci.org/kohana/core) |
+| 3.4.x | [![Build Status - 3.4/master](https://travis-ci.org/kohana/core.svg?branch=3.4%2Fmaster)](https://travis-ci.org/kohana/core) | [![Build Status - 3.4/develop](https://travis-ci.org/kohana/core.svg?branch=3.4%2Fdevelop)](https://travis-ci.org/kohana/core) |
+
+This is the core package for the [Kohana](http://kohanaframework.org/) object oriented HMVC framework built using PHP5.
+It aims to be swift, secure, and small.
+
+Released under a [BSD license](http://kohanaframework.org/license), Kohana can be used legally for any open source,
+commercial, or personal project.
+
+## Documentation and installation
+
+See the [sample application repository](https://github.com/kohana/kohana) for full readme and contributing information.
+You will usually add `kohana/core` as a dependency in your own project's composer.json to install and work with this
+package.
+
+## Installation for development
+
+To work on this package, you'll want to install it with composer to get the required dependencies. Note that there are
+currently circular dependencies between this module and kohana/unittest. These may cause you problems if you are working
+on a feature branch, because composer may not be able to figure out which version of kohana core you have.
+
+To work around this, run composer like: `COMPOSER_ROOT_VERSION=3.3.x-dev composer install`. This tells composer that the
+current checkout is a 3.3.* development version. Obviously change the argument if your branch is based on a different
+version.
+
+After installing the dependencies, you'll need a skeleton Kohana application before you can run the unit tests etc. The
+simplest way to do this is to use kohana/koharness to build a bare project in `/tmp/koharness`.
+
+If in doubt, check the install and test steps in the [.travis.yml](.travis.yml) file.
diff --git a/system/classes/Kohana/Arr.php b/system/classes/Kohana/Arr.php
index 766369a..9d906f2 100644
--- a/system/classes/Kohana/Arr.php
+++ b/system/classes/Kohana/Arr.php
@@ -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))
{
diff --git a/system/classes/Kohana/Cookie.php b/system/classes/Kohana/Cookie.php
index dafb7f5..e68f84f 100644
--- a/system/classes/Kohana/Cookie.php
+++ b/system/classes/Kohana/Cookie.php
@@ -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();
}
}
diff --git a/system/classes/Kohana/Core.php b/system/classes/Kohana/Core.php
index 081d4bb..860090d 100644
--- a/system/classes/Kohana/Core.php
+++ b/system/classes/Kohana/Core.php
@@ -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);
diff --git a/system/classes/Kohana/Date.php b/system/classes/Kohana/Date.php
index 692d658..930edc3 100644
--- a/system/classes/Kohana/Date.php
+++ b/system/classes/Kohana/Date.php
@@ -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);
}
diff --git a/system/classes/Kohana/Debug.php b/system/classes/Kohana/Debug.php
index 5a82bb6..9106b00 100644
--- a/system/classes/Kohana/Debug.php
+++ b/system/classes/Kohana/Debug.php
@@ -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))
diff --git a/system/classes/Kohana/Encrypt.php b/system/classes/Kohana/Encrypt.php
index 6428607..5c59f83 100644
--- a/system/classes/Kohana/Encrypt.php
+++ b/system/classes/Kohana/Encrypt.php
@@ -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));
+ }
+
}
diff --git a/system/classes/Kohana/Form.php b/system/classes/Kohana/Form.php
index 510bd81..65ed30b 100644
--- a/system/classes/Kohana/Form.php
+++ b/system/classes/Kohana/Form.php
@@ -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
*/
diff --git a/system/classes/Kohana/HTML.php b/system/classes/Kohana/HTML.php
index a78bc76..9752a1e 100644
--- a/system/classes/Kohana/HTML.php
+++ b/system/classes/Kohana/HTML.php
@@ -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);
diff --git a/system/classes/Kohana/HTTP.php b/system/classes/Kohana/HTTP.php
index eca52c4..4c22c52 100644
--- a/system/classes/Kohana/HTTP.php
+++ b/system/classes/Kohana/HTTP.php
@@ -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);
diff --git a/system/classes/Kohana/Kohana/Exception.php b/system/classes/Kohana/Kohana/Exception.php
index d54b464..d11a2f4 100644
--- a/system/classes/Kohana/Kohana/Exception.php
+++ b/system/classes/Kohana/Kohana/Exception.php
@@ -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);
}
diff --git a/system/classes/Kohana/Request.php b/system/classes/Kohana/Request.php
index ba77385..84bf9dc 100644
--- a/system/classes/Kohana/Request.php
+++ b/system/classes/Kohana/Request.php
@@ -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
diff --git a/system/classes/Kohana/Request/Client.php b/system/classes/Kohana/Request/Client.php
index 1190a30..3c35faf 100644
--- a/system/classes/Kohana/Request/Client.php
+++ b/system/classes/Kohana/Request/Client.php
@@ -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)
diff --git a/system/classes/Kohana/Request/Client/Curl.php b/system/classes/Kohana/Request/Client/Curl.php
index c5dffa6..c9c7e21 100644
--- a/system/classes/Kohana/Request/Client/Curl.php
+++ b/system/classes/Kohana/Request/Client/Curl.php
@@ -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())
diff --git a/system/classes/Kohana/Request/Client/External.php b/system/classes/Kohana/Request/Client/External.php
index 985b915..363d331 100644
--- a/system/classes/Kohana/Request/Client/External.php
+++ b/system/classes/Kohana/Request/Client/External.php
@@ -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)
{
diff --git a/system/classes/Kohana/Response.php b/system/classes/Kohana/Response.php
index 6869979..298158d 100644
--- a/system/classes/Kohana/Response.php
+++ b/system/classes/Kohana/Response.php
@@ -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
{
diff --git a/system/classes/Kohana/Route.php b/system/classes/Kohana/Route.php
index 718bf17..976c46f 100644
--- a/system/classes/Kohana/Route.php
+++ b/system/classes/Kohana/Route.php
@@ -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;
/**
diff --git a/system/classes/Kohana/Security.php b/system/classes/Kohana/Security.php
index b8f66c6..214e8b1 100644
--- a/system/classes/Kohana/Security.php
+++ b/system/classes/Kohana/Security.php
@@ -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.
diff --git a/system/classes/Kohana/Session/Native.php b/system/classes/Kohana/Session/Native.php
index a5c8917..ffce159 100644
--- a/system/classes/Kohana/Session/Native.php
+++ b/system/classes/Kohana/Session/Native.php
@@ -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);
diff --git a/system/classes/Kohana/Text.php b/system/classes/Kohana/Text.php
index 7514fd6..828c595 100644
--- a/system/classes/Kohana/Text.php
+++ b/system/classes/Kohana/Text.php
@@ -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];
diff --git a/system/classes/Kohana/URL.php b/system/classes/Kohana/URL.php
index b758095..62144f5 100644
--- a/system/classes/Kohana/URL.php
+++ b/system/classes/Kohana/URL.php
@@ -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;
+
+ }
}
diff --git a/system/classes/Kohana/UTF8.php b/system/classes/Kohana/UTF8.php
index ca5e315..fec509e 100644
--- a/system/classes/Kohana/UTF8.php
+++ b/system/classes/Kohana/UTF8.php
@@ -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);
}
}
diff --git a/system/classes/Kohana/Validation.php b/system/classes/Kohana/Validation.php
index 4b46497..71f2ea8 100644
--- a/system/classes/Kohana/Validation.php
+++ b/system/classes/Kohana/Validation.php
@@ -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;
diff --git a/system/classes/Kohana/View.php b/system/classes/Kohana/View.php
index 662a932..3ca5ba0 100644
--- a/system/classes/Kohana/View.php
+++ b/system/classes/Kohana/View.php
@@ -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)
{
diff --git a/system/composer.json b/system/composer.json
index 2c07aa8..9a88f40 100644
--- a/system/composer.json
+++ b/system/composer.json
@@ -1,4 +1,5 @@
{
+ "_readme": "NOTE: see readme for COMPOSER_ROOT_VERSION instructions if you have dependency issues",
"name": "kohana/core",
"description": "Core system classes for the Kohana application framework",
"homepage": "http://kohanaframework.org",
@@ -21,12 +22,19 @@
"require": {
"php": ">=5.3.3"
},
+ "require-dev": {
+ "kohana/unittest": "3.3.*@dev",
+ "kohana/koharness": "*@dev"
+ },
"suggest": {
"ext-http": "*",
"ext-curl": "*",
"ext-mcrypt": "*"
},
"extra": {
+ "installer-paths": {
+ "vendor/{$vendor}/{$name}": ["type:kohana-module"]
+ },
"branch-alias": {
"dev-3.3/develop": "3.3.x-dev",
"dev-3.4/develop": "3.4.x-dev"
diff --git a/system/config/mimes.php b/system/config/mimes.php
index 2e31b0b..ba2fe4a 100644
--- a/system/config/mimes.php
+++ b/system/config/mimes.php
@@ -192,6 +192,7 @@ return array(
'wav' => array('audio/x-wav'),
'wax' => array('audio/x-ms-wax'),
'wbxml' => array('application/wbxml'),
+ 'webapp' => array('application/x-web-app-manifest+json'),
'webm' => array('video/webm'),
'wm' => array('video/x-ms-wm'),
'wma' => array('audio/x-ms-wma'),
diff --git a/system/config/url.php b/system/config/url.php
new file mode 100644
index 0000000..96bd293
--- /dev/null
+++ b/system/config/url.php
@@ -0,0 +1,18 @@
+ array(
+ // Set up your hostnames here
+ //
+ // Example:
+ //
+ // 'example\.org',
+ // '.*\.example\.org',
+ //
+ // Do not forget to escape your dots (.) as these are regex patterns.
+ // These patterns should always fully match,
+ // as they are prepended with `^` and appended with `$`
+ ),
+
+);
diff --git a/system/config/user_agents.php b/system/config/user_agents.php
index f4b92ea..78f03c0 100644
--- a/system/config/user_agents.php
+++ b/system/config/user_agents.php
@@ -3,6 +3,8 @@
return array(
'platform' => array(
+ 'windows nt 10.0'=> 'Windows 10',
+ 'windows nt 6.3' => 'Windows 8.1',
'windows nt 6.2' => 'Windows 8',
'windows nt 6.1' => 'Windows 7',
'windows nt 6.0' => 'Windows Vista',
@@ -47,6 +49,7 @@ return array(
'browser' => array(
'Opera' => 'Opera',
+ 'Edge/12' => 'Edge',
'MSIE' => 'Internet Explorer',
'Internet Explorer' => 'Internet Explorer',
'Shiira' => 'Shiira',
diff --git a/system/guide/kohana/bootstrap.md b/system/guide/kohana/bootstrap.md
index 22c560f..dd66e66 100644
--- a/system/guide/kohana/bootstrap.md
+++ b/system/guide/kohana/bootstrap.md
@@ -52,7 +52,7 @@ You can add conditional statements to make the bootstrap have different values b
/**
* Set the environment status by the domain.
*/
-if (strpos($_SERVER['HTTP_HOST'], 'kohanaphp.com') !== FALSE)
+if (strpos($_SERVER['HTTP_HOST'], 'kohanaframework.org') !== FALSE)
{
// We are live!
Kohana::$environment = Kohana::PRODUCTION;
@@ -66,7 +66,7 @@ if (strpos($_SERVER['HTTP_HOST'], 'kohanaphp.com') !== FALSE)
... [trimmed]
*/
Kohana::init(array(
- 'base_url' => Kohana::$environment === Kohana::PRODUCTION ? '/' : '/kohanaphp.com/',
+ 'base_url' => Kohana::$environment === Kohana::PRODUCTION ? '/' : '/kohanaframework.org/',
'caching' => Kohana::$environment === Kohana::PRODUCTION,
'profile' => Kohana::$environment !== Kohana::PRODUCTION,
'index_file' => FALSE,
diff --git a/system/guide/kohana/flow.md b/system/guide/kohana/flow.md
index 81a2e54..76fdfe8 100644
--- a/system/guide/kohana/flow.md
+++ b/system/guide/kohana/flow.md
@@ -16,7 +16,7 @@ Every application follows the same flow:
* Includes each module's `init.php` file, if it exists.
* The `init.php` file can perform additional environment setup, including adding routes.
10. [Route::set] is called multiple times to define the [application routes](routing).
- 11. [Request::instance] is called to start processing the request.
+ 11. [Request::factory] is called to start processing the request.
1. Checks each route that has been set until a match is found.
2. Creates the controller instance and passes the request to it.
3. Calls the [Controller::before] method.
@@ -24,4 +24,4 @@ Every application follows the same flow:
5. Calls the [Controller::after] method.
* The above 5 steps can be repeated multiple times when using [HMVC sub-requests](requests).
3. Application flow returns to index.php
- 12. The main [Request] response is displayed
\ No newline at end of file
+ 12. The main [Request] response is displayed
diff --git a/system/guide/kohana/install.md b/system/guide/kohana/install.md
index f8e0e42..4e4d212 100644
--- a/system/guide/kohana/install.md
+++ b/system/guide/kohana/install.md
@@ -34,15 +34,27 @@ Kohana::init(array(
));
~~~
- - Make sure the `application/cache` and `application/logs` directories are writable by the web server.
+ - List your trusted hosts. Open `application/config/url.php` and add regex patterns of the hosts you expect your application to be accessible from.
+
+ [!!] Do not forget to escape your dots (.) as these are regex patterns. These patterns should always fully match, as they are prepended with `^` and appended with `$`.
~~~
-sudo chmod -R a+rwx application/cache
-sudo chmod -R a+rwx application/logs
+return array(
+ 'trusted_hosts' => array(
+ 'example\.org',
+ '.*\.example\.org',
+ ),
+);
~~~
- Define a salt for the `Cookie` class.
~~~
-Cookie::$salt = [really-long-cookie-salt-here]
+Cookie::$salt = 'some-really-long-cookie-salt-here';
+~~~
+
+ - Make sure the `application/cache` and `application/logs` directories are writable by the web server.
+~~~
+sudo chmod -R a+rwx application/cache
+sudo chmod -R a+rwx application/logs
~~~
[!!] Make sure to use a unique salt for your application and never to share it. Take a look at the [Cookies](cookies) page for more information on how cookies work in Kohana. If you do not define a `Cookie::$salt` value, Kohana will throw an exception when it encounters any cookie on your domain.
diff --git a/system/guide/kohana/menu.md b/system/guide/kohana/menu.md
index 01e1de4..60a1a7d 100644
--- a/system/guide/kohana/menu.md
+++ b/system/guide/kohana/menu.md
@@ -20,6 +20,7 @@
- [Error Handling](errors)
- [Tips & Common Mistakes](tips)
- [Upgrading from v3.2](upgrading)
+ - [Upgrading from v3.3.3.1](upgrading-from-3-3-3-1)
- Basic Usage
- [Debugging](debugging)
- [Loading Classes](autoloading)
diff --git a/system/guide/kohana/mvc/controllers.md b/system/guide/kohana/mvc/controllers.md
index 3a155db..2c9f8ab 100644
--- a/system/guide/kohana/mvc/controllers.md
+++ b/system/guide/kohana/mvc/controllers.md
@@ -55,7 +55,7 @@ You can also have a controller extend another controller to share common things,
Every controller has the `$this->request` property which is the [Request] object that called the controller. You can use this to get information about the current request, as well as set the response body via `$this->response->body($ouput)`.
-Here is a partial list of the properties and methods available to `$this->request`. These can also be accessed via `Request::instance()`, but `$this->request` is provided as a shortcut. See the [Request] class for more information on any of these.
+Here is a partial list of the properties and methods available to `$this->request`. See the [Request] class for more information on any of these.
Property/method | What it does
--- | ---
diff --git a/system/guide/kohana/security/validation.md b/system/guide/kohana/security/validation.md
index 43b1e1c..baace7f 100644
--- a/system/guide/kohana/security/validation.md
+++ b/system/guide/kohana/security/validation.md
@@ -182,6 +182,7 @@ First, we need a [View] that contains the HTML form, which will be placed in `ap
+
diff --git a/system/guide/kohana/tutorials.md b/system/guide/kohana/tutorials.md
deleted file mode 100644
index c921bcd..0000000
--- a/system/guide/kohana/tutorials.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Tutorials
-
-## Tutorials in this guide
-
-## Tutorials written elsewhere
-
-### Ellisgl's KO3 tutorial on dealtaker.com:
-
-1. [Install and Basic Usage](http://www.dealtaker.com/blog/2009/11/20/kohana-php-3-0-ko3-tutorial-part-1/)
-2. [Views](http://www.dealtaker.com/blog/2009/12/07/kohana-php-3-0-ko3-tutorial-part-2/)
-3. [Controllers](http://www.dealtaker.com/blog/2009/12/30/kohana-php-3-0-ko3-tutorial-part-3/)
-4. [Models](http://www.dealtaker.com/blog/2010/02/01/kohana-php-3-0-ko3-tutorial-part-4/)
-5. [Subrequests](http://www.dealtaker.com/blog/2010/02/25/kohana-php-3-0-ko3-tutorial-part-5/)
-6. [Routes](http://www.dealtaker.com/blog/2010/03/03/kohana-php-3-0-ko3-tutorial-part-6/)
-7. [Helpers](http://www.dealtaker.com/blog/2010/03/26/kohana-php-3-0-ko3-tutorial-part-7/)
-8. [Modules](http://www.dealtaker.com/blog/2010/04/30/kohana-php-3-0-ko3-tutorial-part-8/)
-9. [Vendor Libraries](http://www.dealtaker.com/blog/2010/06/02/kohana-php-3-0-ko3-tutorial-part-9/)
\ No newline at end of file
diff --git a/system/guide/kohana/tutorials/templates.md b/system/guide/kohana/tutorials/templates.md
deleted file mode 100644
index 4bfd9c2..0000000
--- a/system/guide/kohana/tutorials/templates.md
+++ /dev/null
@@ -1,7 +0,0 @@
-Making a template driven site.
-
-
-
-
-
-
\ No newline at end of file
diff --git a/system/guide/kohana/tutorials/translation.md b/system/guide/kohana/tutorials/translation.md
deleted file mode 100644
index 12f7cfc..0000000
--- a/system/guide/kohana/tutorials/translation.md
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/system/guide/kohana/upgrading-from-3-3-3-1.md b/system/guide/kohana/upgrading-from-3-3-3-1.md
new file mode 100644
index 0000000..f4f07fd
--- /dev/null
+++ b/system/guide/kohana/upgrading-from-3-3-3-1.md
@@ -0,0 +1,23 @@
+# Upgrading from 3.3.3.1
+
+Minor version upgrades are usually done in a drop-in fashion. Unfortunately, however, upgrading from 3.3.3.1 to 3.3.4 needs a little configuration. This is because a [security disclosure from HP Fortify](https://github.com/kohana/kohana/issues/74), that unveiled a serious [host header attack](https://github.com/kohana/core/issues/613) vulnerability.
+
+[!!] You *might* still be able to have a drop-in upgrade, in case you have set the `base_url` in the [Kohana::init] call to an absolute URL. We advise you however that you follow the step below to make your application secure, in case some day you decide to change your `base_url` to a relative URL.
+
+## Trusted Hosts
+
+You need to setup a list of trusted hosts. Trusted hosts are hosts that you expect your application to be accessible from.
+
+Open `application/config/url.php` and add regex patterns of these hosts. An example is given hereunder:
+
+~~~
+return array(
+ 'trusted_hosts' => array(
+ 'example\.org',
+ '.*\.example\.org',
+ ),
+);
+~~~
+
+[!!] Do not forget to escape your dots (.) as these are regex patterns. These patterns should always fully match, as they are prepended with `^` and appended with `$`.
+
diff --git a/system/koharness.php b/system/koharness.php
new file mode 100644
index 0000000..7845498
--- /dev/null
+++ b/system/koharness.php
@@ -0,0 +1,8 @@
+ array(
+ 'unittest' => __DIR__ . '/vendor/kohana/unittest'
+ ),
+ 'syspath' => __DIR__,
+);
diff --git a/system/tests/kohana/ArrTest.php b/system/tests/kohana/ArrTest.php
index 12a85ef..a20ec23 100644
--- a/system/tests/kohana/ArrTest.php
+++ b/system/tests/kohana/ArrTest.php
@@ -655,6 +655,22 @@ class Kohana_ArrTest extends Unittest_TestCase
'bar' => 'foobar',
),
),
+ array(
+ 'strip_tags',
+ array(
+ array(
+ 'foo' => '