Skip to content

Commit 99e0797

Browse files
authored
Merge pull request #5556 from codeigniter4/hotfix-4.1.7
Hotfix 4.1.7
2 parents 6b2816c + 3e088a9 commit 99e0797

File tree

22 files changed

+279
-50
lines changed

22 files changed

+279
-50
lines changed

CHANGELOG.md

+19
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
# Changelog
22

3+
## [v4.1.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.7) (2022-01-09)
4+
5+
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.6...v4.1.7)
6+
7+
**Breaking Changes**
8+
9+
* fix: replace deprecated FILTER_SANITIZE_STRING by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5555
10+
11+
**Fixed Bugs**
12+
13+
* fix: BaseConnection::getConnectDuration() number_format(): Passing null to parameter by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5536
14+
* Fix: Debug toolbar selectors by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5544
15+
* Fix: Toolbar. ciDebugBar.showTab() context. by @iRedds in https://github.com/codeigniter4/CodeIgniter4/pull/5554
16+
* Refactor Database Collector display by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/5553
17+
318
## [v4.1.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.6) (2022-01-03)
419

520
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.5...v4.1.6)
621

22+
**SECURITY**
23+
24+
* *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.
25+
726
**Breaking Changes**
827

928
* fix: Incorrect type `BaseBuilder::$tableName` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/5378

system/CodeIgniter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class CodeIgniter
4545
/**
4646
* The current version of CodeIgniter Framework
4747
*/
48-
public const CI_VERSION = '4.1.6';
48+
public const CI_VERSION = '4.1.7';
4949

5050
private const MIN_PHP_VERSION = '7.3';
5151

system/Database/BaseConnection.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ abstract class BaseConnection implements ConnectionInterface
258258
*
259259
* @var float
260260
*/
261-
protected $connectTime;
261+
protected $connectTime = 0.0;
262262

263263
/**
264264
* How long it took to establish connection.
265265
*
266266
* @var float
267267
*/
268-
protected $connectDuration;
268+
protected $connectDuration = 0.0;
269269

270270
/**
271271
* If true, no queries will actually be

system/Debug/Toolbar/Collectors/Database.php

+39-16
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,19 @@ public static function collect(Query $query)
8585
if (count(static::$queries) < $max) {
8686
$queryString = $query->getQuery();
8787

88+
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
89+
90+
if (! is_cli()) {
91+
// when called in the browser, the first two trace arrays
92+
// are from the DB event trigger, which are unneeded
93+
$backtrace = array_slice($backtrace, 2);
94+
}
95+
8896
static::$queries[] = [
8997
'query' => $query,
9098
'string' => $queryString,
9199
'duplicate' => in_array($queryString, array_column(static::$queries, 'string', null), true),
92-
'trace' => debug_backtrace(),
100+
'trace' => $backtrace,
93101
];
94102
}
95103
}
@@ -134,23 +142,39 @@ public function display(): array
134142
$data['queries'] = array_map(static function (array $query) {
135143
$isDuplicate = $query['duplicate'] === true;
136144

137-
// Find the first line that doesn't include `system` in the backtrace
138-
$line = [];
145+
$firstNonSystemLine = '';
146+
147+
foreach ($query['trace'] as $index => &$line) {
148+
// simplify file and line
149+
if (isset($line['file'])) {
150+
$line['file'] = clean_path($line['file']) . ':' . $line['line'];
151+
unset($line['line']);
152+
} else {
153+
$line['file'] = '[internal function]';
154+
}
155+
156+
// find the first trace line that does not originate from `system/`
157+
if ($firstNonSystemLine === '' && strpos($line['file'], 'SYSTEMPATH') === false) {
158+
$firstNonSystemLine = $line['file'];
159+
}
139160

140-
foreach ($query['trace'] as &$traceLine) {
141-
// Clean up the file paths
142-
$traceLine['file'] = str_ireplace(APPPATH, 'APPPATH/', $traceLine['file']);
143-
$traceLine['file'] = str_ireplace(SYSTEMPATH, 'SYSTEMPATH/', $traceLine['file']);
144-
if (defined('VENDORPATH')) {
145-
// VENDORPATH is not defined unless `vendor/autoload.php` exists
146-
$traceLine['file'] = str_ireplace(VENDORPATH, 'VENDORPATH/', $traceLine['file']);
161+
// simplify function call
162+
if (isset($line['class'])) {
163+
$line['function'] = $line['class'] . $line['type'] . $line['function'];
164+
unset($line['class'], $line['type']);
147165
}
148-
$traceLine['file'] = str_ireplace(ROOTPATH, 'ROOTPATH/', $traceLine['file']);
149166

150-
if (strpos($traceLine['file'], 'SYSTEMPATH') !== false) {
151-
continue;
167+
if (strrpos($line['function'], '{closure}') === false) {
168+
$line['function'] .= '()';
152169
}
153-
$line = empty($line) ? $traceLine : $line;
170+
171+
$line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function'];
172+
173+
// add index numbering padded with nonbreaking space
174+
$indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT);
175+
$indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded);
176+
177+
$line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4);
154178
}
155179

156180
return [
@@ -159,8 +183,7 @@ public function display(): array
159183
'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms',
160184
'sql' => $query['query']->debugToolbarDisplay(),
161185
'trace' => $query['trace'],
162-
'trace-file' => str_replace(ROOTPATH, '/', $line['file'] ?? ''),
163-
'trace-line' => $line['line'] ?? '',
186+
'trace-file' => $firstNonSystemLine,
164187
'qid' => md5($query['query'] . microtime()),
165188
];
166189
}, static::$queries);

system/Debug/Toolbar/Views/_database.tpl

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
<tr class="{class}" title="{hover}" data-toggle="{qid}-trace">
1111
<td class="narrow">{duration}</td>
1212
<td>{! sql !}</td>
13-
<td style="text-align: right">{trace-file}:<strong>{trace-line}</strong></td>
13+
<td style="text-align: right"><strong>{trace-file}</strong></td>
1414
</tr>
1515
<tr class="muted" id="{qid}-trace" style="display:none">
1616
<td></td>
1717
<td colspan="2">
1818
{trace}
19-
{file}:<strong>{line}</strong><br/>
19+
{index}<strong>{file}</strong><br/>
20+
{function}<br/><br/>
2021
{/trace}
2122
</td>
2223
</tr>

system/Debug/Toolbar/Views/toolbar.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ var ciDebugBar = {
2424
document.getElementById('debug-icon-link').addEventListener('click', ciDebugBar.toggleToolbar, true);
2525

2626
// Allows to highlight the row of the current history request
27-
var btn = document.querySelector('button[data-time="' + localStorage.getItem('debugbar-time') + '"]');
27+
var btn = this.toolbar.querySelector('button[data-time="' + localStorage.getItem('debugbar-time') + '"]');
2828
ciDebugBar.addClass(btn.parentNode.parentNode, 'current');
2929

30-
historyLoad = document.getElementsByClassName('ci-history-load');
30+
historyLoad = this.toolbar.getElementsByClassName('ci-history-load');
3131

3232
for (var i = 0; i < historyLoad.length; i++)
3333
{
@@ -52,15 +52,15 @@ var ciDebugBar = {
5252
},
5353

5454
createListeners : function () {
55-
var buttons = [].slice.call(document.querySelectorAll('#debug-bar .ci-label a'));
55+
var buttons = [].slice.call(this.toolbar.querySelectorAll('.ci-label a'));
5656

5757
for (var i = 0; i < buttons.length; i++)
5858
{
5959
buttons[i].addEventListener('click', ciDebugBar.showTab, true);
6060
}
6161

6262
// Hook up generic toggle via data attributes `data-toggle="foo"`
63-
var links = document.querySelectorAll('[data-toggle]');
63+
var links = this.toolbar.querySelectorAll('[data-toggle]');
6464
for (var i = 0; i < links.length; i++)
6565
{
6666
links[i].addEventListener('click', ciDebugBar.toggleRows, true);
@@ -502,7 +502,7 @@ var ciDebugBar = {
502502
},
503503

504504
setToolbarPosition: function () {
505-
var btnPosition = document.getElementById('toolbar-position');
505+
var btnPosition = this.toolbar.querySelector('#toolbar-position');
506506

507507
if (ciDebugBar.readCookie('debug-bar-position') === 'top')
508508
{
@@ -531,7 +531,7 @@ var ciDebugBar = {
531531
},
532532

533533
setToolbarTheme: function () {
534-
var btnTheme = document.getElementById('toolbar-theme');
534+
var btnTheme = this.toolbar.querySelector('#toolbar-theme');
535535
var isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
536536
var isLightMode = window.matchMedia("(prefers-color-scheme: light)").matches;
537537

@@ -627,7 +627,7 @@ var ciDebugBar = {
627627

628628
routerLink: function () {
629629
var row, _location;
630-
var rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"]');
630+
var rowGet = this.toolbar.querySelectorAll('td[data-debugbar-route="GET"]');
631631
var patt = /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/;
632632

633633
for (var i = 0; i < rowGet.length; i++)
@@ -653,7 +653,7 @@ var ciDebugBar = {
653653
}
654654
}
655655

656-
rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"] form');
656+
rowGet = this.toolbar.querySelectorAll('td[data-debugbar-route="GET"] form');
657657
for (var i = 0; i < rowGet.length; i++)
658658
{
659659
row = rowGet[i];

system/Helpers/cookie_helper.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function get_cookie($index, bool $xssClean = false)
6565
{
6666
$prefix = isset($_COOKIE[$index]) ? '' : config(App::class)->cookiePrefix;
6767
$request = Services::request();
68-
$filter = $xssClean ? FILTER_SANITIZE_STRING : FILTER_DEFAULT;
68+
$filter = $xssClean ? FILTER_SANITIZE_FULL_SPECIAL_CHARS : FILTER_DEFAULT;
6969

7070
return $request->getCookie($prefix . $index, $filter);
7171
}

system/ThirdParty/Kint/Renderer/CliRenderer.php

+26-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ class CliRenderer extends TextRenderer
5959
*/
6060
public static $min_terminal_width = 40;
6161

62+
/**
63+
* Which stream to check for VT100 support on windows.
64+
*
65+
* null uses STDOUT if it's defined
66+
*
67+
* @var null|resource
68+
*/
69+
public static $windows_stream = null;
70+
6271
protected static $terminal_width = null;
6372

6473
protected $windows_output = false;
@@ -69,8 +78,22 @@ public function __construct()
6978
{
7079
parent::__construct();
7180

72-
if (!self::$force_utf8) {
73-
$this->windows_output = KINT_WIN;
81+
if (!self::$force_utf8 && KINT_WIN) {
82+
if (!KINT_PHP72) {
83+
$this->windows_output = true;
84+
} else {
85+
$stream = self::$windows_stream;
86+
87+
if (!$stream && \defined('STDOUT')) {
88+
$stream = STDOUT;
89+
}
90+
91+
if (!$stream) {
92+
$this->windows_output = true;
93+
} else {
94+
$this->windows_output = !\sapi_windows_vt100_support($stream);
95+
}
96+
}
7497
}
7598

7699
if (!self::$terminal_width) {
@@ -153,7 +176,7 @@ protected function utf8ToWindows($string)
153176
{
154177
return \str_replace(
155178
['', '', '', '', '', '', ''],
156-
["\xda", "\xdc", "\xbf", "\xb3", "\xc0", "\xc4", "\xd9"],
179+
[' ', '=', ' ', '|', ' ', '-', ' '],
157180
$string
158181
);
159182
}

system/ThirdParty/Kint/Renderer/RichRenderer.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ class RichRenderer extends Renderer
133133

134134
public static $always_pre_render = false;
135135

136+
public static $js_nonce = null;
137+
public static $css_nonce = null;
138+
136139
protected $plugin_objs = [];
137140
protected $expand = false;
138141
protected $force_pre_render = false;
@@ -389,10 +392,18 @@ public function preRender()
389392

390393
switch ($type) {
391394
case 'script':
392-
$output .= '<script class="kint-rich-script">'.$contents.'</script>';
395+
$output .= '<script class="kint-rich-script"';
396+
if (null !== self::$js_nonce) {
397+
$output .= ' nonce="'.\htmlspecialchars(self::$js_nonce).'"';
398+
}
399+
$output .= '>'.$contents.'</script>';
393400
break;
394401
case 'style':
395-
$output .= '<style class="kint-rich-style">'.$contents.'</style>';
402+
$output .= '<style class="kint-rich-style"';
403+
if (null !== self::$css_nonce) {
404+
$output .= ' nonce="'.\htmlspecialchars(self::$css_nonce).'"';
405+
}
406+
$output .= '>'.$contents.'</style>';
396407
break;
397408
default:
398409
$output .= $contents;

tests/system/Database/BaseConnectionTest.php

+10
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ public function testStoresConnectionTimings()
123123
$this->assertGreaterThan(0.0, $db->getConnectDuration());
124124
}
125125

126+
/**
127+
* @see https://github.com/codeigniter4/CodeIgniter4/issues/5535
128+
*/
129+
public function testStoresConnectionTimingsNotConnected()
130+
{
131+
$db = new MockConnection($this->options);
132+
133+
$this->assertSame('0.000000', $db->getConnectDuration());
134+
}
135+
126136
public function testMagicIssetTrue()
127137
{
128138
$db = new MockConnection($this->options);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Debug\Toolbar\Collectors;
13+
14+
use CodeIgniter\Database\Query;
15+
use CodeIgniter\Test\CIUnitTestCase;
16+
use PHPUnit\Framework\MockObject\MockObject;
17+
18+
/**
19+
* @internal
20+
*/
21+
final class DatabaseTest extends CIUnitTestCase
22+
{
23+
public function testDisplay(): void
24+
{
25+
/** @var MockObject&Query $query */
26+
$query = $this->getMockBuilder(Query::class)
27+
->disableOriginalConstructor()
28+
->getMock();
29+
30+
// set mock returns
31+
$query->method('getQuery')->willReturn('SHOW TABLES;');
32+
$query->method('debugToolbarDisplay')->willReturn('<strong>SHOW</strong> TABLES;');
33+
$query->method('getDuration')->with(5)->willReturn('1.23456');
34+
35+
Database::collect($query); // <== $query will be called here
36+
$collector = new Database();
37+
38+
$queries = $collector->display()['queries'];
39+
40+
$this->assertSame('1234.56 ms', $queries[0]['duration']);
41+
$this->assertSame('<strong>SHOW</strong> TABLES;', $queries[0]['sql']);
42+
$this->assertSame(clean_path(__FILE__) . ':35', $queries[0]['trace-file']);
43+
44+
foreach ($queries[0]['trace'] as $i => $trace) {
45+
// since we added the index numbering
46+
$this->assertArrayHasKey('index', $trace);
47+
$this->assertSame(
48+
sprintf('%s', $i + 1),
49+
preg_replace(sprintf('/%s/', chr(0xC2) . chr(0xA0)), '', $trace['index'])
50+
);
51+
52+
// since we merged file & line together
53+
$this->assertArrayNotHasKey('line', $trace);
54+
$this->assertArrayHasKey('file', $trace);
55+
56+
// since we dropped object & args in the backtrace for performance
57+
// but args MAY STILL BE present in internal calls
58+
$this->assertArrayNotHasKey('object', $trace);
59+
}
60+
}
61+
}

user_guide_src/source/changelogs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ See all the changes.
1212
.. toctree::
1313
:titlesonly:
1414

15+
v4.1.7
1516
v4.1.6
1617
v4.1.5
1718
v4.1.4

0 commit comments

Comments
 (0)