diff --git a/CHANGELOG.md b/CHANGELOG.md index c2ea5e536da0..23c77082242e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,28 @@ # Changelog +## [v4.1.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.7) (2022-01-09) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.6...v4.1.7) + +**Breaking Changes** + +* fix: replace deprecated FILTER_SANITIZE_STRING by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5555 + +**Fixed Bugs** + +* fix: BaseConnection::getConnectDuration() number_format(): Passing null to parameter by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5536 +* Fix: Debug toolbar selectors by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5544 +* Fix: Toolbar. ciDebugBar.showTab() context. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5554 +* Refactor Database Collector display by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5553 + ## [v4.1.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.6) (2022-01-03) [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.5...v4.1.6) +**SECURITY** + +* *Deserialization of Untrusted Data* found in the ``old()`` function was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-w6jr-wj64-mc9x) for more information. + **Breaking Changes** * fix: Incorrect type `BaseBuilder::$tableName` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5378 diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index bcce77f1f853..ed4e500466f6 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -45,7 +45,7 @@ class CodeIgniter /** * The current version of CodeIgniter Framework */ - public const CI_VERSION = '4.1.6'; + public const CI_VERSION = '4.1.7'; private const MIN_PHP_VERSION = '7.3'; diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 5f678e5114d7..53e5dad1b351 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -258,14 +258,14 @@ abstract class BaseConnection implements ConnectionInterface * * @var float */ - protected $connectTime; + protected $connectTime = 0.0; /** * How long it took to establish connection. * * @var float */ - protected $connectDuration; + protected $connectDuration = 0.0; /** * If true, no queries will actually be diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php index 520ddc7c5dc6..26cb02ddfeb0 100644 --- a/system/Debug/Toolbar/Collectors/Database.php +++ b/system/Debug/Toolbar/Collectors/Database.php @@ -85,11 +85,19 @@ public static function collect(Query $query) if (count(static::$queries) < $max) { $queryString = $query->getQuery(); + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + if (! is_cli()) { + // when called in the browser, the first two trace arrays + // are from the DB event trigger, which are unneeded + $backtrace = array_slice($backtrace, 2); + } + static::$queries[] = [ 'query' => $query, 'string' => $queryString, 'duplicate' => in_array($queryString, array_column(static::$queries, 'string', null), true), - 'trace' => debug_backtrace(), + 'trace' => $backtrace, ]; } } @@ -134,23 +142,39 @@ public function display(): array $data['queries'] = array_map(static function (array $query) { $isDuplicate = $query['duplicate'] === true; - // Find the first line that doesn't include `system` in the backtrace - $line = []; + $firstNonSystemLine = ''; + + foreach ($query['trace'] as $index => &$line) { + // simplify file and line + if (isset($line['file'])) { + $line['file'] = clean_path($line['file']) . ':' . $line['line']; + unset($line['line']); + } else { + $line['file'] = '[internal function]'; + } + + // find the first trace line that does not originate from `system/` + if ($firstNonSystemLine === '' && strpos($line['file'], 'SYSTEMPATH') === false) { + $firstNonSystemLine = $line['file']; + } - foreach ($query['trace'] as &$traceLine) { - // Clean up the file paths - $traceLine['file'] = str_ireplace(APPPATH, 'APPPATH/', $traceLine['file']); - $traceLine['file'] = str_ireplace(SYSTEMPATH, 'SYSTEMPATH/', $traceLine['file']); - if (defined('VENDORPATH')) { - // VENDORPATH is not defined unless `vendor/autoload.php` exists - $traceLine['file'] = str_ireplace(VENDORPATH, 'VENDORPATH/', $traceLine['file']); + // simplify function call + if (isset($line['class'])) { + $line['function'] = $line['class'] . $line['type'] . $line['function']; + unset($line['class'], $line['type']); } - $traceLine['file'] = str_ireplace(ROOTPATH, 'ROOTPATH/', $traceLine['file']); - if (strpos($traceLine['file'], 'SYSTEMPATH') !== false) { - continue; + if (strrpos($line['function'], '{closure}') === false) { + $line['function'] .= '()'; } - $line = empty($line) ? $traceLine : $line; + + $line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function']; + + // add index numbering padded with nonbreaking space + $indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT); + $indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded); + + $line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4); } return [ @@ -159,8 +183,7 @@ public function display(): array 'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms', 'sql' => $query['query']->debugToolbarDisplay(), 'trace' => $query['trace'], - 'trace-file' => str_replace(ROOTPATH, '/', $line['file'] ?? ''), - 'trace-line' => $line['line'] ?? '', + 'trace-file' => $firstNonSystemLine, 'qid' => md5($query['query'] . microtime()), ]; }, static::$queries); diff --git a/system/Debug/Toolbar/Views/_database.tpl b/system/Debug/Toolbar/Views/_database.tpl index a2f5bd9808f1..1bd9b8a88405 100644 --- a/system/Debug/Toolbar/Views/_database.tpl +++ b/system/Debug/Toolbar/Views/_database.tpl @@ -10,13 +10,14 @@ {duration} {! sql !} - {trace-file}:{trace-line} + {trace-file} {trace} - {file}:{line}
+ {index}{file}
+ {function}

{/trace} diff --git a/system/Debug/Toolbar/Views/toolbar.js b/system/Debug/Toolbar/Views/toolbar.js index b6883d4b9f3b..3bd05d5e9592 100644 --- a/system/Debug/Toolbar/Views/toolbar.js +++ b/system/Debug/Toolbar/Views/toolbar.js @@ -24,10 +24,10 @@ var ciDebugBar = { document.getElementById('debug-icon-link').addEventListener('click', ciDebugBar.toggleToolbar, true); // Allows to highlight the row of the current history request - var btn = document.querySelector('button[data-time="' + localStorage.getItem('debugbar-time') + '"]'); + var btn = this.toolbar.querySelector('button[data-time="' + localStorage.getItem('debugbar-time') + '"]'); ciDebugBar.addClass(btn.parentNode.parentNode, 'current'); - historyLoad = document.getElementsByClassName('ci-history-load'); + historyLoad = this.toolbar.getElementsByClassName('ci-history-load'); for (var i = 0; i < historyLoad.length; i++) { @@ -52,7 +52,7 @@ var ciDebugBar = { }, createListeners : function () { - var buttons = [].slice.call(document.querySelectorAll('#debug-bar .ci-label a')); + var buttons = [].slice.call(this.toolbar.querySelectorAll('.ci-label a')); for (var i = 0; i < buttons.length; i++) { @@ -60,7 +60,7 @@ var ciDebugBar = { } // Hook up generic toggle via data attributes `data-toggle="foo"` - var links = document.querySelectorAll('[data-toggle]'); + var links = this.toolbar.querySelectorAll('[data-toggle]'); for (var i = 0; i < links.length; i++) { links[i].addEventListener('click', ciDebugBar.toggleRows, true); @@ -502,7 +502,7 @@ var ciDebugBar = { }, setToolbarPosition: function () { - var btnPosition = document.getElementById('toolbar-position'); + var btnPosition = this.toolbar.querySelector('#toolbar-position'); if (ciDebugBar.readCookie('debug-bar-position') === 'top') { @@ -531,7 +531,7 @@ var ciDebugBar = { }, setToolbarTheme: function () { - var btnTheme = document.getElementById('toolbar-theme'); + var btnTheme = this.toolbar.querySelector('#toolbar-theme'); var isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; var isLightMode = window.matchMedia("(prefers-color-scheme: light)").matches; @@ -627,7 +627,7 @@ var ciDebugBar = { routerLink: function () { var row, _location; - var rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"]'); + var rowGet = this.toolbar.querySelectorAll('td[data-debugbar-route="GET"]'); var patt = /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/; for (var i = 0; i < rowGet.length; i++) @@ -653,7 +653,7 @@ var ciDebugBar = { } } - rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"] form'); + rowGet = this.toolbar.querySelectorAll('td[data-debugbar-route="GET"] form'); for (var i = 0; i < rowGet.length; i++) { row = rowGet[i]; diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index e47a86356dbf..51a746c5395b 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -65,7 +65,7 @@ function get_cookie($index, bool $xssClean = false) { $prefix = isset($_COOKIE[$index]) ? '' : config(App::class)->cookiePrefix; $request = Services::request(); - $filter = $xssClean ? FILTER_SANITIZE_STRING : FILTER_DEFAULT; + $filter = $xssClean ? FILTER_SANITIZE_FULL_SPECIAL_CHARS : FILTER_DEFAULT; return $request->getCookie($prefix . $index, $filter); } diff --git a/system/ThirdParty/Kint/Renderer/CliRenderer.php b/system/ThirdParty/Kint/Renderer/CliRenderer.php index f86671ff1cf3..12b18d036b62 100644 --- a/system/ThirdParty/Kint/Renderer/CliRenderer.php +++ b/system/ThirdParty/Kint/Renderer/CliRenderer.php @@ -59,6 +59,15 @@ class CliRenderer extends TextRenderer */ public static $min_terminal_width = 40; + /** + * Which stream to check for VT100 support on windows. + * + * null uses STDOUT if it's defined + * + * @var null|resource + */ + public static $windows_stream = null; + protected static $terminal_width = null; protected $windows_output = false; @@ -69,8 +78,22 @@ public function __construct() { parent::__construct(); - if (!self::$force_utf8) { - $this->windows_output = KINT_WIN; + if (!self::$force_utf8 && KINT_WIN) { + if (!KINT_PHP72) { + $this->windows_output = true; + } else { + $stream = self::$windows_stream; + + if (!$stream && \defined('STDOUT')) { + $stream = STDOUT; + } + + if (!$stream) { + $this->windows_output = true; + } else { + $this->windows_output = !\sapi_windows_vt100_support($stream); + } + } } if (!self::$terminal_width) { @@ -153,7 +176,7 @@ protected function utf8ToWindows($string) { return \str_replace( ['┌', '═', '┐', '│', '└', '─', '┘'], - ["\xda", "\xdc", "\xbf", "\xb3", "\xc0", "\xc4", "\xd9"], + [' ', '=', ' ', '|', ' ', '-', ' '], $string ); } diff --git a/system/ThirdParty/Kint/Renderer/RichRenderer.php b/system/ThirdParty/Kint/Renderer/RichRenderer.php index 94b7f98ab71f..46a8827ded56 100644 --- a/system/ThirdParty/Kint/Renderer/RichRenderer.php +++ b/system/ThirdParty/Kint/Renderer/RichRenderer.php @@ -133,6 +133,9 @@ class RichRenderer extends Renderer public static $always_pre_render = false; + public static $js_nonce = null; + public static $css_nonce = null; + protected $plugin_objs = []; protected $expand = false; protected $force_pre_render = false; @@ -389,10 +392,18 @@ public function preRender() switch ($type) { case 'script': - $output .= ''; + $output .= '