Skip to content

Commit 2f40190

Browse files
committed
Security/EscapeOutput: bug fix - sniff did not handle arrays correctly
In particular when examining the parameters passed to a function call, the sniff may encounter parameters which are arrays. As things were, the sniff would skip over the array opener and then continue like before. This is buggy, in the same way as examining all function parameters as one long code block was buggy and would lead to errors throw on the incorrect token and/or false positives when the array item contains a ternary. To fix this, I'm special casing arrays, parsing the array items and then recursively calling the `check_code_is_escaped()` method to examine each array item individually. After this, the original call to `check_code_is_escaped()` will continue from the array closer onwards. This gives much higher accuracy when examining array items and fixes the bug. Includes tests proving the bug(s) and safeguarding the fix.
1 parent 8e7b9a4 commit 2f40190

File tree

3 files changed

+35
-6
lines changed

3 files changed

+35
-6
lines changed

WordPress/Sniffs/Security/EscapeOutputSniff.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHP_CodeSniffer\Util\Tokens;
1313
use PHPCSUtils\BackCompat\BCFile;
1414
use PHPCSUtils\Tokens\Collections;
15+
use PHPCSUtils\Utils\Arrays;
1516
use PHPCSUtils\Utils\Conditions;
1617
use PHPCSUtils\Utils\Operators;
1718
use PHPCSUtils\Utils\PassedParameters;
@@ -536,13 +537,22 @@ protected function check_code_is_escaped( $start, $end ) {
536537
continue;
537538
}
538539

539-
// Handle arrays for those functions that accept them.
540-
if ( \T_ARRAY === $this->tokens[ $i ]['code'] ) {
541-
++$i; // Skip the opening parenthesis.
542-
continue;
543-
}
540+
// Examine the items in an array individually for array parameters.
541+
if ( isset( Collections::arrayOpenTokensBC()[ $this->tokens[ $i ]['code'] ] ) ) {
542+
$array_open_close = Arrays::getOpenClose( $this->phpcsFile, $i );
543+
if ( false === $array_open_close ) {
544+
// Short list or misidentified short array token.
545+
continue;
546+
}
547+
548+
$array_items = PassedParameters::getParameters( $this->phpcsFile, $i, 0, true );
549+
if ( ! empty( $array_items ) ) {
550+
foreach ( $array_items as $array_item ) {
551+
$this->check_code_is_escaped( $array_item['start'], ( $array_item['end'] + 1 ) );
552+
}
553+
}
544554

545-
if ( isset( Collections::shortArrayTokens()[ $this->tokens[ $i ]['code'] ] ) ) {
555+
$i = $array_open_close['closer'];
546556
continue;
547557
}
548558

WordPress/Tests/Security/EscapeOutputUnitTest.1.inc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,3 +605,18 @@ _e( domain: $domain ); // OK, well not really, required $text parameter missing,
605605
_ex( domain: $domain, text: 'plain text', context: $context ); // OK.
606606
_e( domain: $domain, text: $text, ); // Bad x 1, only text param.
607607
// phpcs:enable
608+
609+
// Array keys and values should be examined individually.
610+
wp_die( esc_html( $message ), '', array() ); // OK.
611+
wp_die(
612+
esc_html( $message ), // OK.
613+
'',
614+
array(
615+
'back_link' => true, // OK.
616+
'response' => $response ?? 200, // Bad.
617+
'link_url' => ( '' !== $this->link_url ) ? $this->link_url : '', // Bad.
618+
'link_text' => ( '' !== $link_title ) ? esc_attr( $link_title ) : '', // OK.
619+
'charset' => [$set, $rtl] = $array, // Bad x 2 (silly code, but the list shouldn't be treated as an array).
620+
'exit' => $do_exit, // Bad x 1.
621+
)
622+
);

WordPress/Tests/Security/EscapeOutputUnitTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ public function getErrorList( $testFile = '' ) {
149149
595 => 1,
150150
603 => 1,
151151
606 => 1,
152+
616 => 1,
153+
617 => 1,
154+
619 => 2,
155+
620 => 1,
152156
);
153157

154158
case 'EscapeOutputUnitTest.6.inc':

0 commit comments

Comments
 (0)