diff --git a/.gitignore b/.gitignore
index b72f9be20..093963d5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
*~
*.swp
+/composer.lock
+/vendor/*
+/koharness_bootstrap.php
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..603c929a3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+language: php
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - hhvm
+
+before_script:
+ - COMPOSER_ROOT_VERSION=3.3.x-dev composer install --prefer-dist
+ - 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/README.md b/README.md
new file mode 100644
index 000000000..4689a2182
--- /dev/null
+++ b/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
+pacakge.
+
+## 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/classes/Kohana/Arr.php b/classes/Kohana/Arr.php
index 766369a81..fec1c2fa9 100644
--- a/classes/Kohana/Arr.php
+++ b/classes/Kohana/Arr.php
@@ -279,7 +279,13 @@ public static function range($step = 10, $max = 100)
*/
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;
+ }
}
/**
diff --git a/classes/Kohana/Config/Group.php b/classes/Kohana/Config/Group.php
index 0460c898d..3e35fdf55 100644
--- a/classes/Kohana/Config/Group.php
+++ b/classes/Kohana/Config/Group.php
@@ -11,8 +11,8 @@
* @package Kohana
* @category Configuration
* @author Kohana Team
- * @copyright (c) 2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2012-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_Config_Group extends ArrayObject {
diff --git a/classes/Kohana/Config/Source.php b/classes/Kohana/Config/Source.php
index cfce07609..388986085 100644
--- a/classes/Kohana/Config/Source.php
+++ b/classes/Kohana/Config/Source.php
@@ -7,8 +7,8 @@
* @package Kohana
* @category Configuration
* @author Kohana Team
- * @copyright (c) 2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2012-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
interface Kohana_Config_Source {}
diff --git a/classes/Kohana/Config/Writer.php b/classes/Kohana/Config/Writer.php
index 3db22c037..856ebde8d 100644
--- a/classes/Kohana/Config/Writer.php
+++ b/classes/Kohana/Config/Writer.php
@@ -7,8 +7,8 @@
*
* @package Kohana
* @author Kohana Team
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
interface Kohana_Config_Writer extends Kohana_Config_Source
{
diff --git a/classes/Kohana/Cookie.php b/classes/Kohana/Cookie.php
index dafb7f5a4..e68f84fc0 100644
--- a/classes/Kohana/Cookie.php
+++ b/classes/Kohana/Cookie.php
@@ -71,14 +71,14 @@ public static function get($key, $default = NULL)
// 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 @@ public static function get($key, $default = NULL)
* 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 @@ public static function delete($name)
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 @@ public static function delete($name)
*
* $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 @@ public static function salt($name, $value)
// 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/classes/Kohana/Core.php b/classes/Kohana/Core.php
index 537ec6c56..30ef91755 100644
--- a/classes/Kohana/Core.php
+++ b/classes/Kohana/Core.php
@@ -16,8 +16,8 @@
class Kohana_Core {
// Release version and codename
- const VERSION = '3.3.2';
- const CODENAME = 'dryocopus';
+ const VERSION = '3.3.3';
+ const CODENAME = 'uluru';
// Common environment type constants for consistency and convenience
const PRODUCTION = 10;
@@ -322,7 +322,7 @@ public static function init(array $settings = NULL)
}
// 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 = get_magic_quotes_gpc();
// Sanitize all request variables
$_GET = Kohana::sanitize($_GET);
diff --git a/classes/Kohana/Date.php b/classes/Kohana/Date.php
index 692d658cb..930edc381 100644
--- a/classes/Kohana/Date.php
+++ b/classes/Kohana/Date.php
@@ -592,10 +592,10 @@ public static function formatted_time($datetime_str = 'now', $timestamp_format =
$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/classes/Kohana/Debug.php b/classes/Kohana/Debug.php
index 9d1efdf52..9106b00c1 100644
--- a/classes/Kohana/Debug.php
+++ b/classes/Kohana/Debug.php
@@ -5,8 +5,8 @@
* @package Kohana
* @category Base
* @author Kohana Team
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_Debug {
@@ -133,8 +133,8 @@ protected static function _dump( & $var, $length = 128, $limit = 10, $level = 0)
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/classes/Kohana/Encrypt.php b/classes/Kohana/Encrypt.php
index 642860769..5c59f8306 100644
--- a/classes/Kohana/Encrypt.php
+++ b/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 = MCRYPT_DEV_URANDOM;
+
+ /**
+ * @var string Encryption key
+ */
+ protected $_key;
+
+ /**
+ * @var string mcrypt mode
*/
- protected static $_rand;
+ 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 @@ public function __construct($key, $mode, $cipher)
// 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 @@ public function __construct($key, $mode, $cipher)
*/
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 @@ public function decode($data)
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/classes/Kohana/Form.php b/classes/Kohana/Form.php
index 510bd8109..65ed30bb6 100644
--- a/classes/Kohana/Form.php
+++ b/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/classes/Kohana/HTML.php b/classes/Kohana/HTML.php
index a78bc7621..9752a1e65 100644
--- a/classes/Kohana/HTML.php
+++ b/classes/Kohana/HTML.php
@@ -126,9 +126,9 @@ public static function anchor($uri, $title = NULL, array $attributes = NULL, $pr
$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 @@ public static function mailto($email, $title = NULL, array $attributes = NULL)
*/
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 @@ public static function style($file, array $attributes = NULL, $protocol = NULL,
*/
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/classes/Kohana/HTTP.php b/classes/Kohana/HTTP.php
index 083b43589..eca52c4e6 100644
--- a/classes/Kohana/HTTP.php
+++ b/classes/Kohana/HTTP.php
@@ -11,8 +11,8 @@
* @category HTTP
* @author Kohana Team
* @since 3.1.0
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
abstract class Kohana_HTTP {
diff --git a/classes/Kohana/HTTP/Exception.php b/classes/Kohana/HTTP/Exception.php
index cb292e3ec..65c4f31d9 100644
--- a/classes/Kohana/HTTP/Exception.php
+++ b/classes/Kohana/HTTP/Exception.php
@@ -46,7 +46,7 @@ public function __construct($message = NULL, array $variables = NULL, Exception
* Store the Request that triggered this exception.
*
* @param Request $request Request object that triggered this exception.
- * @return Response
+ * @return HTTP_Exception
*/
public function request(Request $request = NULL)
{
diff --git a/classes/Kohana/HTTP/Header.php b/classes/Kohana/HTTP/Header.php
index a74fbb2c6..0debabf6c 100644
--- a/classes/Kohana/HTTP/Header.php
+++ b/classes/Kohana/HTTP/Header.php
@@ -9,8 +9,8 @@
* @category HTTP
* @author Kohana Team
* @since 3.1.0
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_HTTP_Header extends ArrayObject {
@@ -287,7 +287,7 @@ public static function parse_cache_control($cache_control)
* @param int $flags Flags
* @param string $iterator_class The iterator class to use
*/
- public function __construct(array $input = array(), $flags = NULL, $iterator_class = 'ArrayIterator')
+ public function __construct(array $input = array(), $flags = 0, $iterator_class = 'ArrayIterator')
{
/**
* @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
diff --git a/classes/Kohana/HTTP/Message.php b/classes/Kohana/HTTP/Message.php
index 0861a733a..c24046ec1 100644
--- a/classes/Kohana/HTTP/Message.php
+++ b/classes/Kohana/HTTP/Message.php
@@ -7,8 +7,8 @@
* @category HTTP
* @author Kohana Team
* @since 3.1.0
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
interface Kohana_HTTP_Message {
diff --git a/classes/Kohana/HTTP/Request.php b/classes/Kohana/HTTP/Request.php
index 8b2169c5f..b8992725f 100644
--- a/classes/Kohana/HTTP/Request.php
+++ b/classes/Kohana/HTTP/Request.php
@@ -8,8 +8,8 @@
* @category HTTP
* @author Kohana Team
* @since 3.1.0
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
interface Kohana_HTTP_Request extends HTTP_Message {
diff --git a/classes/Kohana/HTTP/Response.php b/classes/Kohana/HTTP/Response.php
index 71058ac49..ddce6b4b9 100644
--- a/classes/Kohana/HTTP/Response.php
+++ b/classes/Kohana/HTTP/Response.php
@@ -8,8 +8,8 @@
* @category HTTP
* @author Kohana Team
* @since 3.1.0
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
interface Kohana_HTTP_Response extends HTTP_Message {
diff --git a/classes/Kohana/Kohana/Exception.php b/classes/Kohana/Kohana/Exception.php
index d54b46436..3cbe166bb 100644
--- a/classes/Kohana/Kohana/Exception.php
+++ b/classes/Kohana/Kohana/Exception.php
@@ -217,6 +217,16 @@ public static function response(Exception $e)
$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']))
{
diff --git a/classes/Kohana/Log/StdErr.php b/classes/Kohana/Log/StdErr.php
index 5097bb75d..53840387a 100644
--- a/classes/Kohana/Log/StdErr.php
+++ b/classes/Kohana/Log/StdErr.php
@@ -5,8 +5,8 @@
* @package Kohana
* @category Logging
* @author Kohana Team
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_Log_StdErr extends Log_Writer {
/**
diff --git a/classes/Kohana/Log/StdOut.php b/classes/Kohana/Log/StdOut.php
index b0cfeb380..608c65389 100644
--- a/classes/Kohana/Log/StdOut.php
+++ b/classes/Kohana/Log/StdOut.php
@@ -5,8 +5,8 @@
* @package Kohana
* @category Logging
* @author Kohana Team
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_Log_StdOut extends Log_Writer {
diff --git a/classes/Kohana/Request.php b/classes/Kohana/Request.php
index ba7738548..2fe0f29e8 100644
--- a/classes/Kohana/Request.php
+++ b/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 @@ public static function process(Request $request, $routes = NULL)
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 @@ protected static function _parse_accept( & $header, array $accepts = NULL)
/**
* 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);
*
@@ -740,7 +746,6 @@ public function uri($uri = NULL)
*
* 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 @@ public function uri($uri = NULL)
*/
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);
}
diff --git a/classes/Kohana/Request/Client.php b/classes/Kohana/Request/Client.php
index 26b744240..3c35faf61 100644
--- a/classes/Kohana/Request/Client.php
+++ b/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 @@ public function follow_headers($follow_headers = NULL)
if ($follow_headers === NULL)
return $this->_follow_headers;
- $this->_follow_headers = $follow_headers;
+ $this->_follow_headers = array_map('strtolower', $follow_headers);
return $this;
}
@@ -405,10 +405,14 @@ public static function on_header_location(Request $request, Response $response,
break;
}
- // Prepare the additional request
+ // Prepare the additional request, copying any follow_headers that were present on the original request
+ $orig_headers = $request->headers()->getArrayCopy();
+ $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)
- ->headers(Arr::extract($request->headers(), $client->follow_headers()));
+ ->headers($follow_headers);
if ($follow_method !== Request::GET)
{
diff --git a/classes/Kohana/Request/Client/Curl.php b/classes/Kohana/Request/Client/Curl.php
index c5dffa6f0..c9c7e21d2 100644
--- a/classes/Kohana/Request/Client/Curl.php
+++ b/classes/Kohana/Request/Client/Curl.php
@@ -34,7 +34,10 @@ public function _send_message(Request $request, Response $response)
// 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/classes/Kohana/Response.php b/classes/Kohana/Response.php
index 2a12e5e40..686997915 100644
--- a/classes/Kohana/Response.php
+++ b/classes/Kohana/Response.php
@@ -7,8 +7,8 @@
* @package Kohana
* @category Base
* @author Kohana Team
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
* @since 3.1.0
*/
class Kohana_Response implements HTTP_Response {
diff --git a/classes/Kohana/Route.php b/classes/Kohana/Route.php
index 718bf1796..976c46f5f 100644
--- a/classes/Kohana/Route.php
+++ b/classes/Kohana/Route.php
@@ -509,6 +509,14 @@ public function is_external()
*/
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/classes/Kohana/Security.php b/classes/Kohana/Security.php
index b8f66c635..b07e499e4 100644
--- a/classes/Kohana/Security.php
+++ b/classes/Kohana/Security.php
@@ -81,8 +81,29 @@ public static function token($new = FALSE)
*/
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/classes/Kohana/Session/Native.php b/classes/Kohana/Session/Native.php
index a5c8917b3..ffce15975 100644
--- a/classes/Kohana/Session/Native.php
+++ b/classes/Kohana/Session/Native.php
@@ -24,8 +24,31 @@ public function id()
*/
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/classes/Kohana/Text.php b/classes/Kohana/Text.php
index 7514fd66c..828c5950d 100644
--- a/classes/Kohana/Text.php
+++ b/classes/Kohana/Text.php
@@ -240,12 +240,13 @@ public static function random($type = NULL, $length = 8)
*
* @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 @@ public static function censor($str, $badwords, $replacement = '#', $replace_part
$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 @@ public static function number($number)
*
* 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 @@ public static function user_agent($agent, $value)
// 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/classes/Kohana/UTF8.php b/classes/Kohana/UTF8.php
index ca5e315a8..fec509e0f 100644
--- a/classes/Kohana/UTF8.php
+++ b/classes/Kohana/UTF8.php
@@ -70,13 +70,17 @@ public static function clean($var, $charset = NULL)
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/classes/Kohana/Validation.php b/classes/Kohana/Validation.php
index 4b46497e4..71f2ea86b 100644
--- a/classes/Kohana/Validation.php
+++ b/classes/Kohana/Validation.php
@@ -219,7 +219,7 @@ public function rule($field, $rule, array $params = NULL)
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 @@ public function check()
}
}
+ // 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/classes/Kohana/View.php b/classes/Kohana/View.php
index 9a45c3548..3ca5ba0ca 100644
--- a/classes/Kohana/View.php
+++ b/classes/Kohana/View.php
@@ -40,6 +40,7 @@ public static function factory($file = NULL, array $data = NULL)
* @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 @@ protected static function capture($kohana_view_filename, array $kohana_view_data
*
* 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 @@ public static function bind_global($key, & $value)
*
* @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)
@@ -232,7 +240,7 @@ public function __toString()
/**
* Display the exception message.
*
- * We use this method here because it's impossible to throw and
+ * We use this method here because it's impossible to throw an
* exception from __toString().
*/
$error_response = Kohana_Exception::_handler($e);
@@ -272,18 +280,21 @@ public function set_filename($file)
* // 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/composer.json b/composer.json
index 2c07aa849..9a88f4012 100644
--- a/composer.json
+++ b/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/config/user_agents.php b/config/user_agents.php
index f4b92eac4..3f631a4f0 100644
--- a/config/user_agents.php
+++ b/config/user_agents.php
@@ -3,6 +3,7 @@
return array(
'platform' => array(
+ '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',
diff --git a/guide/kohana/bootstrap.md b/guide/kohana/bootstrap.md
index 22c560f23..dd66e66dd 100644
--- a/guide/kohana/bootstrap.md
+++ b/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/guide/kohana/flow.md b/guide/kohana/flow.md
index 81a2e54b1..76fdfe8cb 100644
--- a/guide/kohana/flow.md
+++ b/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/guide/kohana/install.md b/guide/kohana/install.md
index f8e0e420b..287d8c616 100644
--- a/guide/kohana/install.md
+++ b/guide/kohana/install.md
@@ -34,15 +34,15 @@ Kohana::init(array(
));
~~~
- - Make sure the `application/cache` and `application/logs` directories are writable by the web server.
+ - Define a salt for the `Cookie` class.
~~~
-sudo chmod -R a+rwx application/cache
-sudo chmod -R a+rwx application/logs
+Cookie::$salt = 'some-really-long-cookie-salt-here';
~~~
- - Define a salt for the `Cookie` class.
+ - Make sure the `application/cache` and `application/logs` directories are writable by the web server.
~~~
-Cookie::$salt = [really-long-cookie-salt-here]
+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/guide/kohana/mvc/controllers.md b/guide/kohana/mvc/controllers.md
index 3a155dbff..2c9f8ab10 100644
--- a/guide/kohana/mvc/controllers.md
+++ b/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/guide/kohana/security/validation.md b/guide/kohana/security/validation.md
index 43b1e1c26..baace7f91 100644
--- a/guide/kohana/security/validation.md
+++ b/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/koharness.php b/koharness.php
new file mode 100644
index 000000000..7845498db
--- /dev/null
+++ b/koharness.php
@@ -0,0 +1,8 @@
+ array(
+ 'unittest' => __DIR__ . '/vendor/kohana/unittest'
+ ),
+ 'syspath' => __DIR__,
+);
diff --git a/tests/kohana/Config/File/ReaderTest.php b/tests/kohana/Config/File/ReaderTest.php
index ab22d14ae..bfc755eda 100644
--- a/tests/kohana/Config/File/ReaderTest.php
+++ b/tests/kohana/Config/File/ReaderTest.php
@@ -10,8 +10,8 @@
* @author Kohana Team
* @author Jeremy Bush
* @author Matt Button
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_Config_File_ReaderTest extends Kohana_Unittest_TestCase {
diff --git a/tests/kohana/Config/GroupTest.php b/tests/kohana/Config/GroupTest.php
index 66fd05856..6222c8df6 100644
--- a/tests/kohana/Config/GroupTest.php
+++ b/tests/kohana/Config/GroupTest.php
@@ -10,8 +10,8 @@
* @author Kohana Team
* @author Jeremy Bush
* @author Matt Button
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_Config_GroupTest extends Kohana_Unittest_TestCase
{
diff --git a/tests/kohana/CookieTest.php b/tests/kohana/CookieTest.php
index 9c1fe400c..25159b9eb 100644
--- a/tests/kohana/CookieTest.php
+++ b/tests/kohana/CookieTest.php
@@ -11,13 +11,15 @@
* @category Tests
* @author Kohana Team
* @author Jeremy Bush
- * @copyright (c) 2008-2012 Kohana Team
+ * @author Andrew Coulton
+ * @copyright (c) 2008-2014 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_CookieTest extends Unittest_TestCase
{
+ const UNIX_TIMESTAMP = 1411040141;
+ const COOKIE_EXPIRATION = 60;
- protected $_default_salt = 'AdaoidadnA£ASDNadnaoiwdnawd';
/**
* Sets up the environment
*/
@@ -26,152 +28,298 @@ public function setUp()
// @codingStandardsIgnoreEnd
{
parent::setUp();
+ Kohana_CookieTest_TestableCookie::$_mock_cookies_set = array();
- Cookie::$salt = $this->_default_salt;
+ $this->setEnvironment(array(
+ 'Cookie::$salt' => 'some-random-salt',
+ 'HTTP_USER_AGENT' => 'cli'
+ ));
}
/**
- * Tears down the environment
+ * Tests that cookies are set with the global path, domain, etc options.
+ *
+ * @covers Cookie::set
*/
- // @codingStandardsIgnoreStart
- public function tearDown()
- // @codingStandardsIgnoreEnd
+ public function test_set_creates_cookie_with_configured_cookie_options()
{
- parent::tearDown();
+ $this->setEnvironment(array(
+ 'Cookie::$path' => '/path',
+ 'Cookie::$domain' => 'my.domain',
+ 'Cookie::$secure' => TRUE,
+ 'Cookie::$httponly' => FALSE,
+ ));
- Cookie::$salt = NULL;
+ Kohana_CookieTest_TestableCookie::set('cookie', 'value');
+
+ $this->assertSetCookieWith(array(
+ 'path' => '/path',
+ 'domain' => 'my.domain',
+ 'secure' => TRUE,
+ 'httponly' => FALSE
+ ));
}
/**
- * Provides test data for test_set()
+ * Provider for test_set_calculates_expiry_from_lifetime
*
- * @return array
+ * @return array of $lifetime, $expect_expiry
*/
- public function provider_set()
+ public function provider_set_calculates_expiry_from_lifetime()
{
return array(
- array('foo', 'bar', NULL, TRUE),
- array('foo', 'bar', 10, TRUE),
+ array(NULL, self::COOKIE_EXPIRATION + self::UNIX_TIMESTAMP),
+ array(0, 0),
+ array(10, 10 + self::UNIX_TIMESTAMP),
);
}
/**
- * Tests cookie::set()
+ * @param int $expiration
+ * @param int $expect_expiry
*
- * @test
- * @dataProvider provider_set
- * @covers cookie::set
- * @param mixed $key key to use
- * @param mixed $value value to set
- * @param mixed $exp exp to set
- * @param boolean $expected Output for cookie::set()
- */
- public function test_set($key, $value, $exp, $expected)
- {
- if (headers_sent()) {
- $this->markTestSkipped('Cannot test setting cookies as headers have already been sent');
- }
+ * @dataProvider provider_set_calculates_expiry_from_lifetime
+ * @covers Cookie::set
+ */
+ public function test_set_calculates_expiry_from_lifetime($expiration, $expect_expiry)
+ {
+ $this->setEnvironment(array('Cookie::$expiration' => self::COOKIE_EXPIRATION));
+ Kohana_CookieTest_TestableCookie::set('foo', 'bar', $expiration);
+ $this->assertSetCookieWith(array('expire' => $expect_expiry));
+ }
+
+ /**
+ * @covers Cookie::get
+ */
+ public function test_get_returns_default_if_cookie_missing()
+ {
+ unset($_COOKIE['missing_cookie']);
+ $this->assertEquals('default', Cookie::get('missing_cookie', 'default'));
+ }
- $this->assertSame($expected, cookie::set($key, $value, $exp));
+ /**
+ * @covers Cookie::get
+ */
+ public function test_get_returns_value_if_cookie_present_and_signed()
+ {
+ Kohana_CookieTest_TestableCookie::set('cookie', 'value');
+ $cookie = Kohana_CookieTest_TestableCookie::$_mock_cookies_set[0];
+ $_COOKIE[$cookie['name']] = $cookie['value'];
+ $this->assertEquals('value', Cookie::get('cookie', 'default'));
}
/**
- * Provides test data for test_get()
+ * Provider for test_get_returns_default_without_deleting_if_cookie_unsigned
*
* @return array
*/
- public function provider_get()
+ public function provider_get_returns_default_without_deleting_if_cookie_unsigned()
{
- // setUp is called after the provider so we need to specify a
- // salt here in order to use it in the provider
- Cookie::$salt = $this->_default_salt;
-
return array(
- array('foo', Cookie::salt('foo', 'bar').'~bar', 'bar'),
- array('bar', Cookie::salt('foo', 'bar').'~bar', NULL),
- array(NULL, Cookie::salt('foo', 'bar').'~bar', NULL),
+ array('unsalted'),
+ array('un~salted'),
);
}
/**
- * Tests cookie::set()
+ * Verifies that unsigned cookies are not available to the kohana application, but are not affected for other
+ * consumers.
+ *
+ * @param string $unsigned_value
*
- * @test
- * @dataProvider provider_get
- * @covers cookie::get
- * @param mixed $key key to use
- * @param mixed $value value to set
- * @param boolean $expected Output for cookie::get()
+ * @dataProvider provider_get_returns_default_without_deleting_if_cookie_unsigned
+ * @covers Cookie::get
*/
- public function test_get($key, $value, $expected)
+ public function test_get_returns_default_without_deleting_if_cookie_unsigned($unsigned_value)
{
- if (headers_sent()) {
- $this->markTestSkipped('Cannot test setting cookies as headers have already been sent');
- }
+ $_COOKIE['cookie'] = $unsigned_value;
+ $this->assertEquals('default', Kohana_CookieTest_TestableCookie::get('cookie', 'default'));
+ $this->assertEquals($unsigned_value, $_COOKIE['cookie'], '$_COOKIE not affected');
+ $this->assertEmpty(Kohana_CookieTest_TestableCookie::$_mock_cookies_set, 'No cookies set or changed');
+ }
- // Force $_COOKIE
- if ($key !== NULL)
- {
- $_COOKIE[$key] = $value;
- }
+ /**
+ * If a cookie looks like a signed cookie but the signature no longer matches, it should be deleted.
+ *
+ * @covers Cookie::get
+ */
+ public function test_get_returns_default_and_deletes_tampered_signed_cookie()
+ {
+ $_COOKIE['cookie'] = Cookie::salt('cookie', 'value').'~tampered';
+ $this->assertEquals('default', Kohana_CookieTest_TestableCookie::get('cookie', 'default'));
+ $this->assertDeletedCookie('cookie');
+ }
+
+ /**
+ * @covers Cookie::delete
+ */
+ public function test_delete_removes_cookie_from_globals_and_expires_cookie()
+ {
+ $_COOKIE['cookie'] = Cookie::salt('cookie', 'value').'~tampered';
+ $this->assertTrue(Kohana_CookieTest_TestableCookie::delete('cookie'));
+ $this->assertDeletedCookie('cookie');
+ }
+
+ /**
+ * @covers Cookie::delete
+ * @link http://dev.kohanaframework.org/issues/3501
+ * @link http://dev.kohanaframework.org/issues/3020
+ */
+ public function test_delete_does_not_require_configured_salt()
+ {
+ Cookie::$salt = NULL;
+ $this->assertTrue(Kohana_CookieTest_TestableCookie::delete('cookie'));
+ $this->assertDeletedCookie('cookie');
+ }
+
+ /**
+ * @covers Cookie::salt
+ * @expectedException Kohana_Exception
+ */
+ public function test_salt_throws_with_no_configured_salt()
+ {
+ Cookie::$salt = NULL;
+ Cookie::salt('key', 'value');
+ }
- $this->assertSame($expected, cookie::get($key));
+ /**
+ * @covers Cookie::salt
+ */
+ public function test_salt_creates_same_hash_for_same_values_and_state()
+ {
+ $name = 'cookie';
+ $value = 'value';
+ $this->assertEquals(Cookie::salt($name, $value), Cookie::salt($name, $value));
}
/**
- * Provides test data for test_delete()
+ * Provider for test_salt_creates_different_hash_for_different_data
*
* @return array
*/
- public function provider_delete()
+ public function provider_salt_creates_different_hash_for_different_data()
{
return array(
- array('foo', TRUE),
+ array(array('name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'), array('name' => 'changed')),
+ array(array('name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'), array('value' => 'changed')),
+ array(array('name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'), array('salt' => 'changed-salt')),
+ array(array('name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'), array('user-agent' => 'Firefox')),
+ array(array('name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'), array('user-agent' => NULL)),
);
}
/**
- * Tests cookie::delete()
+ * @param array $first_args
+ * @param array $changed_args
*
- * @test
- * @dataProvider provider_delete
- * @covers cookie::delete
- * @param mixed $key key to use
- * @param boolean $expected Output for cookie::delete()
+ * @dataProvider provider_salt_creates_different_hash_for_different_data
+ * @covers Cookie::salt
*/
- public function test_delete($key, $expected)
+ public function test_salt_creates_different_hash_for_different_data($first_args, $changed_args)
{
- if (headers_sent()) {
- $this->markTestSkipped('Cannot test setting cookies as headers have already been sent');
+ $second_args = array_merge($first_args, $changed_args);
+ $hashes = array();
+ foreach (array($first_args, $second_args) as $args)
+ {
+ Cookie::$salt = $args['salt'];
+ $this->set_or_remove_http_user_agent($args['user-agent']);
+
+ $hashes[] = Cookie::salt($args['name'], $args['value']);
}
- $this->assertSame($expected, cookie::delete($key));
+ $this->assertNotEquals($hashes[0], $hashes[1]);
}
/**
- * Provides test data for test_salt()
+ * Verify that a cookie was deleted from the global $_COOKIE array, and that a setcookie call was made to remove it
+ * from the client.
*
- * @return array
+ * @param string $name
*/
- public function provider_salt()
+ // @codingStandardsIgnoreStart
+ protected function assertDeletedCookie($name)
+ // @codingStandardsIgnoreEnd
{
- return array(
- array('foo', 'bar', 'b5773a6255d1deefc23f9f69bcc40fdc998e5802'),
- );
+ $this->assertArrayNotHasKey($name, $_COOKIE);
+ // To delete the client-side cookie, Cookie::delete should send a new cookie with value NULL and expiry in the past
+ $this->assertSetCookieWith(array(
+ 'name' => $name,
+ 'value' => NULL,
+ 'expire' => -86400,
+ 'path' => Cookie::$path,
+ 'domain' => Cookie::$domain,
+ 'secure' => Cookie::$secure,
+ 'httponly' => Cookie::$httponly
+ ));
}
/**
- * Tests cookie::salt()
+ * Verify that there was a single call to setcookie including the provided named arguments
*
- * @test
- * @dataProvider provider_salt
- * @covers cookie::salt
- * @param mixed $key key to use
- * @param mixed $value value to salt with
- * @param boolean $expected Output for cookie::delete()
+ * @param array $expected
+ */
+ // @codingStandardsIgnoreStart
+ protected function assertSetCookieWith($expected)
+ // @codingStandardsIgnoreEnd
+ {
+ $this->assertCount(1, Kohana_CookieTest_TestableCookie::$_mock_cookies_set);
+ $relevant_values = array_intersect_key(Kohana_CookieTest_TestableCookie::$_mock_cookies_set[0], $expected);
+ $this->assertEquals($expected, $relevant_values);
+ }
+
+ /**
+ * Configure the $_SERVER[HTTP_USER_AGENT] environment variable for the test
+ *
+ * @param string $user_agent
+ */
+ protected function set_or_remove_http_user_agent($user_agent)
+ {
+ if ($user_agent === NULL)
+ {
+ unset($_SERVER['HTTP_USER_AGENT']);
+ }
+ else
+ {
+ $_SERVER['HTTP_USER_AGENT'] = $user_agent;
+ }
+ }
+}
+
+/**
+ * Class Kohana_CookieTest_TestableCookie wraps the cookie class to mock out the actual setcookie and time calls for
+ * unit testing.
+ */
+class Kohana_CookieTest_TestableCookie extends Cookie {
+
+ /**
+ * @var array setcookie calls that were made
*/
- public function test_salt($key, $value, $expected)
+ public static $_mock_cookies_set = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static function _setcookie($name, $value, $expire, $path, $domain, $secure, $httponly)
{
- $this->assertSame($expected, cookie::salt($key, $value));
+ self::$_mock_cookies_set[] = array(
+ 'name' => $name,
+ 'value' => $value,
+ 'expire' => $expire,
+ 'path' => $path,
+ 'domain' => $domain,
+ 'secure' => $secure,
+ 'httponly' => $httponly
+ );
+
+ return TRUE;
}
+
+ /**
+ * @return int
+ */
+ protected static function _time()
+ {
+ return Kohana_CookieTest::UNIX_TIMESTAMP;
+ }
+
}
diff --git a/tests/kohana/CoreTest.php b/tests/kohana/CoreTest.php
index a0099d103..bdb750914 100644
--- a/tests/kohana/CoreTest.php
+++ b/tests/kohana/CoreTest.php
@@ -18,6 +18,32 @@
*/
class Kohana_CoreTest extends Unittest_TestCase
{
+ protected $old_modules = array();
+
+ /**
+ * Captures the module list as it was before this test
+ *
+ * @return null
+ */
+ // @codingStandardsIgnoreStart
+ public function setUp()
+ // @codingStandardsIgnoreEnd
+ {
+ parent::setUp();
+ $this->old_modules = Kohana::modules();
+ }
+
+ /**
+ * Restores the module list
+ *
+ * @return null
+ */
+ // @codingStandardsIgnoreStart
+ public function tearDown()
+ // @codingStandardsIgnoreEnd
+ {
+ Kohana::modules($this->old_modules);
+ }
/**
* Provides test data for test_sanitize()
@@ -107,33 +133,15 @@ public function test_list_files_returns_array_on_success_and_failure()
*/
public function test_globals_removes_user_def_globals()
{
- // Store the globals
- $temp_globals = array(
- 'cookie' => $_COOKIE,
- 'get' => $_GET,
- 'files' => $_FILES,
- 'post' => $_POST,
- 'request' => $_REQUEST,
- 'server' => $_SERVER,
- 'session' => $_SESSION,
- 'globals' => $GLOBALS,
- );
-
- $GLOBALS = array('hackers' => 'foobar','name' => array('','',''), '_POST' => array());
+ $GLOBALS['hackers'] = 'foobar';
+ $GLOBALS['name'] = array('','','');
+ $GLOBALS['_POST'] = array();
Kohana::globals();
- $this->assertEquals(array('_POST' => array()), $GLOBALS);
-
- // Reset the globals for other tests
- $_COOKIE = $temp_globals['cookie'];
- $_GET = $temp_globals['get'];
- $_FILES = $temp_globals['files'];
- $_POST = $temp_globals['post'];
- $_REQUEST = $temp_globals['request'];
- $_SERVER = $temp_globals['server'];
- $_SESSION = $temp_globals['session'];
- $GLOBALS = $temp_globals['globals'];
+ $this->assertFalse(isset($GLOBALS['hackers']));
+ $this->assertFalse(isset($GLOBALS['name']));
+ $this->assertTrue(isset($GLOBALS['_POST']));
}
/**
@@ -175,35 +183,18 @@ public function test_cache($key, $value, $lifetime)
public function provider_message()
{
return array(
- // $value, $result
- array(':field must not be empty', 'validation', 'not_empty'),
- array(
+ array('no_message_file', 'anything', 'default', 'default'),
+ array('no_message_file', NULL, 'anything', array()),
+ array('kohana_core_message_tests', 'bottom_only', 'anything', 'inherited bottom message'),
+ array('kohana_core_message_tests', 'cfs_replaced', 'anything', 'overriding cfs_replaced message'),
+ array('kohana_core_message_tests', 'top_only', 'anything', 'top only message'),
+ array('kohana_core_message_tests', 'missing', 'default', 'default'),
+ array('kohana_core_message_tests', NULL, 'anything',
array(
- 'alpha' => ':field must contain only letters',
- 'alpha_dash' => ':field must contain only numbers, letters and dashes',
- 'alpha_numeric' => ':field must contain only letters and numbers',
- 'color' => ':field must be a color',
- 'credit_card' => ':field must be a credit card number',
- 'date' => ':field must be a date',
- 'decimal' => ':field must be a decimal with :param2 places',
- 'digit' => ':field must be a digit',
- 'email' => ':field must be a email address',
- 'email_domain' => ':field must contain a valid email domain',
- 'equals' => ':field must equal :param2',
- 'exact_length' => ':field must be exactly :param2 characters long',
- 'in_array' => ':field must be one of the available options',
- 'ip' => ':field must be an ip address',
- 'matches' => ':field must be the same as :param2',
- 'min_length' => ':field must be at least :param2 characters long',
- 'max_length' => ':field must not exceed :param2 characters long',
- 'not_empty' => ':field must not be empty',
- 'numeric' => ':field must be numeric',
- 'phone' => ':field must be a phone number',
- 'range' => ':field must be within the range of :param2 to :param3',
- 'regex' => ':field does not match the required format',
- 'url' => ':field must be a url',
- ),
- 'validation', NULL,
+ 'bottom_only' => 'inherited bottom message',
+ 'cfs_replaced' => 'overriding cfs_replaced message',
+ 'top_only' => 'top only message'
+ )
),
);
}
@@ -213,15 +204,18 @@ public function provider_message()
*
* @test
* @dataProvider provider_message
- * @covers Kohana::message
- * @param boolean $expected Output for Kohana::message
- * @param boolean $file File to look in for Kohana::message
- * @param boolean $key Key for Kohana::message
+ * @covers Kohana::message
+ * @param string $file to pass to Kohana::message
+ * @param string $key to pass to Kohana::message
+ * @param string $default to pass to Kohana::message
+ * @param string $expected Output for Kohana::message
*/
- public function test_message($expected, $file, $key)
+ public function test_message($file, $key, $default, $expected)
{
- $this->markTestSkipped('This test is incredibly fragile and needs to be re-done');
- $this->assertEquals($expected, Kohana::message($file, $key));
+ $test_path = realpath(dirname(__FILE__).'/../test_data/message_tests');
+ Kohana::modules(array('top' => "$test_path/top_module", 'bottom' => "$test_path/bottom_module"));
+
+ $this->assertEquals($expected, Kohana::message($file, $key, $default, $expected));
}
/**
@@ -314,7 +308,7 @@ public function provider_modules_sets_and_returns_valid_modules()
{
return array(
array(array(), array()),
- array(array('unittest' => MODPATH.'unittest'), array('unittest' => $this->dirSeparator(MODPATH.'unittest/'))),
+ array(array('module' => __DIR__), array('module' => $this->dirSeparator(__DIR__.'/'))),
);
}
diff --git a/tests/kohana/DebugTest.php b/tests/kohana/DebugTest.php
index 39176ec17..3f845361e 100644
--- a/tests/kohana/DebugTest.php
+++ b/tests/kohana/DebugTest.php
@@ -13,8 +13,8 @@
* @category Tests
* @author Kohana Team
* @author Jeremy Bush
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_DebugTest extends Unittest_TestCase
{
diff --git a/tests/kohana/EncryptTest.php b/tests/kohana/EncryptTest.php
new file mode 100644
index 000000000..9e93acabf
--- /dev/null
+++ b/tests/kohana/EncryptTest.php
@@ -0,0 +1,747 @@
+
+ * @copyright (c) 2014 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_EncryptTest extends Unittest_TestCase
+{
+
+ /**
+ * Provider for test_encode
+ * AES Multiblock Message Test (MMT) Sample Vectors - Known Answer Test (KAT)
+ * @link http://csrc.nist.gov/groups/STM/cavp/index.html NIST - Cryptographic Algorithm Validation Program
+ * @link http://csrc.nist.gov/groups/STM/cavp/documents/aes/aesmmt.zip file used CBCMMT128.rsp
+ *
+ * @return array of $mode, $cipher, $key, $iv, $txt_plain, $txt_encoded
+ */
+ public function provider_encode()
+ {
+ return array(
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "1f8e4973953f3fb0bd6b16662e9a3c17"),
+ // IV
+ pack("H*", "2fe2b333ceda8f98f4a99b40d2cd34a8"),
+ // txt_plain
+ pack("H*", "45cf12964fc824ab76616ae2f4bf0822"),
+ // txt_encoded
+ pack("H*", "0f61c4d44c5147c03c195ad7e2cc12b2"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "1f8e4973953f3fb0bd6b16662e9a3c17"),
+ // IV
+ pack("H*", "2fe2b333ceda8f98f4a99b40d2cd34a8"),
+ // txt_plain
+ pack("H*", "45cf12964fc824ab76616ae2f4bf0822"),
+ // txt_encoded
+ pack("H*", "0f61c4d44c5147c03c195ad7e2cc12b2"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "0700d603a1c514e46b6191ba430a3a0c"),
+ // IV
+ pack("H*", "aad1583cd91365e3bb2f0c3430d065bb"),
+ // txt_plain
+ pack("H*", "068b25c7bfb1f8bdd4cfc908f69dffc5ddc726a197f0e5f720f730393279be91"),
+ // txt_encoded
+ pack("H*", "c4dc61d9725967a3020104a9738f23868527ce839aab1752fd8bdb95a82c4d00"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "3348aa51e9a45c2dbe33ccc47f96e8de"),
+ // IV
+ pack("H*", "19153c673160df2b1d38c28060e59b96"),
+ // txt_plain
+ pack("H*", "9b7cee827a26575afdbb7c7a329f887238052e3601a7917456ba61251c214763d5e1847a6ad5d54127a399ab07ee3599"),
+ // txt_encoded
+ pack("H*", "d5aed6c9622ec451a15db12819952b6752501cf05cdbf8cda34a457726ded97818e1f127a28d72db5652749f0c6afee5"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "b7f3c9576e12dd0db63e8f8fac2b9a39"),
+ // IV
+ pack("H*", "c80f095d8bb1a060699f7c19974a1aa0"),
+ // txt_plain
+ pack("H*", "9ac19954ce1319b354d3220460f71c1e373f1cd336240881160cfde46ebfed2e791e8d5a1a136ebd1dc469dec00c4187722b841cdabcb22c1be8a14657da200e"),
+ // txt_encoded
+ pack("H*", "19b9609772c63f338608bf6eb52ca10be65097f89c1e0905c42401fd47791ae2c5440b2d473116ca78bd9ff2fb6015cfd316524eae7dcb95ae738ebeae84a467"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "b6f9afbfe5a1562bba1368fc72ac9d9c"),
+ // IV
+ pack("H*", "3f9d5ebe250ee7ce384b0d00ee849322"),
+ // txt_plain
+ pack("H*", "db397ec22718dbffb9c9d13de0efcd4611bf792be4fce0dc5f25d4f577ed8cdbd4eb9208d593dda3d4653954ab64f05676caa3ce9bfa795b08b67ceebc923fdc89a8c431188e9e482d8553982cf304d1"),
+ // txt_encoded
+ pack("H*", "10ea27b19e16b93af169c4a88e06e35c99d8b420980b058e34b4b8f132b13766f72728202b089f428fecdb41c79f8aa0d0ef68f5786481cca29e2126f69bc14160f1ae2187878ba5c49cf3961e1b7ee9"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "bbe7b7ba07124ff1ae7c3416fe8b465e"),
+ // IV
+ pack("H*", "7f65b5ee3630bed6b84202d97fb97a1e"),
+ // txt_plain
+ pack("H*", "2aad0c2c4306568bad7447460fd3dac054346d26feddbc9abd9110914011b4794be2a9a00a519a51a5b5124014f4ed2735480db21b434e99a911bb0b60fe0253763725b628d5739a5117b7ee3aefafc5b4c1bf446467e7bf5f78f31ff7caf187"),
+ // txt_encoded
+ pack("H*", "3b8611bfc4973c5cd8e982b073b33184cd26110159172e44988eb5ff5661a1e16fad67258fcbfee55469267a12dc374893b4e3533d36f5634c3095583596f135aa8cd1138dc898bc5651ee35a92ebf89ab6aeb5366653bc60a70e0074fc11efe"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "89a553730433f7e6d67d16d373bd5360"),
+ // IV
+ pack("H*", "f724558db3433a523f4e51a5bea70497"),
+ // txt_plain
+ pack("H*", "807bc4ea684eedcfdcca30180680b0f1ae2814f35f36d053c5aea6595a386c1442770f4d7297d8b91825ee7237241da8925dd594ccf676aecd46ca2068e8d37a3a0ec8a7d5185a201e663b5ff36ae197110188a23503763b8218826d23ced74b31e9f6e2d7fbfa6cb43420c7807a8625"),
+ // txt_encoded
+ pack("H*", "406af1429a478c3d07e555c5287a60500d37fc39b68e5bbb9bafd6ddb223828561d6171a308d5b1a4551e8a5e7d572918d25c968d3871848d2f16635caa9847f38590b1df58ab5efb985f2c66cfaf86f61b3f9c0afad6c963c49cee9b8bc81a2ddb06c967f325515a4849eec37ce721a"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "c491ca31f91708458e29a925ec558d78"),
+ // IV
+ pack("H*", "9ef934946e5cd0ae97bd58532cb49381"),
+ // txt_plain
+ pack("H*", "cb6a787e0dec56f9a165957f81af336ca6b40785d9e94093c6190e5152649f882e874d79ac5e167bd2a74ce5ae088d2ee854f6539e0a94796b1e1bd4c9fcdbc79acbef4d01eeb89776d18af71ae2a4fc47dd66df6c4dbe1d1850e466549a47b636bcc7c2b3a62495b56bb67b6d455f1eebd9bfefecbca6c7f335cfce9b45cb9d"),
+ // txt_encoded
+ pack("H*", "7b2931f5855f717145e00f152a9f4794359b1ffcb3e55f594e33098b51c23a6c74a06c1d94fded7fd2ae42c7db7acaef5844cb33aeddc6852585ed0020a6699d2cb53809cefd169148ce42292afab063443978306c582c18b9ce0da3d084ce4d3c482cfd8fcf1a85084e89fb88b40a084d5e972466d07666126fb761f84078f2"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "f6e87d71b0104d6eb06a68dc6a71f498"),
+ // IV
+ pack("H*", "1c245f26195b76ebebc2edcac412a2f8"),
+ // txt_plain
+ pack("H*", "f82bef3c73a6f7f80db285726d691db6bf55eec25a859d3ba0e0445f26b9bb3b16a3161ed1866e4dd8f2e5f8ecb4e46d74a7a78c20cdfc7bcc9e479ba7a0caba9438238ad0c01651d5d98de37f03ddce6e6b4bd4ab03cf9e8ed818aedfa1cf963b932067b97d776dce1087196e7e913f7448e38244509f0caf36bd8217e15336d35c149fd4e41707893fdb84014f8729"),
+ // txt_encoded
+ pack("H*", "b09512f3eff9ed0d85890983a73dadbb7c3678d52581be64a8a8fc586f490f2521297a478a0598040ebd0f5509fafb0969f9d9e600eaef33b1b93eed99687b167f89a5065aac439ce46f3b8d22d30865e64e45ef8cd30b6984353a844a11c8cd60dba0e8866b3ee30d24b3fa8a643b328353e06010fa8273c8fd54ef0a2b6930e5520aae5cd5902f9b86a33592ca4365"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "2c14413751c31e2730570ba3361c786b"),
+ // IV
+ pack("H*", "1dbbeb2f19abb448af849796244a19d7"),
+ // txt_plain
+ pack("H*", "40d930f9a05334d9816fe204999c3f82a03f6a0457a8c475c94553d1d116693adc618049f0a769a2eed6a6cb14c0143ec5cccdbc8dec4ce560cfd206225709326d4de7948e54d603d01b12d7fed752fb23f1aa4494fbb00130e9ded4e77e37c079042d828040c325b1a5efd15fc842e44014ca4374bf38f3c3fc3ee327733b0c8aee1abcd055772f18dc04603f7b2c1ea69ff662361f2be0a171bbdcea1e5d3f"),
+ // txt_encoded
+ pack("H*", "6be8a12800455a320538853e0cba31bd2d80ea0c85164a4c5c261ae485417d93effe2ebc0d0a0b51d6ea18633d210cf63c0c4ddbc27607f2e81ed9113191ef86d56f3b99be6c415a4150299fb846ce7160b40b63baf1179d19275a2e83698376d28b92548c68e06e6d994e2c1501ed297014e702cdefee2f656447706009614d801de1caaf73f8b7fa56cf1ba94b631933bbe577624380850f117435a0355b2b"),
+ ),
+ );
+ }
+
+ /**
+ * @param string $mode
+ * @param string $cipher
+ * @param string $key Encryption key
+ * @param string $iv Initialization vector
+ * @param string $txt_plain Plain text to be encrypted
+ * @param string $txt_encoded Known ecrypted text
+ *
+ * @dataProvider provider_encode
+ * @covers Encrypt::encode
+ */
+ public function test_encode($mode, $cipher, $key, $iv, $txt_plain, $txt_encoded)
+ {
+ // initialize
+ $e = new Kohana_EncryptTest_IvStubbed($key, $iv, $mode, $cipher);
+
+ // prepare data
+ $expected = base64_encode($iv . $txt_encoded);
+ $actual = $e->encode($txt_plain);
+
+ // assert
+ $this->assertSame($expected, $actual);
+ }
+
+ /**
+ * Provider for test_decode
+ * AES Multiblock Message Test (MMT) Sample Vectors - Known Answer Test (KAT)
+ * @link http://csrc.nist.gov/groups/STM/cavp/index.html NIST - Cryptographic Algorithm Validation Program
+ * @link http://csrc.nist.gov/groups/STM/cavp/documents/aes/aesmmt.zip file used CBCMMT128.rsp
+ *
+ * @return array of $mode, $cipher, $key, $iv, $txt_encoded, $txt_plain
+ */
+ public function provider_decode()
+ {
+ return array(
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "6a7082cf8cda13eff48c8158dda206ae"),
+ // IV
+ pack("H*", "bd4172934078c2011cb1f31cffaf486e"),
+ // txt_encoded
+ pack("H*", "f8eb31b31e374e960030cd1cadb0ef0c"),
+ // txt_plain
+ pack("H*", "940bc76d61e2c49dddd5df7f37fcf105"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "625eefa18a4756454e218d8bfed56e36"),
+ // IV
+ pack("H*", "73d9d0e27c2ec568fbc11f6a0998d7c8"),
+ // txt_encoded
+ pack("H*", "5d6fed86f0c4fe59a078d6361a142812514b295dc62ff5d608a42ea37614e6a1"),
+ // txt_plain
+ pack("H*", "360dc1896ce601dfb2a949250067aad96737847a4580ede2654a329b842fe81e"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "fd6e0b954ae2e3b723d6c9fcae6ab09b"),
+ // IV
+ pack("H*", "f08b65c9f4dd950039941da2e8058c4e"),
+ // txt_encoded
+ pack("H*", "e29e3114c8000eb484395b256b1b3267894f290d3999819ff35da03e6463c186c4d7ebb964941f1986a2d69572fcaba8"),
+ // txt_plain
+ pack("H*", "a206385945b21f812a9475f47fddbb7fbdda958a8d14c0dbcdaec36e8b28f1f6ececa1ceae4ce17721d162c1d42a66c1"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "7b1ab9144b0239315cd5eec6c75663bd"),
+ // IV
+ pack("H*", "0b1e74f45c17ff304d99c059ce5cde09"),
+ // txt_encoded
+ pack("H*", "d3f89b71e033070f9d7516a6cb4ea5ef51d6fb63d4f0fea089d0a60e47bbb3c2e10e9ba3b282c7cb79aefe3068ce228377c21a58fe5a0f8883d0dbd3d096beca"),
+ // txt_plain
+ pack("H*", "b968aeb199ad6b3c8e01f26c2edad444538c78bfa36ed68ca76123b8cdce615a01f6112bb80bfc3f17490578fb1f909a52e162637b062db04efee291a1f1af60"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "36466b6bd25ea3857ea42f0cac1919b1"),
+ // IV
+ pack("H*", "7186fb6bdfa98a16189544b228f3bcd3"),
+ // txt_encoded
+ pack("H*", "9ed957bd9bc52bba76f68cfbcde52157a8ca4f71ac050a3d92bdebbfd7c78316b4c9f0ba509fad0235fdafe90056ad115dfdbf08338b2acb1c807a88182dd2a882d1810d4302d598454e34ef2b23687d"),
+ // txt_plain
+ pack("H*", "999983467c47bb1d66d7327ab5c58f61ddb09b93bd2460cb78cbc12b5fa1ea0c5f759ccc5e478697687012ff4673f6e61eecaeda0ccad2d674d3098c7d17f887b62b56f56b03b4d055bf3a4460e83efa"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "89373ee6e28397640d5082eed4123239"),
+ // IV
+ pack("H*", "1a74d7c859672c804b82472f7e6d3c6b"),
+ // txt_encoded
+ pack("H*", "1bcba44ddff503db7c8c2ec4c4eea0e827957740cce125c1e11769842fa97e25f1b89269e6d77923a512a358312f4ba1cd33f2d111280cd83e1ef9e7cf7036d55048d5c273652afa611cc81b4e9dac7b5078b7c4716062e1032ead1e3329588a"),
+ // txt_plain
+ pack("H*", "45efd00daa4cdc8273ef785cae9e944a7664a2391e1e2c449f475acec0124bbc22944331678617408a1702917971f4654310ffb9229bec6173715ae512d37f93aaa6abf009f7e30d65669d1db0366b5bce4c7b00f871014f5753744a1878dc57"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "bab0cceddc0abd63e3f82e9fbff7b8aa"),
+ // IV
+ pack("H*", "68b9140f300490c5c942f66e777eb806"),
+ // txt_encoded
+ pack("H*", "c65b94b1f291fa9f0600f22c3c0432c895ad5d177bcccc9ea44e8ec339c9adf43855b326179d6d81aa36ef59462fd86127e9d81b0f286f93306bf74d4c79e47c1b3d4b74edd3a16290e3c63b742e41f20d66ceee794316bb63d3bd002712a1b136ba6185bd5c1dab81b07db90d2af5e5"),
+ // txt_plain
+ pack("H*", "c5585ff215bbb73ba5393440852fb199436de0d15e55c631f877670aa3eda9f672eb1f876f09544e63558436b8928000db2f02a5ad90f95b05ac4cf49e198e617e7678480fdf0efacc6aae691271e6cdd3541ebf719a1ccaedb24e2f80f92455dd5910cb5086b0960a3942ec182dcbd7"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "9c702898efa44557b29ed283f5bc0293"),
+ // IV
+ pack("H*", "cec6e1b82e8b2a591a9fa5ff1cf5cc51"),
+ // txt_encoded
+ pack("H*", "ba9f646755dacc22911f51d7de2f7e7cb0bc0b75257ea44fe883edb055c7c28ede04c3a0adcb10128ad4517d0093fa16bb0bcd2635e7a0ba92c7609bc8d8568002a7a983473724d256513aa7d51b477aabec1975ab5faf2872a6407e922180eff02f1ef86a4591c8bd3d143da6f0ef0e4806f94ace0d5b0151c99640fccbc843"),
+ // txt_plain
+ pack("H*", "1d1f8d81bdc3e2c7cb057f408e6450000c5aaed3260ff1e87fbb6f324df6887ffd8f78d7e2a04c9ed9deda9d64482d2b002f4a2b78d8b4f691875c8295d4a64b22257ceaf713ed2f4b92530d7ad7151d629acda882b4829577a43990b0948c1149c22fe4273656d1b08833930e8b06709a94579a78fc220f7057bbc1fa9f6563"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "5674636dbdb38f705f0b08c372ef4785"),
+ // IV
+ pack("H*", "3f20ce0509b57420d53b6be4d0b7f0a9"),
+ // txt_encoded
+ pack("H*", "198351f453103face6655666fe90bdbd9630e3733b2d66c013a634e91f2bf015bd2d975d71b26322e44defa32d4e9dce50363557046ece08ba38f258dae5fd3e5049c647476c81e73482e40c171d89f9fea29452caf995733589b0061464fbd5dabe27dc5ea463a3deeb7dcb43664ae6a65c498c143883ab8e83b51e5410b181647602443dc3cfffe86f0205398fa83c"),
+ // txt_plain
+ pack("H*", "6d40fd2f908f48ce19241b6b278b1b1676dffd4a97ce9f8a1574c33bc59237deb536bee376fd6c381e6987700e39283aa111cf1a59f26fae6fb6700bf012646a2ab80239bf5e1632329043aa87d7911978b36523a2bc0bed9a9737ccf7a00baa2f3822b4e9e742e168e7069290705fed2eb63aa044b78f97dd33a8d6b24741ec1fd8c8db79d93b884e762dba0f406961"),
+ ),
+ array(
+ // mode
+ MCRYPT_MODE_CBC,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // key
+ pack("H*", "97a1025529b9925e25bbe78770ca2f99"),
+ // IV
+ pack("H*", "d4b4eab92aa9637e87d366384ed6915c"),
+ // txt_encoded
+ pack("H*", "22cdc3306fcd4d31ccd32720cbb61bad28d855670657c48c7b88c31f4fa1f93c01b57da90be63ead67d6a325525e6ed45083e6fb70a53529d1fa0f55653b942af59d78a2660361d63a7290155ac5c43312a25b235dacbbc863faf00940c99624076dfa44068e7c554c9038176953e571751dfc0954d41d113771b06466b1c8d13e0d4cb675ed58d1a619e1540970983781dc11d2dd8525ab5745958d615defda"),
+ // txt_plain
+ pack("H*", "e8b89150d8438bf5b17449d6ed26bd72127e10e4aa57cad85283e8359e089208e84921649f5b60ea21f7867cbc9620560c4c6238db021216db453c9943f1f1a60546173daef2557c3cdd855031b353d4bf176f28439e48785c37d38f270aa4a6faad2baabcb0c0b2d1dd5322937498ce803ba1148440a52e227ddba4872fe4d81d2d76a939d24755adb8a7b8452ceed2d179e1a5848f316f5c016300a390bfa7"),
+ ),
+ );
+ }
+
+ /**
+ * @param string $mode
+ * @param string $cipher
+ * @param string $key Encryption key
+ * @param string $iv Initialization vector
+ * @param string $txt_encoded ecrypted text
+ * @param string $txt_plain Known plain text that is decripted
+ *
+ * @dataProvider provider_decode
+ * @covers Encrypt::decode
+ */
+ public function test_decode($mode, $cipher, $key, $iv, $txt_encoded, $txt_plain)
+ {
+ // initialize
+ $e = new Encrypt($key, $mode, $cipher);
+
+ // prepare data
+ $expected = $txt_plain;
+ $actual = $e->decode(base64_encode($iv . $txt_encoded));
+
+ // assert
+ $this->assertSame($expected, $actual);
+ }
+
+ /**
+ * Provider for test_encode_decode, test_consecutive_encode_different_results
+ *
+ * @return array of $key, $mode, $cipher, $txt_plain
+ */
+ public function provider_encode_decode()
+ {
+ return array(
+ array(
+ // key
+ "Some super secret key",
+ // mode
+ MCRYPT_MODE_NOFB,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // txt_plain
+ "The quick brown fox jumps over the lazy dog",
+ ),
+ array(
+ // key
+ "De finibus bonorum et malorum",
+ // mode
+ MCRYPT_MODE_NOFB,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // txt_plain
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ ),
+ );
+ }
+
+ /**
+ * @param type $key Encryption Key
+ * @param type $mode Encryption Mode
+ * @param type $cipher Encryption Cipher
+ * @param type $txt_plain Plain text to encode and then decode back
+ *
+ * @dataProvider provider_encode_decode
+ * @covers Encrypt::encode
+ * @covers Encrypt::decode
+ */
+ public function test_encode_decode($key, $mode, $cipher, $txt_plain)
+ {
+ // initialize, encode
+ $e = new Encrypt($key, $mode, $cipher);
+ $txt_encoded = $e->encode($txt_plain);
+
+ // prepare data
+ $expected = $txt_plain;
+ $actual = $e->decode($txt_encoded);
+
+ // assert
+ $this->assertSame($expected, $actual);
+ }
+
+ /**
+ * Provider for test_decode_invalid_data
+ *
+ * @return array of $key, $mode, $cipher, $txt_invalid_encoded
+ */
+ public function provider_decode_invalid_data()
+ {
+ return array(
+ array(
+ // key
+ "Some super secret key",
+ // mode
+ MCRYPT_MODE_NOFB,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // txt_invalid_encoded
+ ".:This data is not a valid base 64 string:.",
+ ),
+ array(
+ // key
+ "Some super secret key",
+ // mode
+ MCRYPT_MODE_NOFB,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // txt_invalid_encoded
+ base64_encode("too short"),
+ ),
+ );
+ }
+
+ /**
+ * Tests for decode when the string is not valid base64,
+ * or is too short to contain a valid IV
+ *
+ * @param type $key
+ * @param type $mode
+ * @param type $cipher
+ * @param type $txt_encoded
+ *
+ * @dataProvider provider_decode_invalid_data
+ */
+ public function test_decode_invalid_data($key, $mode, $cipher, $txt_invalid_encoded)
+ {
+ // initialize
+ $e = new Encrypt($key, $mode, $cipher);
+
+ // assert
+ $this->AssertFalse($e->decode($txt_invalid_encoded));
+ }
+
+ /**
+ * @param type $key Encryption Key
+ * @param type $mode Encryption Mode
+ * @param type $cipher Encryption Cipher
+ * @param type $txt_plain Plain text to encode and then decode back
+ *
+ * @dataProvider provider_encode_decode
+ * @covers Encrypt::encode
+ */
+ public function test_consecutive_encode_produce_different_results($key, $mode, $cipher, $txt_plain)
+ {
+ // initialize, encode twice
+ $e = new Encrypt($key, $mode, $cipher);
+ $txt_encoded_first = $e->encode($txt_plain);
+ $txt_encoded_second = $e->encode($txt_plain);
+
+ // assert
+ $this->assertNotEquals($txt_encoded_first, $txt_encoded_second);
+ }
+
+ /**
+ * Provider for test_key_normalization
+ *
+ * @return array of $key, $iv, $mode, $cipher, $txt_plain
+ */
+ public function provider_key_normalization()
+ {
+ return array(
+ array(
+ // key
+ "Some super secret key",
+ // IV
+ pack("H*", "2fe2b333ceda8f98f4a99b40d2cd34a8"),
+ // mode
+ MCRYPT_MODE_NOFB,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // txt_plain
+ "The quick brown fox jumps over the lazy dog",
+ ),
+ array(
+ // key
+ "De finibus bonorum et malorum",
+ // IV
+ pack("H*", "2fe2b333ceda8f98f4a99b40d2cd34a8"),
+ // mode
+ MCRYPT_MODE_NOFB,
+ // cypher
+ MCRYPT_RIJNDAEL_128,
+ // txt_plain
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ ),
+ );
+ }
+
+ /**
+ * Test if key normalization logic behaves well
+ * Encrypt::_normalize_key was ment for PHP > 5.6.0
+ *
+ * We are testing our key normalization only against lower versions of PHP
+ * (PHP < 5.6.0) to see if it matches the internal key padding those
+ * PHP versions already have
+ *
+ * @param type $key Encryption Key
+ * @param type $mode Encryption Mode
+ * @param type $cipher Encryption Cipher
+ * @param type $txt_plain Plain text to encode and then decode back
+ *
+ * @dataProvider provider_key_normalization
+ * @covers Encrypt::_normalize_key
+ */
+ public function test_key_normalization($key, $iv, $mode, $cipher, $txt_plain)
+ {
+ if (version_compare(PHP_VERSION, '5.6.0', '>='))
+ {
+ $this->markTestSkipped('Starting from PHP 5.6.0, mcrypt does not pad encryption keys with null bytes.');
+ }
+
+ // initialize, encode twice
+ $e1 = new Kohana_EncryptTest_IvStubbed($key, $iv, $mode, $cipher);
+ $e2 = new Kohana_EncryptTest_KeyNormalized($key, $iv, $mode, $cipher);
+
+ $txt_encoded_1 = $e1->encode($txt_plain);
+ $txt_encoded_2 = $e2->encode($txt_plain);
+
+ // assert
+ $this->assertSame($txt_encoded_1, $txt_encoded_2);
+ }
+
+ /**
+ * @expectedException Kohana_Exception
+ * @expectedExceptionMessage No encryption key is defined in the encryption configuration group
+ */
+ public function test_instance_throw_exception_when_no_key_provided()
+ {
+ Encrypt::instance();
+ }
+
+ /**
+ * Provider for test_instance_returns_singleton
+ *
+ * @return array of $instance_name, $missing_config
+ */
+ public function provider_instance_returns_singleton()
+ {
+ return array(
+ array(
+ 'default',
+ array(
+ 'key' => 'trwQwVXX96TIJoKxyBHB9AJkwAOHixuV1ENZmIWyanI0j1zNgSVvqywy044Agaj',
+ )
+ ),
+ array(
+ 'blowfish',
+ array(
+ 'key' => '7bZJJkmNrelj5NaKoY6h6rMSRSmeUlJuTeOd5HHka5XknyMX4uGSfeVolTz4IYy',
+ 'cipher' => MCRYPT_BLOWFISH,
+ 'mode' => MCRYPT_MODE_ECB,
+ )
+ ),
+ array(
+ 'tripledes',
+ array(
+ 'key' => 'a9hcSLRvA3LkFc7EJgxXIKQuz1ec91J7P6WNq1IaxMZp4CTj5m31gZLARLxI1jD',
+ 'cipher' => MCRYPT_3DES,
+ 'mode' => MCRYPT_MODE_CBC,
+ )
+ ),
+ );
+ }
+
+ /**
+ * Test to multiple calls to the instance() method returns same instance
+ * also test if the instances are appropriately configured.
+ *
+ * @param string $instance_name instance name
+ * @param array $config_array array of config variables missing from config
+ *
+ * @dataProvider provider_instance_returns_singleton
+ */
+ public function test_instance_returns_singleton($instance_name, array $config_array)
+ {
+ // load config
+ $config = Kohana::$config->load('encrypt');
+ // if instance name is NULL the config group should be the default
+ $config_group = $instance_name ? : Encrypt::$default;
+ // if config group does not exists, create
+ if (!array_key_exists($config_group, $config))
+ {
+ $config[$config_group] = array();
+ }
+ // fill in the missing config variables
+ $config[$config_group] = $config[$config_group] + $config_array;
+
+ // call instance twice
+ $e = Encrypt::instance($instance_name);
+ $e2 = Encrypt::instance($instance_name);
+
+ // assert instances
+ $this->assertInstanceOf('Encrypt', $e);
+ $this->assertInstanceOf('Encrypt', $e2);
+ $this->assertSame($e, $e2);
+
+ // test if instances are well configured
+ // prepare expected variables
+ $expected_cipher = $config[$config_group]['cipher'];
+ $expected_mode = $config[$config_group]['mode'];
+ $expected_key_size = mcrypt_get_key_size($expected_cipher, $expected_mode);
+ $expected_key = substr($config[$config_group]['key'], 0, $expected_key_size);
+
+ // assert
+ $this->assertSameProtectedProperty($expected_key, $e, '_key');
+ $this->assertSameProtectedProperty($expected_cipher, $e, '_cipher');
+ $this->assertSameProtectedProperty($expected_mode, $e, '_mode');
+ }
+
+ /**
+ * Helper method to test for private/protected properties
+ *
+ * @param mixed $expect Expected value
+ * @param mixed $object object that holds the private/protected property
+ * @param string $name the name of the private/protected property
+ */
+ protected function assertSameProtectedProperty($expect, $object, $name)
+ {
+ $refl = new ReflectionClass($object);
+ $property = $refl->getProperty($name);
+ $property->setAccessible(TRUE);
+ $this->assertSame($expect, $property->getValue($object));
+ }
+
+}
+
+/**
+ * Class Kohana_EncryptTest_IvStubbed wraps the Encrypt class to mock out
+ * the actual mcrypt_create_iv calls for unit testing.
+ */
+class Kohana_EncryptTest_IvStubbed extends Encrypt
+{
+
+ /**
+ * override constructor to force class use known IVs
+ *
+ * @param string $key encryption key
+ * @param string $iv feed a known IV
+ * @param string $mode mcrypt mode
+ * @param string $cipher mcrypt cipher
+ */
+ public function __construct($key, $iv, $mode, $cipher)
+ {
+ parent::__construct($key, $mode, $cipher);
+
+ $this->_iv = $iv;
+ }
+
+ /**
+ * Fake a random initialization vector by returning a known one
+ *
+ * @return string a known IV
+ */
+ protected function _create_iv()
+ {
+ return isset($this->_iv) ? $this->_iv : FALSE;
+ }
+
+}
+
+/**
+ * Class Kohana_EncryptTest_KeyNormalized wraps the Encrypt class to mock out
+ * the actual mcrypt_create_iv calls for unit testing, as well as to always
+ * normalize keys
+ */
+class Kohana_EncryptTest_KeyNormalized extends Kohana_EncryptTest_IvStubbed
+{
+
+ /**
+ * override constructor to force key normalization
+ *
+ * @param string $key encryption key
+ * @param string $mode mcrypt mode
+ * @param string $cipher mcrypt cipher
+ */
+ public function __construct($key, $iv, $mode, $cipher)
+ {
+ parent::__construct($key, $iv, $mode, $cipher);
+
+ $this->_key = $this->_normalize_key($this->_key, $this->_cipher, $this->_mode);
+ }
+
+
+}
+
diff --git a/tests/kohana/FeedTest.php b/tests/kohana/FeedTest.php
index 40986303b..e4279dd51 100644
--- a/tests/kohana/FeedTest.php
+++ b/tests/kohana/FeedTest.php
@@ -25,7 +25,8 @@ public function provider_parse()
{
return array(
// $source, $expected
- array('http://dev.kohanaframework.org/projects/kohana3/activity.atom', 15),
+ array(realpath(__DIR__.'/../test_data/feeds/activity.atom'), array('Proposals (Political/Workflow) #4839 (New)', 'Proposals (Political/Workflow) #4782')),
+ array(realpath(__DIR__.'/../test_data/feeds/example.rss20'), array('Example entry')),
);
}
@@ -38,11 +39,15 @@ public function provider_parse()
* @param string $source URL to test
* @param integer $expected Count of items
*/
- public function test_parse($source, $expected)
+ public function test_parse($source, $expected_titles)
{
- $this->markTestSkipped('We don\'t go to the internet for tests.');
+ $titles = array();
+ foreach (Feed::parse($source) as $item)
+ {
+ $titles[] = $item['title'];
+ }
- $this->assertEquals($expected, count(Feed::parse($source)));
+ $this->assertSame($expected_titles, $titles);
}
/**
diff --git a/tests/kohana/FileTest.php b/tests/kohana/FileTest.php
index 8d4c49112..712bb2cdd 100644
--- a/tests/kohana/FileTest.php
+++ b/tests/kohana/FileTest.php
@@ -5,7 +5,7 @@
*
* @group kohana
* @group kohana.core
- * @group kohana.core.url
+ * @group kohana.core.file
*
* @package Kohana
* @category Tests
@@ -25,8 +25,7 @@ public function provider_mime()
{
return array(
// $value, $result
- array(Kohana::find_file('classes', 'File')),
- array(Kohana::find_file('tests', 'test_data/github', 'png')),
+ array(Kohana::find_file('tests', 'test_data/github', 'png'), 'image/png'),
);
}
@@ -38,12 +37,10 @@ public function provider_mime()
* @param boolean $input Input for File::mime
* @param boolean $expected Output for File::mime
*/
- public function test_mime($input)
+ public function test_mime($input, $expected)
{
- $this->markTestSkipped(
- 'This test doesn\'t do anything useful!'
- );
- $this->assertSame(1, preg_match('/^(?:application|audio|image|message|multipart|text|video)\/[a-z.+0-9-]+$/i', File::mime($input)));
+ //@todo: File::mime coverage needs significant improvement or to be dropped for a composer package - it's a "horribly unreliable" method with very little testing
+ $this->assertSame($expected, File::mime($input));
}
/**
diff --git a/tests/kohana/HTMLTest.php b/tests/kohana/HTMLTest.php
index 015a65da4..c92ad1bbe 100644
--- a/tests/kohana/HTMLTest.php
+++ b/tests/kohana/HTMLTest.php
@@ -117,6 +117,10 @@ public function provider_script()
'https',
FALSE
),
+ array(
+ '',
+ '//google.com/script.js',
+ ),
);
}
@@ -193,6 +197,13 @@ public function provider_style()
'https',
TRUE
),
+ array(
+ '',
+ '//google.com/style.css',
+ array(),
+ NULL,
+ FALSE
+ ),
);
}
@@ -223,6 +234,20 @@ public function test_style($expected, $file, array $attributes = NULL, $protocol
public function provider_anchor()
{
return array(
+ // a fragment-only anchor
+ array(
+ 'Kohana',
+ array(),
+ '#go-to-section-kohana',
+ 'Kohana',
+ ),
+ // a query-only anchor
+ array(
+ 'Category A',
+ array(),
+ '?cat=a',
+ 'Category A',
+ ),
array(
'Kohana',
array(),
diff --git a/tests/kohana/Http/HeaderTest.php b/tests/kohana/Http/HeaderTest.php
index 0894b468e..8f241d3a5 100644
--- a/tests/kohana/Http/HeaderTest.php
+++ b/tests/kohana/Http/HeaderTest.php
@@ -11,8 +11,8 @@
* @package Kohana
* @category Tests
* @author Kohana Team
- * @copyright (c) 2008-2012 Kohana Team
- * @license http://kohanaphp.com/license
+ * @copyright (c) 2008-2014 Kohana Team
+ * @license http://kohanaframework.org/license
*/
class Kohana_HTTP_HeaderTest extends Unittest_TestCase {
diff --git a/tests/kohana/RequestTest.php b/tests/kohana/RequestTest.php
index 3ba516f35..eb31f4710 100644
--- a/tests/kohana/RequestTest.php
+++ b/tests/kohana/RequestTest.php
@@ -270,6 +270,11 @@ public function provider_url()
'http',
'http://localhost/kohana/foo'
),
+ array(
+ 'http://www.google.com',
+ 'http',
+ 'http://www.google.com'
+ ),
);
}
@@ -296,7 +301,14 @@ public function test_url($uri, $protocol, $expected)
'Kohana::$index_file' => FALSE,
));
- $this->assertEquals(Request::factory($uri)->url($protocol), $expected);
+ // issue #3967: inject the route so that we don't conflict with the application's default route
+ $route = new Route('((/))');
+ $route->defaults(array(
+ 'controller' => 'welcome',
+ 'action' => 'index',
+ ));
+
+ $this->assertEquals(Request::factory($uri, array(), TRUE, array($route))->url($protocol), $expected);
}
/**
@@ -397,8 +409,15 @@ public function test_post_max_size_exceeded($content_length, $expected)
*/
public function provider_uri_only_trimed_on_internal()
{
+ // issue #3967: inject the route so that we don't conflict with the application's default route
+ $route = new Route('((/))');
+ $route->defaults(array(
+ 'controller' => 'welcome',
+ 'action' => 'index',
+ ));
+
$old_request = Request::$initial;
- Request::$initial = new Request(TRUE);
+ Request::$initial = new Request(TRUE, array(), TRUE, array($route));
$result = array(
array(
diff --git a/tests/kohana/ResponseTest.php b/tests/kohana/ResponseTest.php
index d5e8870c1..998916314 100644
--- a/tests/kohana/ResponseTest.php
+++ b/tests/kohana/ResponseTest.php
@@ -171,27 +171,6 @@ public function test_cookie_get()
$this->assertSame(Cookie::$expiration, $cookie['expiration']);
}
- /**
- * Tests that the headers are not sent by PHP in CLI mode
- *
- * @return void
- */
- public function test_send_headers_cli()
- {
- if (headers_sent())
- {
- $this->markTestSkipped('Cannot test this feature as headers have already been sent!');
- }
-
- $content_type = 'application/json';
- $response = new Response;
- $response->headers('content-type', $content_type)
- ->send_headers();
-
- $this->assertFalse(headers_sent());
-
- }
-
/**
* Test the content type is sent when set
*
@@ -205,4 +184,4 @@ public function test_content_type_when_set()
$headers = $response->send_headers()->headers();
$this->assertSame($content_type, (string) $headers['content-type']);
}
-}
\ No newline at end of file
+}
diff --git a/tests/kohana/RouteTest.php b/tests/kohana/RouteTest.php
index dd7a3f9d9..ff7cec319 100644
--- a/tests/kohana/RouteTest.php
+++ b/tests/kohana/RouteTest.php
@@ -909,4 +909,43 @@ public function test_route_filter_modify_params($route, $defaults, $filter, $uri
$this->assertSame($expected_params, $params);
}
+ /**
+ * Provides test data for test_route_uri_encode_parameters
+ *
+ * @return array
+ */
+ public function provider_route_uri_encode_parameters()
+ {
+ return array(
+ array(
+ 'article',
+ 'blog/article/',
+ array(
+ 'controller' => 'home',
+ 'action' => 'index'
+ ),
+ 'article_name',
+ 'Article name with special chars \\ ##',
+ 'blog/article/Article%20name%20with%20special%20chars%20\\%20%23%23'
+ )
+ );
+ }
+
+ /**
+ * http://dev.kohanaframework.org/issues/4079
+ *
+ * @test
+ * @covers Route::get
+ * @ticket 4079
+ * @dataProvider provider_route_uri_encode_parameters
+ */
+ public function test_route_uri_encode_parameters($name, $uri_callback, $defaults, $uri_key, $uri_value, $expected)
+ {
+ Route::set($name, $uri_callback)->defaults($defaults);
+
+ $get_route_uri = Route::get($name)->uri(array($uri_key => $uri_value));
+
+ $this->assertSame($expected, $get_route_uri);
+ }
+
}
diff --git a/tests/kohana/SecurityTest.php b/tests/kohana/SecurityTest.php
index 4c3b368f5..25eb020b5 100644
--- a/tests/kohana/SecurityTest.php
+++ b/tests/kohana/SecurityTest.php
@@ -67,17 +67,6 @@ public function test_strip_image_tags($expected, $input)
*/
public function provider_csrf_token()
{
- // Unfortunately this data provider has to use the session in order to
- // generate its data. If headers have already been sent then this method
- // throws an error, even if the test is does not run. If we return an
- // empty array then this also causes an error, so the only way to get
- // around it is to return an array of misc data and have the test skip
- // if headers have been sent. It's annoying this hack has to be
- // implemented, but the security code isn't exactly brilliantly
- // implemented. Ideally we'd be able to inject a session instance
- if (headers_sent())
- return array(array('', '', 0));
-
$array = array();
for ($i = 0; $i <= 4; $i++)
{
@@ -96,10 +85,7 @@ public function provider_csrf_token()
*/
public function test_csrf_token($expected, $input, $iteration)
{
- if (headers_sent()) {
- $this->markTestSkipped('Headers have already been sent, session not available');
- }
-
+ //@todo: the Security::token tests need to be reviewed to check how much of the logic they're actually covering
Security::$token_name = 'token_'.$iteration;
$this->assertSame(TRUE, $input);
$this->assertSame($expected, Security::token(FALSE));
diff --git a/tests/kohana/SessionTest.php b/tests/kohana/SessionTest.php
index 1f34fa807..83a35921d 100644
--- a/tests/kohana/SessionTest.php
+++ b/tests/kohana/SessionTest.php
@@ -95,20 +95,14 @@ public function test_constructor_uses_settings_from_config_and_casts($expected,
*/
public function test_constructor_loads_session_with_session_id()
{
- $this->markTestIncomplete(
- 'Need to work out why constructor is not being called'
- );
-
$config = array();
$session_id = 'lolums';
// Don't auto-call constructor, we need to setup the mock first
- $session = $this->getMockForAbstractClass(
- 'Session',
- array(),
- '',
- FALSE
- );
+ $session = $this->getMockBuilder('Session')
+ ->disableOriginalConstructor()
+ ->setMethods(array('read'))
+ ->getMockForAbstractClass();
$session
->expects($this->once())
diff --git a/tests/kohana/TextTest.php b/tests/kohana/TextTest.php
index a59bd5939..0c93270d5 100644
--- a/tests/kohana/TextTest.php
+++ b/tests/kohana/TextTest.php
@@ -196,6 +196,30 @@ function test_alternate_resets_when_called_with_no_params_and_returns_empty_stri
$this->assertSame('yes', Text::alternate($val_a, $val_b, $val_c));
}
+ /**
+ * Provides test data for test_ucfirst
+ *
+ * @return array Test data
+ */
+ public function provider_ucfirst()
+ {
+ return array(
+ array('Content-Type', 'content-type', '-'),
+ array('Բարեւ|Ձեզ', 'բարեւ|ձեզ', '|'),
+ );
+ }
+
+ /**
+ * Covers Text::ucfirst()
+ *
+ * @test
+ * @dataProvider provider_ucfirst
+ */
+ public function test_ucfirst($expected, $string, $delimiter)
+ {
+ $this->assertSame($expected, Text::ucfirst($string, $delimiter));
+ }
+
/**
* Provides test data for test_reducde_slashes()
*
@@ -386,9 +410,90 @@ function provider_widont()
{
return array
(
- array('No gain, no pain', 'No gain, no pain'),
- array("spaces?what'rethey?", "spaces?what'rethey?"),
- array('', ''),
+ // A very simple widont test
+ array(
+ 'A very simple test',
+ 'A very simple test',
+ ),
+ // Single word items shouldn't be changed
+ array(
+ 'Test',
+ 'Test',
+ ),
+ // Single word after single space shouldn't be changed either
+ array(
+ ' Test',
+ ' Test',
+ ),
+ // Single word with HTML all around
+ array(
+ '
Test
',
+ '
Test
',
+ ),
+ // Single word after single space with HTML all around
+ array(
+ '
Test
',
+ '
Test
',
+ ),
+ // Widont with more than one paragraph
+ array(
+ '