diff --git a/classes/Kohana/Arr.php b/classes/Kohana/Arr.php index 6bdfe59b2..bcd4735f7 100644 --- a/classes/Kohana/Arr.php +++ b/classes/Kohana/Arr.php @@ -282,7 +282,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..fe4cc490d 100644 --- a/classes/Kohana/Cookie.php +++ b/classes/Kohana/Cookie.php @@ -71,7 +71,7 @@ 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; 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 2b1689e98..43fee91f0 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 { diff --git a/classes/Kohana/HTML.php b/classes/Kohana/HTML.php index a95781303..b34363651 100644 --- a/classes/Kohana/HTML.php +++ b/classes/Kohana/HTML.php @@ -127,9 +127,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); } } 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/Header.php b/classes/Kohana/HTTP/Header.php index 6a35e9138..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 { 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/Request.php b/classes/Kohana/Request.php index ba7738548..bdc059ecf 100644 --- a/classes/Kohana/Request.php +++ b/classes/Kohana/Request.php @@ -740,7 +740,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 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/Response.php b/classes/Kohana/Response.php index 14e7938e1..04a47ee56 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/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/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/DebugTest.php b/tests/kohana/DebugTest.php index ba04799d9..ce9ae6e9a 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/HTMLTest.php b/tests/kohana/HTMLTest.php index 015a65da4..c4505bc40 100644 --- a/tests/kohana/HTMLTest.php +++ b/tests/kohana/HTMLTest.php @@ -223,6 +223,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/request/ClientTest.php b/tests/kohana/request/ClientTest.php index fffc1d5d3..9ad870297 100644 --- a/tests/kohana/request/ClientTest.php +++ b/tests/kohana/request/ClientTest.php @@ -175,6 +175,29 @@ public function test_follows_with_headers() $this->assertFalse(isset($headers['x-not-in-follow']), 'X-Not-In-Follow should not be passed to next request'); } + /** + * Tests that the follow_headers are only added to a redirect request if they were present in the original + * + * @ticket 4790 + */ + public function test_follow_does_not_add_extra_headers() + { + $response = Request::factory( + $this->_dummy_redirect_uri(301), + array( + 'follow' => TRUE, + 'follow_headers' => array('Authorization') + )) + ->headers(array()) + ->execute(); + + $data = json_decode($response->body(),TRUE); + $headers = $data['rq_headers']; + + $this->assertArrayNotHasKey('authorization', $headers, 'Empty headers should not be added when following redirects'); + } + + /** * Provider for test_follows_with_strict_method *