mirror of
https://github.com/kamilwylegala/cakephp2-php8.git
synced 2025-01-19 11:06:15 +00:00
edfda47cf4
Fix missing HTML encoding when error messages contain HTML. This can happen when user data is used as an offset in an array in an unchecked way. Thanks to Teppei Fukuda for reporting this issue via the responsible security disclosure process.
651 lines
16 KiB
PHP
651 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
|
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
|
*
|
|
* Licensed under The MIT License
|
|
* For full copyright and license information, please see the LICENSE.txt
|
|
* Redistributions of files must retain the above copyright notice.
|
|
*
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
|
* @link http://cakephp.org CakePHP Project
|
|
* @since CakePHP(tm) v 1.2.0.5432
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
|
*/
|
|
|
|
App::uses('Debugger', 'Utility');
|
|
|
|
/**
|
|
* DebuggerTestCaseDebugger class
|
|
*
|
|
* @package Cake.Test.Case.Utility
|
|
*/
|
|
class DebuggerTestCaseDebugger extends Debugger {
|
|
}
|
|
|
|
/**
|
|
* DebuggerTest class
|
|
*
|
|
* !!! Be careful with changing code below as it may
|
|
* !!! change line numbers which are used in the tests
|
|
*
|
|
* @package Cake.Test.Case.Utility
|
|
*/
|
|
class DebuggerTest extends CakeTestCase {
|
|
|
|
protected $_restoreError = false;
|
|
|
|
/**
|
|
* setUp method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setUp() {
|
|
parent::setUp();
|
|
Configure::write('debug', 2);
|
|
Configure::write('log', false);
|
|
}
|
|
|
|
/**
|
|
* tearDown method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function tearDown() {
|
|
parent::tearDown();
|
|
Configure::write('log', true);
|
|
if ($this->_restoreError) {
|
|
restore_error_handler();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* testDocRef method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testDocRef() {
|
|
ini_set('docref_root', '');
|
|
$this->assertEquals(ini_get('docref_root'), '');
|
|
new Debugger();
|
|
$this->assertEquals(ini_get('docref_root'), 'http://php.net/');
|
|
}
|
|
|
|
/**
|
|
* test Excerpt writing
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testExcerpt() {
|
|
$result = Debugger::excerpt(__FILE__, __LINE__, 2);
|
|
$this->assertTrue(is_array($result));
|
|
$this->assertEquals(5, count($result));
|
|
$this->assertRegExp('/function(.+)testExcerpt/', $result[1]);
|
|
|
|
$result = Debugger::excerpt(__FILE__, 2, 2);
|
|
$this->assertTrue(is_array($result));
|
|
$this->assertEquals(4, count($result));
|
|
|
|
$pattern = '/<code>.*?<span style\="color\: \#\d+">.*?<\?php/';
|
|
$this->assertRegExp($pattern, $result[0]);
|
|
|
|
$result = Debugger::excerpt(__FILE__, 11, 2);
|
|
$this->assertEquals(5, count($result));
|
|
|
|
$pattern = '/<span style\="color\: \#\d{6}">\*<\/span>/';
|
|
$this->assertRegExp($pattern, $result[0]);
|
|
|
|
$return = Debugger::excerpt('[internal]', 2, 2);
|
|
$this->assertTrue(empty($return));
|
|
}
|
|
|
|
/**
|
|
* testOutput method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testOutput() {
|
|
set_error_handler('Debugger::showError');
|
|
$this->_restoreError = true;
|
|
|
|
$result = Debugger::output(false);
|
|
$this->assertEquals('', $result);
|
|
$out .= '';
|
|
$result = Debugger::output(true);
|
|
|
|
$this->assertEquals('Notice', $result[0]['error']);
|
|
$this->assertRegExp('/Undefined variable\:\s+out/', $result[0]['description']);
|
|
$this->assertRegExp('/DebuggerTest::testOutput/i', $result[0]['trace']);
|
|
|
|
ob_start();
|
|
Debugger::output('txt');
|
|
$other .= '';
|
|
$result = ob_get_clean();
|
|
|
|
$this->assertRegExp('/Undefined variable:\s+other/', $result);
|
|
$this->assertRegExp('/Context:/', $result);
|
|
$this->assertRegExp('/DebuggerTest::testOutput/i', $result);
|
|
|
|
ob_start();
|
|
Debugger::output('html');
|
|
$wrong .= '';
|
|
$result = ob_get_clean();
|
|
$this->assertRegExp('/<pre class="cake-error">.+<\/pre>/', $result);
|
|
$this->assertRegExp('/<b>Notice<\/b>/', $result);
|
|
$this->assertRegExp('/variable:\s+wrong/', $result);
|
|
|
|
ob_start();
|
|
Debugger::output('js');
|
|
$buzz .= '';
|
|
$result = explode('</a>', ob_get_clean());
|
|
$this->assertTags($result[0], array(
|
|
'pre' => array('class' => 'cake-error'),
|
|
'a' => array(
|
|
'href' => "javascript:void(0);",
|
|
'onclick' => "preg:/document\.getElementById\('cakeErr[a-z0-9]+\-trace'\)\.style\.display = " .
|
|
"\(document\.getElementById\('cakeErr[a-z0-9]+\-trace'\)\.style\.display == 'none'" .
|
|
" \? '' \: 'none'\);/"
|
|
),
|
|
'b' => array(), 'Notice', '/b', ' (8)',
|
|
));
|
|
|
|
$this->assertRegExp('/Undefined variable:\s+buzz/', $result[1]);
|
|
$this->assertRegExp('/<a[^>]+>Code/', $result[1]);
|
|
$this->assertRegExp('/<a[^>]+>Context/', $result[2]);
|
|
$this->assertContains('$wrong = ''', $result[3], 'Context should be HTML escaped.');
|
|
}
|
|
|
|
/**
|
|
* test encodes error messages
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testOutputEncodeDescription() {
|
|
set_error_handler('Debugger::showError');
|
|
$this->_restoreError = true;
|
|
|
|
ob_start();
|
|
$a = 'things';
|
|
$b = $a['<script>alert(1)</script>'];
|
|
$result = ob_get_clean();
|
|
|
|
$this->assertNotContains('<script>alert(1)', $result);
|
|
$this->assertContains('<script>alert(1)', $result);
|
|
}
|
|
|
|
/**
|
|
* Tests that changes in output formats using Debugger::output() change the templates used.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testChangeOutputFormats() {
|
|
set_error_handler('Debugger::showError');
|
|
$this->_restoreError = true;
|
|
|
|
Debugger::output('js', array(
|
|
'traceLine' => '{:reference} - <a href="txmt://open?url=file://{:file}' .
|
|
'&line={:line}">{:path}</a>, line {:line}'
|
|
));
|
|
$result = Debugger::trace();
|
|
$this->assertRegExp('/' . preg_quote('txmt://open?url=file://', '/') . '(\/|[A-Z]:\\\\)' . '/', $result);
|
|
|
|
Debugger::output('xml', array(
|
|
'error' => '<error><code>{:code}</code><file>{:file}</file><line>{:line}</line>' .
|
|
'{:description}</error>',
|
|
'context' => "<context>{:context}</context>",
|
|
'trace' => "<stack>{:trace}</stack>",
|
|
));
|
|
Debugger::output('xml');
|
|
|
|
ob_start();
|
|
$foo .= '';
|
|
$result = ob_get_clean();
|
|
|
|
$data = array(
|
|
'error' => array(),
|
|
'code' => array(), '8', '/code',
|
|
'file' => array(), 'preg:/[^<]+/', '/file',
|
|
'line' => array(), '' . ((int)__LINE__ - 7), '/line',
|
|
'preg:/Undefined variable:\s+foo/',
|
|
'/error'
|
|
);
|
|
$this->assertTags($result, $data, true);
|
|
}
|
|
|
|
/**
|
|
* Test that outputAs works.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testOutputAs() {
|
|
Debugger::outputAs('html');
|
|
$this->assertEquals('html', Debugger::outputAs());
|
|
}
|
|
|
|
/**
|
|
* Test that choosing a non-existent format causes an exception
|
|
*
|
|
* @expectedException CakeException
|
|
* @return void
|
|
*/
|
|
public function testOutputAsException() {
|
|
Debugger::outputAs('Invalid junk');
|
|
}
|
|
|
|
/**
|
|
* Tests that changes in output formats using Debugger::output() change the templates used.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testAddFormat() {
|
|
set_error_handler('Debugger::showError');
|
|
$this->_restoreError = true;
|
|
|
|
Debugger::addFormat('js', array(
|
|
'traceLine' => '{:reference} - <a href="txmt://open?url=file://{:file}' .
|
|
'&line={:line}">{:path}</a>, line {:line}'
|
|
));
|
|
Debugger::outputAs('js');
|
|
|
|
$result = Debugger::trace();
|
|
$this->assertRegExp('/' . preg_quote('txmt://open?url=file://', '/') . '(\/|[A-Z]:\\\\)' . '/', $result);
|
|
|
|
Debugger::addFormat('xml', array(
|
|
'error' => '<error><code>{:code}</code><file>{:file}</file><line>{:line}</line>' .
|
|
'{:description}</error>',
|
|
));
|
|
Debugger::outputAs('xml');
|
|
|
|
ob_start();
|
|
$foo .= '';
|
|
$result = ob_get_clean();
|
|
|
|
$data = array(
|
|
'<error',
|
|
'<code', '8', '/code',
|
|
'<file', 'preg:/[^<]+/', '/file',
|
|
'<line', '' . ((int)__LINE__ - 7), '/line',
|
|
'preg:/Undefined variable:\s+foo/',
|
|
'/error'
|
|
);
|
|
$this->assertTags($result, $data, true);
|
|
}
|
|
|
|
/**
|
|
* Test adding a format that is handled by a callback.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testAddFormatCallback() {
|
|
set_error_handler('Debugger::showError');
|
|
$this->_restoreError = true;
|
|
|
|
Debugger::addFormat('callback', array('callback' => array($this, 'customFormat')));
|
|
Debugger::outputAs('callback');
|
|
|
|
ob_start();
|
|
$foo .= '';
|
|
$result = ob_get_clean();
|
|
$this->assertContains('Notice: I eated an error', $result);
|
|
$this->assertContains('DebuggerTest.php', $result);
|
|
}
|
|
|
|
/**
|
|
* Test method for testing addFormat with callbacks.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function customFormat($error, $strings) {
|
|
return $error['error'] . ': I eated an error ' . $error['file'];
|
|
}
|
|
|
|
/**
|
|
* testTrimPath method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testTrimPath() {
|
|
$this->assertEquals('APP' . DS, Debugger::trimPath(APP));
|
|
$this->assertEquals('CORE', Debugger::trimPath(CAKE_CORE_INCLUDE_PATH));
|
|
$this->assertEquals('ROOT', Debugger::trimPath(ROOT));
|
|
$this->assertEquals('CORE' . DS . 'Cake' . DS, Debugger::trimPath(CAKE));
|
|
$this->assertEquals('Some/Other/Path', Debugger::trimPath('Some/Other/Path'));
|
|
}
|
|
|
|
/**
|
|
* testExportVar method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testExportVar() {
|
|
App::uses('Controller', 'Controller');
|
|
$Controller = new Controller();
|
|
$Controller->helpers = array('Html', 'Form');
|
|
$View = new View($Controller);
|
|
$View->int = 2;
|
|
$View->float = 1.333;
|
|
|
|
$result = Debugger::exportVar($View);
|
|
$expected = <<<TEXT
|
|
object(View) {
|
|
Helpers => object(HelperCollection) {}
|
|
Blocks => object(ViewBlock) {}
|
|
plugin => null
|
|
name => ''
|
|
passedArgs => array()
|
|
helpers => array(
|
|
(int) 0 => 'Html',
|
|
(int) 1 => 'Form'
|
|
)
|
|
viewPath => ''
|
|
viewVars => array()
|
|
view => null
|
|
layout => 'default'
|
|
layoutPath => null
|
|
autoLayout => true
|
|
ext => '.ctp'
|
|
subDir => null
|
|
theme => null
|
|
cacheAction => false
|
|
validationErrors => array()
|
|
hasRendered => false
|
|
uuids => array()
|
|
request => object(CakeRequest) {}
|
|
response => object(CakeResponse) {}
|
|
elementCache => 'default'
|
|
elementCacheSettings => array()
|
|
Html => object(HtmlHelper) {}
|
|
Form => object(FormHelper) {}
|
|
int => (int) 2
|
|
float => (float) 1.333
|
|
|
|
TEXT;
|
|
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
|
|
$expected .= <<<TEXT
|
|
[protected] _passedVars => array(
|
|
(int) 0 => 'viewVars',
|
|
(int) 1 => 'autoLayout',
|
|
(int) 2 => 'ext',
|
|
(int) 3 => 'helpers',
|
|
(int) 4 => 'view',
|
|
(int) 5 => 'layout',
|
|
(int) 6 => 'name',
|
|
(int) 7 => 'theme',
|
|
(int) 8 => 'layoutPath',
|
|
(int) 9 => 'viewPath',
|
|
(int) 10 => 'request',
|
|
(int) 11 => 'plugin',
|
|
(int) 12 => 'passedArgs',
|
|
(int) 13 => 'cacheAction'
|
|
)
|
|
[protected] _scripts => array()
|
|
[protected] _paths => array()
|
|
[protected] _pathsForPlugin => array()
|
|
[protected] _parents => array()
|
|
[protected] _current => null
|
|
[protected] _currentType => ''
|
|
[protected] _stack => array()
|
|
[protected] _eventManager => object(CakeEventManager) {}
|
|
[protected] _eventManagerConfigured => false
|
|
|
|
TEXT;
|
|
}
|
|
$expected .= <<<TEXT
|
|
}
|
|
TEXT;
|
|
|
|
$this->assertTextEquals($expected, $result);
|
|
|
|
$data = array(
|
|
1 => 'Index one',
|
|
5 => 'Index five'
|
|
);
|
|
$result = Debugger::exportVar($data);
|
|
$expected = <<<TEXT
|
|
array(
|
|
(int) 1 => 'Index one',
|
|
(int) 5 => 'Index five'
|
|
)
|
|
TEXT;
|
|
$this->assertTextEquals($expected, $result);
|
|
|
|
$data = array(
|
|
'key' => array(
|
|
'value'
|
|
)
|
|
);
|
|
$result = Debugger::exportVar($data, 1);
|
|
$expected = <<<TEXT
|
|
array(
|
|
'key' => array(
|
|
[maximum depth reached]
|
|
)
|
|
)
|
|
TEXT;
|
|
$this->assertTextEquals($expected, $result);
|
|
|
|
$data = false;
|
|
$result = Debugger::exportVar($data);
|
|
$expected = <<<TEXT
|
|
false
|
|
TEXT;
|
|
$this->assertTextEquals($expected, $result);
|
|
|
|
$file = fopen('php://output', 'w');
|
|
fclose($file);
|
|
$result = Debugger::exportVar($file);
|
|
$this->assertTextEquals('unknown', $result);
|
|
}
|
|
|
|
/**
|
|
* Test exporting various kinds of false.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testExportVarZero() {
|
|
$data = array(
|
|
'nothing' => '',
|
|
'null' => null,
|
|
'false' => false,
|
|
'szero' => '0',
|
|
'zero' => 0
|
|
);
|
|
$result = Debugger::exportVar($data);
|
|
$expected = <<<TEXT
|
|
array(
|
|
'nothing' => '',
|
|
'null' => null,
|
|
'false' => false,
|
|
'szero' => '0',
|
|
'zero' => (int) 0
|
|
)
|
|
TEXT;
|
|
$this->assertTextEquals($expected, $result);
|
|
}
|
|
|
|
/**
|
|
* testLog method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testLog() {
|
|
if (file_exists(LOGS . 'debug.log')) {
|
|
unlink(LOGS . 'debug.log');
|
|
}
|
|
CakeLog::config('file', array('engine' => 'File', 'path' => TMP . 'logs' . DS));
|
|
|
|
Debugger::log('cool');
|
|
$result = file_get_contents(LOGS . 'debug.log');
|
|
$this->assertContains('DebuggerTest::testLog', $result);
|
|
$this->assertContains("'cool'", $result);
|
|
|
|
unlink(LOGS . 'debug.log');
|
|
|
|
Debugger::log(array('whatever', 'here'));
|
|
$result = file_get_contents(LOGS . 'debug.log');
|
|
$this->assertContains('DebuggerTest::testLog', $result);
|
|
$this->assertContains('[main]', $result);
|
|
$this->assertContains('array', $result);
|
|
$this->assertContains("'whatever',", $result);
|
|
$this->assertContains("'here'", $result);
|
|
}
|
|
|
|
/**
|
|
* test log() depth
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testLogDepth() {
|
|
if (file_exists(LOGS . 'debug.log')) {
|
|
unlink(LOGS . 'debug.log');
|
|
}
|
|
CakeLog::config('file', array('engine' => 'File', 'path' => TMP . 'logs' . DS));
|
|
|
|
$val = array(
|
|
'test' => array('key' => 'val')
|
|
);
|
|
Debugger::log($val, LOG_DEBUG, 0);
|
|
$result = file_get_contents(LOGS . 'debug.log');
|
|
$this->assertContains('DebuggerTest::testLog', $result);
|
|
$this->assertNotContains("/'val'/", $result);
|
|
|
|
unlink(LOGS . 'debug.log');
|
|
}
|
|
|
|
/**
|
|
* testDump method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testDump() {
|
|
$var = array('People' => array(
|
|
array(
|
|
'name' => 'joeseph',
|
|
'coat' => 'technicolor',
|
|
'hair_color' => 'brown'
|
|
),
|
|
array(
|
|
'name' => 'Shaft',
|
|
'coat' => 'black',
|
|
'hair' => 'black'
|
|
)
|
|
));
|
|
ob_start();
|
|
Debugger::dump($var);
|
|
$result = ob_get_clean();
|
|
|
|
$open = PHP_SAPI === 'cli' ? "\n" : '<pre>';
|
|
$close = PHP_SAPI === 'cli' ? "\n" : '</pre>';
|
|
$expected = <<<TEXT
|
|
{$open}array(
|
|
'People' => array(
|
|
(int) 0 => array(
|
|
'name' => 'joeseph',
|
|
'coat' => 'technicolor',
|
|
'hair_color' => 'brown'
|
|
),
|
|
(int) 1 => array(
|
|
'name' => 'Shaft',
|
|
'coat' => 'black',
|
|
'hair' => 'black'
|
|
)
|
|
)
|
|
){$close}
|
|
TEXT;
|
|
$this->assertTextEquals($expected, $result);
|
|
|
|
ob_start();
|
|
Debugger::dump($var, 1);
|
|
$result = ob_get_clean();
|
|
|
|
$open = PHP_SAPI === 'cli' ? "\n" : '<pre>';
|
|
$close = PHP_SAPI === 'cli' ? "\n" : '</pre>';
|
|
$expected = <<<TEXT
|
|
{$open}array(
|
|
'People' => array(
|
|
[maximum depth reached]
|
|
)
|
|
){$close}
|
|
TEXT;
|
|
$this->assertTextEquals($expected, $result);
|
|
}
|
|
|
|
/**
|
|
* test getInstance.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testGetInstance() {
|
|
$result = Debugger::getInstance();
|
|
$this->assertInstanceOf('Debugger', $result);
|
|
|
|
$result = Debugger::getInstance('DebuggerTestCaseDebugger');
|
|
$this->assertInstanceOf('DebuggerTestCaseDebugger', $result);
|
|
|
|
$result = Debugger::getInstance();
|
|
$this->assertInstanceOf('DebuggerTestCaseDebugger', $result);
|
|
|
|
$result = Debugger::getInstance('Debugger');
|
|
$this->assertInstanceOf('Debugger', $result);
|
|
}
|
|
|
|
/**
|
|
* testNoDbCredentials
|
|
*
|
|
* If a connection error occurs, the config variable is passed through exportVar
|
|
* *** our database login credentials such that they are never visible
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testNoDbCredentials() {
|
|
$config = array(
|
|
'datasource' => 'mysql',
|
|
'persistent' => false,
|
|
'host' => 'void.cakephp.org',
|
|
'login' => 'cakephp-user',
|
|
'password' => 'cakephp-password',
|
|
'database' => 'cakephp-database',
|
|
'prefix' => ''
|
|
);
|
|
|
|
$output = Debugger::exportVar($config);
|
|
|
|
$expectedArray = array(
|
|
'datasource' => 'mysql',
|
|
'persistent' => false,
|
|
'host' => '*****',
|
|
'login' => '*****',
|
|
'password' => '*****',
|
|
'database' => '*****',
|
|
'prefix' => ''
|
|
);
|
|
$expected = Debugger::exportVar($expectedArray);
|
|
|
|
$this->assertEquals($expected, $output);
|
|
}
|
|
|
|
/**
|
|
* Test that exportVar() doesn't loop through recursive structures.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testExportVarRecursion() {
|
|
$output = Debugger::exportVar($GLOBALS);
|
|
$this->assertContains("'GLOBALS' => [recursion]", $output);
|
|
}
|
|
|
|
/**
|
|
* test trace exclude
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testTraceExclude() {
|
|
$result = Debugger::trace();
|
|
$this->assertRegExp('/^DebuggerTest::testTraceExclude/', $result);
|
|
|
|
$result = Debugger::trace(array(
|
|
'exclude' => array('DebuggerTest::testTraceExclude')
|
|
));
|
|
$this->assertNotRegExp('/^DebuggerTest::testTraceExclude/', $result);
|
|
}
|
|
}
|