Skip to content

Commit 0ef36b8

Browse files
committed
Security/StaticStrreplace: use pre-parsed function call argument information / support PHP 8.0+ named arguments
As things were, the sniff walked the tokens in the function call itself, but what with the switch to extending the `AbstractFunctionParameterSniff` class, we already have the start and end pointers of each passed parameter available, so let's start using that information instead of doing the token walking within the sniff. By using the pre-parsed information, the sniff automatically will start supporting the use of PHP 8.0+ named arguments in function calls. Includes tests.
1 parent 697d2c5 commit 0ef36b8

File tree

3 files changed

+33
-22
lines changed

3 files changed

+33
-22
lines changed

WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
namespace WordPressVIPMinimum\Sniffs\Security;
1111

1212
use PHP_CodeSniffer\Util\Tokens;
13+
use PHPCSUtils\Exceptions\UnexpectedTokenType;
1314
use PHPCSUtils\Tokens\Collections;
14-
use PHPCSUtils\Utils\Arrays;
1515
use PHPCSUtils\Utils\PassedParameters;
1616
use WordPressCS\WordPress\AbstractFunctionParameterSniff;
1717

@@ -48,49 +48,50 @@ class StaticStrreplaceSniff extends AbstractFunctionParameterSniff {
4848
* @return void
4949
*/
5050
public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) {
51-
52-
$openBracket = $this->phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true );
53-
54-
if ( $this->tokens[ $openBracket ]['code'] !== T_OPEN_PARENTHESIS ) {
51+
$search_param = PassedParameters::getParameterFromStack( $parameters, 1, 'search' );
52+
$replace_param = PassedParameters::getParameterFromStack( $parameters, 2, 'replace' );
53+
$subject_param = PassedParameters::getParameterFromStack( $parameters, 3, 'subject' );
54+
55+
if ( $search_param === false || $replace_param === false || $subject_param === false ) {
56+
/*
57+
* Either an invalid function call (missing PHP required parameter); or function call
58+
* with argument unpacking; or live coding.
59+
* In all these cases, this is not the code pattern this sniff is looking for, so bow out.
60+
*/
5561
return;
5662
}
5763

5864
$static_text_tokens = Tokens::$emptyTokens;
5965
$static_text_tokens[ T_CONSTANT_ENCAPSED_STRING ] = T_CONSTANT_ENCAPSED_STRING;
6066

61-
$next_start_ptr = $openBracket + 1;
62-
for ( $i = 0; $i < 3; $i++ ) {
63-
$param_ptr = $this->phpcsFile->findNext( array_merge( Tokens::$emptyTokens, [ T_COMMA ] ), $next_start_ptr, null, true );
64-
if ( $param_ptr === false ) {
65-
// Live coding or parse error. Ignore.
66-
return;
67+
foreach ( [ $search_param, $replace_param, $subject_param ] as $param ) {
68+
$has_non_static_text = $this->phpcsFile->findNext( $static_text_tokens, $param['start'], ( $param['end'] + 1 ), true );
69+
if ( $has_non_static_text === false ) {
70+
// The parameter contained only tokens which could be considered static text.
71+
continue;
6772
}
6873

69-
if ( isset( Collections::arrayOpenTokensBC()[ $this->tokens[ $param_ptr ]['code'] ] ) ) {
70-
$arrayOpenClose = Arrays::getOpenClose( $this->phpcsFile, $param_ptr );
71-
if ( $arrayOpenClose === false ) {
74+
if ( isset( Collections::arrayOpenTokensBC()[ $this->tokens[ $has_non_static_text ]['code'] ] ) ) {
75+
try {
76+
$array_items = PassedParameters::getParameters( $this->phpcsFile, $has_non_static_text );
77+
} catch ( UnexpectedTokenType $e ) {
7278
// Short list, parse error or live coding, bow out.
7379
return;
7480
}
7581

76-
$array_items = PassedParameters::getParameters( $this->phpcsFile, $param_ptr );
7782
foreach ( $array_items as $array_item ) {
7883
$has_non_static_text = $this->phpcsFile->findNext( $static_text_tokens, $array_item['start'], $array_item['end'], true );
7984
if ( $has_non_static_text !== false ) {
8085
return;
8186
}
8287
}
8388

84-
$next_start_ptr = $arrayOpenClose['closer'] + 1;
89+
// The array only contained items with tokens which could be considered static text.
8590
continue;
8691
}
8792

88-
if ( $this->tokens[ $param_ptr ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) {
89-
return;
90-
}
91-
92-
$next_start_ptr = $param_ptr + 1;
93-
93+
// Non-static text token found. Not what we're looking for.
94+
return;
9495
}
9596

9697
$message = 'This code pattern is often used to run a very dangerous shell programs on your server. The code in these files needs to be reviewed, and possibly cleaned.';

WordPressVIPMinimum/Tests/Security/StaticStrreplaceUnitTest.inc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,11 @@ str_replace( 'foo', 'bar', 'foobar', $count ); // Bad.
6161

6262
// Don't confuse PHP 7.1+ short list with short array.
6363
str_replace( [ $a, $b ] = $search, [ 'bar', 'foo' ], 'foobar', ); // OK.
64+
65+
// Safeguard support for PHP 8.0+ named parameters.
66+
str_replace( search: [ 'foo', 'bar' ], replace: 'baz', count: $count ); // OK, well not really, missing required $subject param, but not our concern.
67+
str_replace( search: 'foo', replace: 'baz', subjects: [ 'foobar', 'foobar' ] ); // OK, well not really, typo in $subject param name, not our concern.
68+
str_replace( subject: $subject, count: $count, replace: 'baz', search: 'foo', ); // OK, different parameter order.
69+
70+
str_replace( count: $count, subject: [ 'foobar', 'foobar' ], search: 'foo', replace: 'baz', ); // Bad with different parameter order.
71+
str_replace( [ 'foo', 'bar' ], 'baz', count: $count, subject: 'foobar' ); // Bad, with mixed named and unnamed params.

WordPressVIPMinimum/Tests/Security/StaticStrreplaceUnitTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public function getErrorList() {
2828
56 => 1,
2929
57 => 1,
3030
60 => 1,
31+
70 => 1,
32+
71 => 1,
3133
];
3234
}
3335

0 commit comments

Comments
 (0)