diff --git a/cake/VERSION.txt b/cake/VERSION.txt index 1f0f7116b..e5c64475c 100644 --- a/cake/VERSION.txt +++ b/cake/VERSION.txt @@ -18,7 +18,8 @@ // @license MIT License (http://www.opensource.org/licenses/mit-license.php) // +--------------------------------------------------------------------------------------------+ // //////////////////////////////////////////////////////////////////////////////////////////////////// -1.3.5 +1.3.6 + diff --git a/cake/config/config.php b/cake/config/config.php index a36b17f9d..071441c21 100644 --- a/cake/config/config.php +++ b/cake/config/config.php @@ -17,4 +17,4 @@ * @since CakePHP(tm) v 1.1.11.4062 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -return $config['Cake.version'] = '1.3.5'; +return $config['Cake.version'] = '1.3.6'; diff --git a/cake/libs/controller/components/auth.php b/cake/libs/controller/components/auth.php index 993cecf44..e91351874 100644 --- a/cake/libs/controller/components/auth.php +++ b/cake/libs/controller/components/auth.php @@ -520,7 +520,6 @@ class AuthComponent extends Component { $valid = $this->Acl->check($user, $this->action()); break; case 'crud': - $this->mapActions(); if (!isset($this->actionMap[$this->request['action']])) { trigger_error( sprintf(__('Auth::startup() - Attempted access of un-mapped action "%1$s" in controller "%2$s"'), $this->request['action'], $this->request['controller']), @@ -535,7 +534,6 @@ class AuthComponent extends Component { } break; case 'model': - $this->mapActions(); $action = $this->request['action']; if (isset($this->actionMap[$action])) { $action = $this->actionMap[$action]; diff --git a/cake/libs/controller/components/security.php b/cake/libs/controller/components/security.php index e4db0ceb5..da7f86e67 100644 --- a/cake/libs/controller/components/security.php +++ b/cake/libs/controller/components/security.php @@ -618,10 +618,15 @@ class SecurityComponent extends Component { } unset($check['_Token']); + $locked = str_rot13($locked); + if (preg_match('/(\A|;|{|})O\:[0-9]+/', $locked)) { + return false; + } + $lockedFields = array(); $fields = Set::flatten($check); $fieldList = array_keys($fields); - $locked = unserialize(str_rot13($locked)); + $locked = unserialize($locked); $multi = array(); foreach ($fieldList as $i => $key) { diff --git a/cake/libs/i18n.php b/cake/libs/i18n.php index cbc84801f..d12cbb3dc 100644 --- a/cake/libs/i18n.php +++ b/cake/libs/i18n.php @@ -139,11 +139,11 @@ class I18n { $_this->domain = $domain . '_' . $_this->l10n->lang; - if (empty($_this->__domains[$domain][$_this->__lang])) { + if (!isset($_this->__domains[$domain][$_this->__lang])) { $_this->__domains[$domain][$_this->__lang] = Cache::read($_this->domain, '_cake_core_'); } - if (empty($_this->__domains[$domain][$_this->__lang][$_this->category])) { + if (!isset($_this->__domains[$domain][$_this->__lang][$_this->category])) { $_this->__bindTextDomain($domain); Cache::write($_this->domain, $_this->__domains[$domain][$_this->__lang], '_cake_core_'); } diff --git a/cake/libs/model/model.php b/cake/libs/model/model.php index c4b693a0c..bc07631e8 100644 --- a/cake/libs/model/model.php +++ b/cake/libs/model/model.php @@ -629,9 +629,9 @@ class Model extends Object { * * Example: Turn off the associated Model Support request, * to temporarily lighten the User model: - * + * * `$this->User->unbindModel( array('hasMany' => array('Supportrequest')) );` - * + * * unbound models that are not made permanent will reset with the next call to Model::find() * * @param array $params Set of bindings to unbind (indexed by binding type) @@ -1636,7 +1636,7 @@ class Model extends Object { } if ($options['atomic'] && $options['validate'] !== 'only') { - $db->begin($this); + $transactionBegun = $db->begin($this); } if (Set::numeric(array_keys($data))) { @@ -1676,8 +1676,12 @@ class Model extends Object { break; default: if ($options['atomic']) { - if ($validates && ($db->commit($this) !== false)) { - return true; + if ($validates) { + if ($transactionBegun) { + return $db->commit($this) !== false; + } else { + return true; + } } $db->rollback($this); return false; @@ -1787,7 +1791,11 @@ class Model extends Object { default: if ($options['atomic']) { if ($validates) { - return ($db->commit($this) !== false); + if ($transactionBegun) { + return $db->commit($this) !== false; + } else { + return true; + } } else { $db->rollback($this); } @@ -1870,7 +1878,7 @@ class Model extends Object { )); } - if ($db->delete($this)) { + if ($db->delete($this, array($this->alias . '.' . $this->primaryKey => $id))) { if (!empty($this->belongsTo)) { $this->updateCounterCache($keys[$this->alias]); } diff --git a/cake/libs/view/helpers/form.php b/cake/libs/view/helpers/form.php index d48e0b94e..b347b69ed 100644 --- a/cake/libs/view/helpers/form.php +++ b/cake/libs/view/helpers/form.php @@ -218,7 +218,8 @@ class FormHelper extends AppHelper { $data = $this->fieldset[$modelEntity]; $recordExists = ( isset($this->request->data[$model]) && - !empty($this->request->data[$model][$data['key']]) + !empty($this->request->data[$model][$data['key']]) && + !is_array($this->request->data[$model][$data['key']]) ); if ($recordExists) { diff --git a/cake/libs/view/helpers/number.php b/cake/libs/view/helpers/number.php index 7bbed96d5..b7d612471 100644 --- a/cake/libs/view/helpers/number.php +++ b/cake/libs/view/helpers/number.php @@ -48,7 +48,7 @@ class NumberHelper extends AppHelper { 'decimals' => '.', 'negative' => '()','escape' => false ), 'EUR' => array( - 'before'=>'€', 'after' => 'c', 'zero' => 0, 'places' => 2, 'thousands' => '.', + 'before'=>'€', 'after' => false, 'zero' => 0, 'places' => 2, 'thousands' => '.', 'decimals' => ',', 'negative' => '()', 'escape' => false ) ); diff --git a/cake/libs/view/pages/home.ctp b/cake/libs/view/pages/home.ctp index 6a1c67065..c8939c85e 100644 --- a/cake/libs/view/pages/home.ctp +++ b/cake/libs/view/pages/home.ctp @@ -21,7 +21,7 @@ if (Configure::read() == 0): endif; ?>

- + 0): Debugger::checkSecurityKeys(); diff --git a/cake/tests/cases/libs/cake_test_case.test.php b/cake/tests/cases/libs/cake_test_case.test.php index 21551bae5..f9e5e8410 100644 --- a/cake/tests/cases/libs/cake_test_case.test.php +++ b/cake/tests/cases/libs/cake_test_case.test.php @@ -60,7 +60,6 @@ class CakeTestCaseTest extends CakeTestCase { */ function tearDown() { Configure::write('debug', $this->_debug); - unset($this->Case); unset($this->Result); unset($this->Reporter); } @@ -77,6 +76,30 @@ class CakeTestCaseTest extends CakeTestCase { $this->assertEquals(0, $result->errorCount()); $this->assertTrue($result->wasSuccessful()); $this->assertEquals(0, $result->failureCount()); + + $input = 'My link'; + $pattern = array( + 'a' => array('href' => '/test.html', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertTrue($test->assertTags($input, $pattern), 'Double quoted attributes %s'); + + $input = "My link"; + $pattern = array( + 'a' => array('href' => '/test.html', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertTrue($test->assertTags($input, $pattern), 'Single quoted attributes %s'); + + $input = "My link"; + $pattern = array( + 'a' => array('href' => 'preg:/.*\.html/', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertTrue($test->assertTags($input, $pattern), 'Single quoted attributes %s'); } /** @@ -181,6 +204,7 @@ class CakeTestCaseTest extends CakeTestCase { $manager->expects($this->once())->method('unload'); $result = $test->run(); $this->assertEquals(1, $result->errorCount()); + } /** * testSkipIf diff --git a/cake/tests/cases/libs/controller/components/security.test.php b/cake/tests/cases/libs/controller/components/security.test.php index b338217ef..22274277a 100644 --- a/cake/tests/cases/libs/controller/components/security.test.php +++ b/cake/tests/cases/libs/controller/components/security.test.php @@ -608,6 +608,29 @@ DIGEST; $this->assertFalse($result, 'validatePost passed when fields were missing. %s'); } +/** + * Test that objects can't be passed into the serialized string. This was a vector for RFI and LFI + * attacks. Thanks to Felix Wilhelm + * + * @return void + */ + function testValidatePostObjectDeserialize() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->request->params['_Token']['key']; + $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877'; + + // a corrupted serialized object, so we can see if it ever gets to deserialize + $attack = 'O:3:"App":1:{s:5:"__map";a:1:{s:3:"foo";s:7:"Hacked!";s:1:"fail"}}'; + $fields .= urlencode(':' . str_rot13($attack)); + + $this->Controller->request->data = array( + 'Model' => array('username' => 'mark', 'password' => 'foo', 'valid' => '0'), + '_Token' => compact('key', 'fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertFalse($result, 'validatePost passed when key was missing. %s'); + } + /** * Tests validation of checkbox arrays * diff --git a/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php b/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php index 25b10af5f..5b81954c9 100644 --- a/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php +++ b/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php @@ -799,7 +799,7 @@ class DboMysqlTest extends CakeTestCase { $this->db->virtualFieldSeparator = '_$_'; $result = $this->db->fields($model, null, array('data', 'other__field')); - $expected = array('`BinaryTest`.`data`', '(SUM(id)) AS BinaryTest_$_other__field'); + $expected = array('`BinaryTest`.`data`', '(SUM(id)) AS `BinaryTest_$_other__field`'); $this->assertEqual($result, $expected); } diff --git a/cake/tests/cases/libs/model/model_write.test.php b/cake/tests/cases/libs/model/model_write.test.php index d27b3e4d9..744e50db5 100644 --- a/cake/tests/cases/libs/model/model_write.test.php +++ b/cake/tests/cases/libs/model/model_write.test.php @@ -3059,6 +3059,22 @@ class ModelWriteTest extends BaseModelTest { $Post->saveAll($data); } +/** + * test saveAll with nested saveAll call. + * + * @return void + */ + function testSaveAllNestedSaveAll() { + $this->loadFixtures('Sample'); + $TransactionTestModel =& new TransactionTestModel(); + + $data = array( + array('apple_id' => 1, 'name' => 'sample5'), + ); + + $this->assertTrue($TransactionTestModel->saveAll($data, array('atomic' => true))); + } + /** * testSaveAllTransaction method * diff --git a/cake/tests/cases/libs/model/models.php b/cake/tests/cases/libs/model/models.php index 8770e2c01..5daa08506 100644 --- a/cake/tests/cases/libs/model/models.php +++ b/cake/tests/cases/libs/model/models.php @@ -292,7 +292,7 @@ class Article extends CakeTestModel { */ class BeforeDeleteComment extends CakeTestModel { var $name = 'BeforeDeleteComment'; - + var $useTable = 'comments'; function beforeDelete($cascade = true) { @@ -3559,6 +3559,7 @@ class FruitNoWith extends CakeTestModel { ) ); } + class UuidTagNoWith extends CakeTestModel { public $name = 'UuidTag'; public $useTable = 'uuid_tags'; @@ -3581,5 +3582,16 @@ class ProductUpdateAll extends CakeTestModel { class GroupUpdateAll extends CakeTestModel { public $name = 'GroupUpdateAll'; public $useTable = 'group_update_all'; - +} + +class TransactionTestModel extends CakeTestModel { + var $name = 'TransactionTestModel'; + var $useTable = 'samples'; + + function afterSave($created) { + $data = array( + array('apple_id' => 1, 'name' => 'sample6'), + ); + $this->saveAll($data, array('atomic' => true, 'callbacks' => false)); + } } diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index 16b7472e7..92c3f0971 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -56,6 +56,8 @@ class RouterTest extends CakeTestCase { } $this->assertPattern('/^http(s)?:\/\//', Router::url('/', true)); $this->assertPattern('/^http(s)?:\/\//', Router::url(null, true)); + $this->assertPattern('/^http(s)?:\/\//', Router::url(array('full_base' => true))); + $this->assertIdentical(FULL_BASE_URL . '/', Router::url(array('full_base' => true))); } /** @@ -1707,8 +1709,8 @@ class RouterTest extends CakeTestCase { function testParsingWithPatternOnAction() { Router::reload(); Router::connect( - '/blog/:action/*', - array('controller' => 'blog_posts'), + '/blog/:action/*', + array('controller' => 'blog_posts'), array('action' => 'other|actions') ); $result = Router::parse('/blog/other'); @@ -1730,7 +1732,7 @@ class RouterTest extends CakeTestCase { 'named' => array() ); $this->assertEqual($expected, $result); - + $result = Router::url(array('controller' => 'blog_posts', 'action' => 'foo')); $this->assertEqual('/blog_posts/foo', $result); @@ -2265,6 +2267,31 @@ class RouterTest extends CakeTestCase { $this->assertEqual($url, Router::url($url)); } +/** + * Testing that patterns on the :action param work properly. + * + * @return void + */ + function testPatternOnAction() { + $route =& new CakeRoute( + '/blog/:action/*', + array('controller' => 'blog_posts'), + array('action' => 'other|actions') + ); + $result = $route->match(array('controller' => 'blog_posts', 'action' => 'foo')); + $this->assertFalse($result); + + $result = $route->match(array('controller' => 'blog_posts', 'action' => 'actions')); + $this->assertEquals('/blog/actions/', $result); + + $result = $route->parse('/blog/other'); + $expected = array('controller' => 'blog_posts', 'action' => 'other', 'pass' => array(), 'named' => array()); + $this->assertEqual($expected, $result); + + $result = $route->parse('/blog/foobar'); + $this->assertFalse($result); + } + /** * test setting redirect routes * diff --git a/cake/tests/cases/libs/view/helpers/form.test.php b/cake/tests/cases/libs/view/helpers/form.test.php index 7f0325f64..494fa778b 100644 --- a/cake/tests/cases/libs/view/helpers/form.test.php +++ b/cake/tests/cases/libs/view/helpers/form.test.php @@ -5679,6 +5679,31 @@ class FormHelperTest extends CakeTestCase { $this->assertTags($result, $expected, true); } +/** + * test that create() doesn't cause errors by multiple id's being in the primary key + * as could happen with multiple select or checkboxes. + * + * @return void + */ + function testCreateWithMultipleIdInData() { + $encoding = strtolower(Configure::read('App.encoding')); + + $this->Form->request->data['Contact']['id'] = array(1, 2); + $result = $this->Form->create('Contact'); + $expected = array( + 'form' => array( + 'id' => 'ContactAddForm', + 'method' => 'post', + 'action' => '/contacts/add', + 'accept-charset' => $encoding + ), + 'div' => array('style' => 'display:none;'), + 'input' => array('type' => 'hidden', 'name' => '_method', 'value' => 'POST'), + '/div' + ); + $this->assertTags($result, $expected); + } + /** * test that create() doesn't add in extra passed params. * diff --git a/cake/tests/cases/libs/view/helpers/number.test.php b/cake/tests/cases/libs/view/helpers/number.test.php index 6260a82ad..1d1d04c4c 100644 --- a/cake/tests/cases/libs/view/helpers/number.test.php +++ b/cake/tests/cases/libs/view/helpers/number.test.php @@ -237,7 +237,7 @@ class NumberHelperTest extends CakeTestCase { $this->assertEqual($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '99c'; + $expected = '€0,99'; $this->assertEqual($expected, $result); $result = $this->Number->currency($value, 'GBP'); @@ -259,7 +259,7 @@ class NumberHelperTest extends CakeTestCase { $this->assertEqual($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '(99c)'; + $expected = '(€0,99)'; $this->assertEqual($expected, $result); $result = $this->Number->currency($value, 'GBP'); @@ -271,7 +271,7 @@ class NumberHelperTest extends CakeTestCase { $this->assertEqual($expected, $result); $result = $this->Number->currency($value, 'EUR', array('negative'=>'-')); - $expected = '-99c'; + $expected = '-€0,99'; $this->assertEqual($expected, $result); $result = $this->Number->currency($value, 'GBP', array('negative'=>'-')); @@ -337,6 +337,10 @@ class NumberHelperTest extends CakeTestCase { $result = $this->Number->currency('0.35', 'GBP'); $expected = '35p'; $this->assertEqual($expected, $result); + + $result = $this->Number->currency('0.35', 'EUR'); + $expected = '€0,35'; + $this->assertEqual($expected, $result); } /** diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 3a3e81895..2d0bda4a9 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -20,6 +20,10 @@ App::import('Core', 'View'); App::import('Helper', array('Html', 'Paginator', 'Form', 'Js')); +if (!defined('FULL_BASE_URL')) { + define('FULL_BASE_URL', 'http://cakephp.org'); +} + /** * PaginatorHelperTest class * @@ -1777,6 +1781,15 @@ class PaginatorHelperTest extends CakeTestCase { '/span', ); $this->assertTags($result, $expected); + + $this->Paginator->options(array('url' => array('full_base' => true))); + $result = $this->Paginator->first(); + $expected = array( + ' array('href' => FULL_BASE_URL . '/index/page:1/sort:Client.name/direction:DESC')), '<< first', '/a', + '/span', + ); + $this->assertTags($result, $expected); } /**