Skip to content

Commit 58ac13b

Browse files
committed
SanitizationHelperTrait::is_sanitized(): add tests
1 parent 04244a8 commit 58ac13b

2 files changed

Lines changed: 363 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
/*
4+
* Test cases that should not be considered sanitized.
5+
*/
6+
7+
/* testNotWithinFunctionCall */
8+
$var = $_GET['bar'];
9+
10+
/* testNonSanitizingFunction */
11+
strtolower( $_REQUEST['name'] );
12+
13+
/* testInnerFunctionNotSanitizing */
14+
sanitize_text_field( strtolower( get_input() ) ); // T_STRING entry token: not reached via the sniffs that call the method (they only ever pass a T_VARIABLE), but the method supports any token type.
15+
16+
/* testUnslashedInNonSanitizingFunction */
17+
strtolower( wp_unslash( $_POST['content'] ) );
18+
19+
/* testPartiallyQualified */
20+
MyNamespace\sanitize_text_field( $_POST['title'] );
21+
22+
/* testFullyQualifiedNamespaced */
23+
\MyNamespace\absint( $_GET['id'] );
24+
25+
/* testNamespaceRelative */
26+
namespace\array_map( 'sanitize_text_field', $_REQUEST['items'] ); // The method should recognize this as sanitized once it can resolve relative namespaces since this test file is not namespaced.
27+
28+
/* testNamespaceRelativeSub */
29+
namespace\Sub\wp_unslash( $_SERVER['foo'] );
30+
31+
$obj->sanitize_text_field( /* testMethodCall */ $_POST['title'] );
32+
33+
/* testStaticMethodCall */
34+
MyClass::sanitize_text_field( $_POST['title'] );
35+
36+
/* testArrayWalkingNonSanitizingCallback */
37+
array_map( 'strtolower', $_POST['items'] );
38+
39+
/* testArrayWalkingNonStringCallback */
40+
array_map( array( $obj, 'sanitize_text_field' ), $_POST['items'] );
41+
42+
/* testArrayWalkingMissingCallback */
43+
map_deep( value: $_POST['items'] ); // Missing the required callback argument, but that's not the concern of the method.
44+
45+
/*
46+
* Test cases that should be considered sanitized.
47+
*/
48+
49+
/* testInUnset */
50+
unset( $_COOKIE['temp'] );
51+
52+
/* testSafeCast */
53+
(int) $_POST['count'];
54+
55+
/* testSanitizingFunction */
56+
sanitize_text_field( $_GET['name'] );
57+
58+
/* testSanitizingAndUnslashingFunction */
59+
\absint( $_POST['id'] );
60+
61+
/* testUnslashedThenSanitized */
62+
sanitize_text_field( wp_unslash( $_POST['name'] ) );
63+
64+
/* testArrayWalkingSanitizingCallback */
65+
array_map( 'sanitize_text_field', $_POST['items'] );
66+
67+
sanitize_text_field( /* testStringTokenSanitized */ get_input() ); // T_STRING entry token: not reached via the sniffs that call the method (they only ever pass a T_VARIABLE), but the method supports any token type.
68+
69+
/*
70+
* Unslashing function call wrapped in a sanitizing function, across the namespace forms of the inner call.
71+
* All of these currently return `true`.
72+
*
73+
* `\wp_unslash()` is the fully qualified global unslashing function, so `true` is correct in every version.
74+
*
75+
* `MyNamespace\wp_unslash()`, `\MyNamespace\wp_unslash()` and `namespace\Sub\wp_unslash()` are namespaced
76+
* functions distinct from the global one, so the method should return `false`. They are false positives in
77+
* PHPCS 3.x due to https://github.com/WordPress/WordPress-Coding-Standards/issues/2665. PHPCS 4.x returns
78+
* `false` correctly.
79+
*
80+
* `namespace\wp_unslash()` is the global function in this non-namespaced file, so it should be `true`, but
81+
* the method does not resolve relative names: like the namespaced forms it returns `true` in PHPCS 3.x (via
82+
* the same bug) and `false` in PHPCS 4.x.
83+
*/
84+
/* testFullyQualifiedGlobalUnslashSanitized */
85+
sanitize_text_field( \wp_unslash( $_POST['foo'] ) );
86+
/* testPartiallyQualifiedUnslashSanitized */
87+
sanitize_text_field( MyNamespace\wp_unslash( $_POST['foo'] ) );
88+
/* testFullyQualifiedNamespacedUnslashSanitized */
89+
sanitize_text_field( \MyNamespace\wp_unslash( $_POST['foo'] ) );
90+
/* testNamespaceRelativeUnslashSanitized */
91+
sanitize_text_field( namespace\wp_unslash( $_POST['foo'] ) );
92+
/* testNamespaceRelativeSubUnslashSanitized */
93+
sanitize_text_field( namespace\Sub\wp_unslash( $_POST['foo'] ) );
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
<?php
2+
/**
3+
* WordPress Coding Standard.
4+
*
5+
* @package WPCS\WordPressCodingStandards
6+
* @link https://github.com/WordPress/WordPress-Coding-Standards
7+
* @license https://opensource.org/licenses/MIT MIT
8+
*/
9+
10+
namespace WordPressCS\WordPress\Tests\Helpers\SanitizationHelperTrait;
11+
12+
use PHPCSUtils\TestUtils\UtilityMethodTestCase;
13+
use WordPressCS\WordPress\Helpers\SanitizationHelperTrait;
14+
15+
/**
16+
* Tests for the `SanitizationHelperTrait::is_sanitized()` utility method.
17+
*
18+
* @since 3.4.0
19+
*
20+
* @covers \WordPressCS\WordPress\Helpers\SanitizationHelperTrait::is_sanitized
21+
*/
22+
final class IsSanitizedUnitTest extends UtilityMethodTestCase {
23+
24+
/**
25+
* Test class using the SanitizationHelperTrait for testing purposes.
26+
*
27+
* @var object
28+
*/
29+
private static $testClass;
30+
31+
/**
32+
* Set up the test class.
33+
*
34+
* @beforeClass
35+
*
36+
* @return void
37+
*/
38+
public static function setUpBeforeClass(): void {
39+
parent::setUpBeforeClass();
40+
41+
self::$testClass = new class() {
42+
use SanitizationHelperTrait;
43+
};
44+
}
45+
46+
/**
47+
* Test is_sanitized() returns false if the token does not exist.
48+
*
49+
* @return void
50+
*/
51+
public function testIsSanitizedReturnsFalseIfTokenDoesNotExist() {
52+
$this->assertFalse( self::$testClass->is_sanitized( self::$phpcsFile, -1 ) );
53+
}
54+
55+
/**
56+
* Test is_sanitized().
57+
*
58+
* @dataProvider dataIsSanitized
59+
*
60+
* @param string $testMarker The comment which prefaces the target token in the test file.
61+
* @param bool $expectedResult The expected return value.
62+
* @param int|string $tokenType The token type to search for. Defaults to T_VARIABLE.
63+
*
64+
* @return void
65+
*/
66+
public function testIsSanitized( $testMarker, $expectedResult, $tokenType = \T_VARIABLE ) {
67+
$stackPtr = $this->getTargetToken( $testMarker, $tokenType );
68+
$result = self::$testClass->is_sanitized( self::$phpcsFile, $stackPtr );
69+
70+
$this->assertSame( $expectedResult, $result );
71+
}
72+
73+
/**
74+
* Data provider.
75+
*
76+
* @see testIsSanitized()
77+
*
78+
* @return array<string, array<string, bool|int|string>>
79+
*/
80+
public static function dataIsSanitized() {
81+
return array(
82+
// Cases where false should be returned.
83+
'not_within_function_call' => array(
84+
'testMarker' => '/* testNotWithinFunctionCall */',
85+
'expectedResult' => false,
86+
),
87+
'non_sanitizing_function' => array(
88+
'testMarker' => '/* testNonSanitizingFunction */',
89+
'expectedResult' => false,
90+
),
91+
'inner_function_not_sanitizing' => array(
92+
'testMarker' => '/* testInnerFunctionNotSanitizing */',
93+
'expectedResult' => false,
94+
'tokenType' => \T_STRING,
95+
),
96+
'unslashed_in_non_sanitizing_function' => array(
97+
'testMarker' => '/* testUnslashedInNonSanitizingFunction */',
98+
'expectedResult' => false,
99+
),
100+
'partially_qualified' => array(
101+
'testMarker' => '/* testPartiallyQualified */',
102+
'expectedResult' => false,
103+
),
104+
'fully_qualified_namespaced' => array(
105+
'testMarker' => '/* testFullyQualifiedNamespaced */',
106+
'expectedResult' => false,
107+
),
108+
'namespace_relative' => array(
109+
'testMarker' => '/* testNamespaceRelative */',
110+
'expectedResult' => false,
111+
),
112+
'namespace_relative_sub' => array(
113+
'testMarker' => '/* testNamespaceRelativeSub */',
114+
'expectedResult' => false,
115+
),
116+
'method_call' => array(
117+
'testMarker' => '/* testMethodCall */',
118+
'expectedResult' => false,
119+
),
120+
'static_method_call' => array(
121+
'testMarker' => '/* testStaticMethodCall */',
122+
'expectedResult' => false,
123+
),
124+
'array_walking_non_sanitizing_callback' => array(
125+
'testMarker' => '/* testArrayWalkingNonSanitizingCallback */',
126+
'expectedResult' => false,
127+
),
128+
'array_walking_non_string_callback' => array(
129+
'testMarker' => '/* testArrayWalkingNonStringCallback */',
130+
'expectedResult' => false,
131+
),
132+
'array_walking_missing_callback' => array(
133+
'testMarker' => '/* testArrayWalkingMissingCallback */',
134+
'expectedResult' => false,
135+
),
136+
137+
// Cases where true should be returned.
138+
'in_unset' => array(
139+
'testMarker' => '/* testInUnset */',
140+
'expectedResult' => true,
141+
),
142+
'safe_cast' => array(
143+
'testMarker' => '/* testSafeCast */',
144+
'expectedResult' => true,
145+
),
146+
'sanitizing_function' => array(
147+
'testMarker' => '/* testSanitizingFunction */',
148+
'expectedResult' => true,
149+
),
150+
'sanitizing_and_unslashing_function' => array(
151+
'testMarker' => '/* testSanitizingAndUnslashingFunction */',
152+
'expectedResult' => true,
153+
),
154+
'unslashed_then_sanitized' => array(
155+
'testMarker' => '/* testUnslashedThenSanitized */',
156+
'expectedResult' => true,
157+
),
158+
'array_walking_sanitizing_callback' => array(
159+
'testMarker' => '/* testArrayWalkingSanitizingCallback */',
160+
'expectedResult' => true,
161+
),
162+
'string_token_sanitized' => array(
163+
'testMarker' => '/* testStringTokenSanitized */',
164+
'expectedResult' => true,
165+
'tokenType' => \T_STRING,
166+
),
167+
'fully_qualified_global_unslash_sanitized' => array(
168+
'testMarker' => '/* testFullyQualifiedGlobalUnslashSanitized */',
169+
'expectedResult' => true,
170+
),
171+
'partially_qualified_unslash_sanitized' => array(
172+
'testMarker' => '/* testPartiallyQualifiedUnslashSanitized */',
173+
'expectedResult' => true,
174+
),
175+
'fully_qualified_namespaced_unslash_sanitized' => array(
176+
'testMarker' => '/* testFullyQualifiedNamespacedUnslashSanitized */',
177+
'expectedResult' => true,
178+
),
179+
'namespace_relative_unslash_sanitized' => array(
180+
'testMarker' => '/* testNamespaceRelativeUnslashSanitized */',
181+
'expectedResult' => true,
182+
),
183+
'namespace_relative_sub_unslash_sanitized' => array(
184+
'testMarker' => '/* testNamespaceRelativeSubUnslashSanitized */',
185+
'expectedResult' => true,
186+
),
187+
);
188+
}
189+
190+
/**
191+
* Test that is_sanitized() invokes the unslash callback when the value is used
192+
* without being unslashed, and not when the value has already been unslashed.
193+
*
194+
* @dataProvider dataIsSanitizedUnslashCallback
195+
*
196+
* @param string $testMarker The comment which prefaces the target token in the test file.
197+
* @param bool $expectedResult The expected return value of is_sanitized().
198+
* @param bool $expectedCalled Whether the unslash callback is expected to be called.
199+
*
200+
* @return void
201+
*/
202+
public function testIsSanitizedUnslashCallback( $testMarker, $expectedResult, $expectedCalled ) {
203+
$stackPtr = $this->getTargetToken( $testMarker, \T_VARIABLE );
204+
$callCount = 0;
205+
$callArgs = array();
206+
$callback = static function ( $phpcsFile, $ptr ) use ( &$callCount, &$callArgs ) {
207+
++$callCount;
208+
$callArgs = array( $phpcsFile, $ptr );
209+
};
210+
211+
$result = self::$testClass->is_sanitized( self::$phpcsFile, $stackPtr, $callback );
212+
213+
$this->assertSame( $expectedResult, $result, "Return value mismatch for $testMarker" );
214+
$this->assertSame(
215+
$expectedCalled ? 1 : 0,
216+
$callCount,
217+
"Unexpected number of unslash callback invocations for $testMarker"
218+
);
219+
220+
if ( true === $expectedCalled ) {
221+
$this->assertSame(
222+
array( self::$phpcsFile, $stackPtr ),
223+
$callArgs,
224+
"The unslash callback received unexpected arguments for $testMarker"
225+
);
226+
}
227+
}
228+
229+
/**
230+
* Data provider.
231+
*
232+
* @see testIsSanitizedUnslashCallback()
233+
*
234+
* @return array<string, array<string, bool|string>>
235+
*/
236+
public static function dataIsSanitizedUnslashCallback() {
237+
return array(
238+
'not_within_function_call' => array(
239+
'testMarker' => '/* testNotWithinFunctionCall */',
240+
'expectedResult' => false,
241+
'expectedCalled' => true,
242+
),
243+
'non_sanitizing_function' => array(
244+
'testMarker' => '/* testNonSanitizingFunction */',
245+
'expectedResult' => false,
246+
'expectedCalled' => true,
247+
),
248+
'sanitized_not_unslashed' => array(
249+
'testMarker' => '/* testSanitizingFunction */',
250+
'expectedResult' => true,
251+
'expectedCalled' => true,
252+
),
253+
'unslashed_then_sanitized' => array(
254+
'testMarker' => '/* testUnslashedThenSanitized */',
255+
'expectedResult' => true,
256+
'expectedCalled' => false,
257+
),
258+
'unslashed_in_non_sanitizing_function' => array(
259+
'testMarker' => '/* testUnslashedInNonSanitizingFunction */',
260+
'expectedResult' => false,
261+
'expectedCalled' => false,
262+
),
263+
'sanitizing_and_unslashing_function' => array(
264+
'testMarker' => '/* testSanitizingAndUnslashingFunction */',
265+
'expectedResult' => true,
266+
'expectedCalled' => false,
267+
),
268+
);
269+
}
270+
}

0 commit comments

Comments
 (0)