GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( 699b70...879176 )
by Chris
13:23
created

WordPress_Sniffs_XSS_EscapeOutputSniff   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 333
Duplicated Lines 1.2 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 4
loc 333
rs 8.295
c 0
b 0
f 0
wmc 42
lcom 1
cbo 3

2 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 9 1
F process() 4 233 41

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WordPress_Sniffs_XSS_EscapeOutputSniff often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WordPress_Sniffs_XSS_EscapeOutputSniff, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * WordPress Coding Standard.
4
 *
5
 * @package WPCS\WordPressCodingStandards
6
 * @link    https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
7
 * @license https://opensource.org/licenses/MIT MIT
8
 */
9
10
/**
11
 * Verifies that all outputted strings are escaped.
12
 *
13
 * @link    http://codex.wordpress.org/Data_Validation Data Validation on WordPress Codex
14
 *
15
 * @package WPCS\WordPressCodingStandards
16
 *
17
 * @since   2013-06-11
18
 * @since   0.4.0 This class now extends WordPress_Sniff.
19
 * @since   0.5.0 The various function list properties which used to be contained in this class
20
 *                have been moved to the WordPress_Sniff parent class.
21
 */
22
class WordPress_Sniffs_XSS_EscapeOutputSniff extends WordPress_Sniff {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
23
24
	/**
25
	 * Custom list of functions which escape values for output.
26
	 *
27
	 * @since 0.5.0
28
	 *
29
	 * @var string[]
30
	 */
31
	public $customEscapingFunctions = array();
32
33
	/**
34
	 * Custom list of functions whose return values are pre-escaped for output.
35
	 *
36
	 * @since 0.3.0
37
	 *
38
	 * @var string[]
39
	 */
40
	public $customAutoEscapedFunctions = array();
41
42
	/**
43
	 * Custom list of functions which escape values for output.
44
	 *
45
	 * @since      0.3.0
46
	 * @deprecated 0.5.0 Use $customEscapingFunctions instead.
47
	 * @see        WordPress_Sniffs_XSS_EscapeOutputSniff::$customEscapingFunctions
48
	 *
49
	 * @var string[]
50
	 */
51
	public $customSanitizingFunctions = array();
52
53
	/**
54
	 * Custom list of functions which print output incorporating the passed values.
55
	 *
56
	 * @since 0.4.0
57
	 *
58
	 * @var string[]
59
	 */
60
	public $customPrintingFunctions = array();
61
62
	/**
63
	 * Printing functions that incorporate unsafe values.
64
	 *
65
	 * @since 0.4.0
66
	 *
67
	 * @var array
68
	 */
69
	public static $unsafePrintingFunctions = array(
70
		'_e'  => 'esc_html_e() or esc_attr_e()',
71
		'_ex' => 'esc_html_ex() or esc_attr_ex()',
72
	);
73
74
	/**
75
	 * Whether the custom functions were added to the default lists yet.
76
	 *
77
	 * @var bool
78
	 */
79
	public static $addedCustomFunctions = false;
80
81
	/**
82
	 * List of names of the tokens representing PHP magic constants.
83
	 *
84
	 * @var array
85
	 */
86
	private $magic_constant_tokens = array(
87
		'T_CLASS_C'  => true, // __CLASS__
88
		'T_FILE'     => true, // __FILE__
89
		'T_FUNC_C'   => true, // __FUNCTION__
90
		'T_LINE'     => true, // __LINE__
91
		'T_METHOD_C' => true, // __METHOD__
92
		'T_NS_C'     => true, // __NAMESPACE__
93
		'T_TRAIT_C'  => true, // __TRAIT__
94
	);
95
96
	/**
97
	 * Returns an array of tokens this test wants to listen for.
98
	 *
99
	 * @return array
100
	 */
101
	public function register() {
102
		return array(
103
			T_ECHO,
104
			T_PRINT,
105
			T_EXIT,
106
			T_STRING,
107
		);
108
109
	}
110
111
	/**
112
	 * Processes this test, when one of its tokens is encountered.
113
	 *
114
	 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
115
	 * @param int                  $stackPtr  The position of the current token
116
	 *                                        in the stack passed in $tokens.
117
	 *
118
	 * @return int|void
119
	 */
120
	public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) {
121
		// Merge any custom functions with the defaults, if we haven't already.
122
		if ( ! self::$addedCustomFunctions ) {
123
			self::$escapingFunctions    = array_merge( self::$escapingFunctions, array_flip( $this->customEscapingFunctions ) );
124
			self::$autoEscapedFunctions = array_merge( self::$autoEscapedFunctions, array_flip( $this->customAutoEscapedFunctions ) );
125
			self::$printingFunctions    = array_merge( self::$printingFunctions, array_flip( $this->customPrintingFunctions ) );
126
127
			if ( ! empty( $this->customSanitizingFunctions ) ) {
0 ignored issues
show
Deprecated Code introduced by
The property WordPress_Sniffs_XSS_Esc...stomSanitizingFunctions has been deprecated with message: 0.5.0 Use $customEscapingFunctions instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
128
				self::$escapingFunctions = array_merge( self::$escapingFunctions, array_flip( $this->customSanitizingFunctions ) );
0 ignored issues
show
Deprecated Code introduced by
The property WordPress_Sniffs_XSS_Esc...stomSanitizingFunctions has been deprecated with message: 0.5.0 Use $customEscapingFunctions instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
129
				$phpcsFile->addWarning( 'The customSanitizingFunctions property is deprecated in favor of customEscapingFunctions.', 0, 'DeprecatedCustomSanitizingFunctions' );
130
			}
131
132
			self::$addedCustomFunctions = true;
133
		}
134
135
		$this->init( $phpcsFile );
136
137
		$function = $this->tokens[ $stackPtr ]['content'];
138
139
		// Find the opening parenthesis (if present; T_ECHO might not have it).
140
		$open_paren = $phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );
141
142
		// If function, not T_ECHO nor T_PRINT.
143
		if ( T_STRING === $this->tokens[ $stackPtr ]['code'] ) {
144
			// Skip if it is a function but is not of the printing functions.
145
			if ( ! isset( self::$printingFunctions[ $this->tokens[ $stackPtr ]['content'] ] ) ) {
146
				return;
147
			}
148
149
			if ( isset( $this->tokens[ $open_paren ]['parenthesis_closer'] ) ) {
150
				$end_of_statement = $this->tokens[ $open_paren ]['parenthesis_closer'];
151
			}
152
153
			// These functions only need to have the first argument escaped.
154
			if ( in_array( $function, array( 'trigger_error', 'user_error' ), true ) ) {
155
				$end_of_statement = $phpcsFile->findEndOfStatement( $open_paren + 1 );
156
			}
157
		}
158
159
		// Checking for the ignore comment, ex: //xss ok.
160
		if ( $this->has_whitelist_comment( 'xss', $stackPtr ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->has_whitelist_comment('xss', $stackPtr) of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
161
			return;
162
		}
163
164
		if ( isset( $end_of_statement, self::$unsafePrintingFunctions[ $function ] ) ) {
165
			$error = $phpcsFile->addError( "Expected next thing to be an escaping function (like %s), not '%s'", $stackPtr, 'UnsafePrintingFunction', array( self::$unsafePrintingFunctions[ $function ], $function ) );
166
167
			// If the error was reported, don't bother checking the function's arguments.
168
			if ( $error ) {
169
				return $end_of_statement;
170
			}
171
		}
172
173
		$ternary = false;
174
175
		// This is already determined if this is a function and not T_ECHO.
176
		if ( ! isset( $end_of_statement ) ) {
177
178
			$end_of_statement = $phpcsFile->findNext( array( T_SEMICOLON, T_CLOSE_TAG ), $stackPtr );
179
			$last_token       = $phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $end_of_statement - 1 ), null, true );
180
181
			// Check for the ternary operator. We only need to do this here if this
182
			// echo is lacking parenthesis. Otherwise it will be handled below.
183
			if ( T_OPEN_PARENTHESIS !== $this->tokens[ $open_paren ]['code'] || T_CLOSE_PARENTHESIS !== $this->tokens[ $last_token ]['code'] ) {
184
185
				$ternary = $phpcsFile->findNext( T_INLINE_THEN, $stackPtr, $end_of_statement );
186
187
				// If there is a ternary skip over the part before the ?. However, if
188
				// the ternary is within parentheses, it will be handled in the loop.
189
				if ( $ternary && empty( $this->tokens[ $ternary ]['nested_parenthesis'] ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ternary of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
190
					$stackPtr = $ternary;
191
				}
192
			}
193
		}
194
195
		// Ignore the function itself.
196
		$stackPtr++;
197
198
		$in_cast = false;
199
200
		// Looping through echo'd components.
201
		$watch = true;
202
		for ( $i = $stackPtr; $i < $end_of_statement; $i++ ) {
203
204
			// Ignore whitespaces and comments.
205
			if ( in_array( $this->tokens[ $i ]['code'], PHP_CodeSniffer_Tokens::$emptyTokens, true ) ) {
206
				continue;
207
			}
208
209
			if ( T_OPEN_PARENTHESIS === $this->tokens[ $i ]['code'] ) {
210
211
				if ( $in_cast ) {
212
213
					// Skip to the end of a function call if it has been casted to a safe value.
214
					$i       = $this->tokens[ $i ]['parenthesis_closer'];
215
					$in_cast = false;
216
217
				} else {
218
219
					// Skip over the condition part of a ternary (i.e., to after the ?).
220
					$ternary = $phpcsFile->findNext( T_INLINE_THEN, $i, $this->tokens[ $i ]['parenthesis_closer'] );
221
222
					if ( $ternary ) {
223
224
						$next_paren = $phpcsFile->findNext( T_OPEN_PARENTHESIS, ( $i + 1 ), $this->tokens[ $i ]['parenthesis_closer'] );
225
226
						// We only do it if the ternary isn't within a subset of parentheses.
227
						if ( ! $next_paren || $ternary > $this->tokens[ $next_paren ]['parenthesis_closer'] ) {
228
							$i = $ternary;
229
						}
230
					}
231
				}
232
233
				continue;
234
			}
235
236
			// Handle arrays for those functions that accept them.
237
			if ( T_ARRAY === $this->tokens[ $i ]['code'] ) {
238
				$i++; // Skip the opening parenthesis.
239
				continue;
240
			}
241
242
			if ( in_array( $this->tokens[ $i ]['code'], array( T_DOUBLE_ARROW, T_CLOSE_PARENTHESIS ), true ) ) {
243
				continue;
244
			}
245
246
			// Handle magic constants for debug functions.
247
			if ( isset( $this->magic_constant_tokens[ $this->tokens[ $i ]['type'] ] ) ) {
248
				continue;
249
			}
250
251
			// Wake up on concatenation characters, another part to check.
252
			if ( in_array( $this->tokens[ $i ]['code'], array( T_STRING_CONCAT ), true ) ) {
253
				$watch = true;
254
				continue;
255
			}
256
257
			// Wake up after a ternary else (:).
258
			if ( $ternary && in_array( $this->tokens[ $i ]['code'], array( T_INLINE_ELSE ), true ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ternary of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
259
				$watch = true;
260
				continue;
261
			}
262
263
			// Wake up for commas.
264
			if ( T_COMMA === $this->tokens[ $i ]['code'] ) {
265
				$in_cast = false;
266
				$watch   = true;
267
				continue;
268
			}
269
270
			if ( false === $watch ) {
271
				continue;
272
			}
273
274
			// Allow T_CONSTANT_ENCAPSED_STRING eg: echo 'Some String';
275
			// Also T_LNUMBER, e.g.: echo 45; exit -1; and booleans.
276
			if ( in_array( $this->tokens[ $i ]['code'], array( T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_MINUS, T_TRUE, T_FALSE, T_NULL ), true ) ) {
277
				continue;
278
			}
279
280
			$watch = false;
281
282
			// Allow int/double/bool casted variables.
283
			if ( in_array( $this->tokens[ $i ]['code'], array( T_INT_CAST, T_DOUBLE_CAST, T_BOOL_CAST ), true ) ) {
284
				$in_cast = true;
285
				continue;
286
			}
287
288
			// Now check that next token is a function call.
289
			if ( T_STRING === $this->tokens[ $i ]['code'] ) {
290
291
				$ptr                    = $i;
292
				$functionName           = $this->tokens[ $i ]['content'];
293
				$function_opener        = $this->phpcsFile->findNext( array( T_OPEN_PARENTHESIS ), ( $i + 1 ), null, null, null, true );
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
294
				$is_formatting_function = isset( self::$formattingFunctions[ $functionName ] );
295
296
				if ( $function_opener ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $function_opener of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
297
298
					if ( 'array_map' === $functionName ) {
299
300
						// Get the first parameter (name of function being used on the array).
301
						$mapped_function = $this->phpcsFile->findNext(
302
							PHP_CodeSniffer_Tokens::$emptyTokens,
303
							( $function_opener + 1 ),
304
							$this->tokens[ $function_opener ]['parenthesis_closer'],
305
							true
306
						);
307
308
						// If we're able to resolve the function name, do so.
309 View Code Duplication
						if ( $mapped_function && T_CONSTANT_ENCAPSED_STRING === $this->tokens[ $mapped_function ]['code'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
							$functionName = trim( $this->tokens[ $mapped_function ]['content'], '\'' );
311
							$ptr = $mapped_function;
312
						}
313
					}
314
315
					// Skip pointer to after the function.
316
					// If this is a formatting function we just skip over the opening
317
					// parenthesis. Otherwise we skip all the way to the closing.
318
					if ( $is_formatting_function ) {
319
						$i     = ( $function_opener + 1 );
320
						$watch = true;
321
					} else {
322
						$i = $this->tokens[ $function_opener ]['parenthesis_closer'];
323
					}
324
				}
325
326
				// If this is a safe function, we don't flag it.
327
				if (
328
					$is_formatting_function
329
					|| isset( self::$autoEscapedFunctions[ $functionName ] )
330
					|| isset( self::$escapingFunctions[ $functionName ] )
331
				) {
332
					continue;
333
				}
334
335
				$content = $functionName;
336
337
			} else {
338
				$content = $this->tokens[ $i ]['content'];
339
				$ptr     = $i;
340
			}
341
342
			$this->phpcsFile->addError(
343
				"Expected next thing to be an escaping function (see Codex for 'Data Validation'), not '%s'",
344
				$ptr,
345
				'OutputNotEscaped',
346
				$content
347
			);
348
		}
349
350
		return $end_of_statement;
351
352
	} // end process()
353
354
} // End class.
355