Skip to content

Commit 8928be7

Browse files
committed
Add custom ExitAfterRedirect sniff and ruleset configuration
1 parent 237cf3d commit 8928be7

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
/**
3+
* StellarWP Coding Standards.
4+
*
5+
* @package StellarWP\Sniffs\Security
6+
* @since TBD
7+
*/
8+
9+
namespace StellarWP\Sniffs\Security;
10+
11+
use PHP_CodeSniffer\Files\File;
12+
use PHP_CodeSniffer\Sniffs\Sniff;
13+
use PHP_CodeSniffer\Util\Tokens;
14+
15+
/**
16+
* Checks that functions which require exit or die after are not left without them.
17+
*
18+
* @since TBD
19+
*/
20+
class ExitAfterRedirectSniff implements Sniff {
21+
/**
22+
* Functions that need to be followed by an exit.
23+
*
24+
* @since TBD
25+
*
26+
* @var array<string>
27+
*/
28+
public $functions = [
29+
'wp_redirect',
30+
'wp_safe_redirect',
31+
'wp_doing_ajax',
32+
];
33+
34+
/**
35+
* Returns an array of tokens this test wants to listen for.
36+
*
37+
* @since TBD
38+
*
39+
* @return array<int|string>
40+
*/
41+
public function register() {
42+
return [ T_STRING ];
43+
}
44+
45+
/**
46+
* Processes this test, when one of its tokens is encountered.
47+
*
48+
* @since TBD
49+
*
50+
* @param File $phpcsFile The file being scanned.
51+
* @param int $stackPtr The position of the current token in the stack.
52+
*
53+
* @return void
54+
*/
55+
public function process( File $phpcsFile, $stackPtr ) {
56+
$tokens = $phpcsFile->getTokens();
57+
58+
// Find the function call.
59+
$name = $tokens[ $stackPtr ]['content'];
60+
$function_name = strtolower( $name );
61+
62+
if ( ! in_array( $function_name, $this->functions, true ) ) {
63+
return;
64+
}
65+
66+
// Find the opening and closing parenthesis of the function call.
67+
$open_paren = $phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true );
68+
if ( ! isset( $tokens[ $open_paren ] ) || $tokens[ $open_paren ]['code'] !== T_OPEN_PARENTHESIS ) {
69+
return;
70+
}
71+
72+
// Check if the function call is followed by a semicolon (end of statement).
73+
$close_paren = $tokens[ $open_paren ]['parenthesis_closer'];
74+
$next_token = $phpcsFile->findNext( Tokens::$emptyTokens, $close_paren + 1, null, true );
75+
76+
// If the next non-empty token is a semicolon, we need to check if an exit follows.
77+
if ( isset( $tokens[ $next_token ] ) && $tokens[ $next_token ]['code'] === T_SEMICOLON ) {
78+
// Check if exit follows in the current scope.
79+
$exit_found = false;
80+
$start = $next_token + 1;
81+
$end = $phpcsFile->numTokens;
82+
83+
// If we're in a function or method, only search until the end of the function.
84+
if ( isset( $tokens[ $stackPtr ]['conditions'] ) ) {
85+
foreach ( $tokens[ $stackPtr ]['conditions'] as $scope => $type ) {
86+
if ( in_array( $type, [ T_FUNCTION, T_CLOSURE, T_ANON_CLASS ], true ) ) {
87+
if ( isset( $tokens[ $scope ]['scope_closer'] ) ) {
88+
$end = $tokens[ $scope ]['scope_closer'];
89+
}
90+
break;
91+
}
92+
}
93+
}
94+
95+
// Search for exit or die statements.
96+
for ( $i = $start; $i < $end; $i++ ) {
97+
// Check for exit or die calls
98+
if ( isset( $tokens[ $i ] ) ) {
99+
$token_code = $tokens[ $i ]['code'];
100+
$token_content = isset( $tokens[ $i ]['content'] ) ? strtolower( $tokens[ $i ]['content'] ) : '';
101+
102+
// Check for exit, die, or return statements
103+
if ( $token_code === T_EXIT ||
104+
( $token_code === T_STRING && in_array( $token_content, [ 'die', 'tribe_exit', 'tec_exit' ], true ) ) ||
105+
$token_code === T_RETURN ) {
106+
$exit_found = true;
107+
break;
108+
}
109+
}
110+
}
111+
112+
if ( ! $exit_found ) {
113+
$phpcsFile->addError(
114+
'%s() should be followed by a call to exit; for proper redirection.',
115+
$stackPtr,
116+
'NoExit',
117+
[ $name ]
118+
);
119+
}
120+
}
121+
}
122+
}

StellarWP/ruleset.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
<exclude name="WordPress.Security.EscapeOutput.ExceptionNotEscaped"/>
1616
</rule>
1717

18+
<!-- Override the ExitAfterRedirect rule with our custom implementation -->
19+
<rule ref="WordPressVIPMinimum.Security.ExitAfterRedirect.NoExit">
20+
<severity>0</severity>
21+
</rule>
22+
<rule ref="StellarWP.Security.ExitAfterRedirect"/>
23+
1824
<!-- Warns about missing short descriptions in docblocks. -->
1925
<rule ref="Generic.Commenting.DocComment">
2026
<exclude name="Generic.Commenting.DocComment.MissingShort" />

0 commit comments

Comments
 (0)