|
9 | 9 |
|
10 | 10 | namespace WordPressVIPMinimum\Sniffs\Variables;
|
11 | 11 |
|
| 12 | +use PHP_CodeSniffer\Util\Tokens; |
| 13 | +use PHPCSUtils\Utils\TextStrings; |
12 | 14 | use WordPressVIPMinimum\Sniffs\Sniff;
|
13 | 15 |
|
14 | 16 | /**
|
|
17 | 19 | class ServerVariablesSniff extends Sniff {
|
18 | 20 |
|
19 | 21 | /**
|
20 |
| - * List of restricted constant names. |
| 22 | + * List of restricted indices. |
21 | 23 | *
|
22 | 24 | * @var array<string, array<string, bool>>
|
23 | 25 | */
|
@@ -53,21 +55,94 @@ public function register() {
|
53 | 55 | */
|
54 | 56 | public function process_token( $stackPtr ) {
|
55 | 57 |
|
56 |
| - if ( $this->tokens[ $stackPtr ]['content'] !== '$_SERVER' ) { |
57 |
| - // Not the variable we are looking for. |
| 58 | + if ( $this->tokens[ $stackPtr ]['content'] !== '$_SERVER' |
| 59 | + && $this->tokens[ $stackPtr ]['content'] !== '$GLOBALS' |
| 60 | + ) { |
| 61 | + // Not a variable we are looking for. |
58 | 62 | return;
|
59 | 63 | }
|
60 | 64 |
|
61 |
| - $variableNamePtr = $this->phpcsFile->findNext( [ T_CONSTANT_ENCAPSED_STRING ], $stackPtr + 1, null, false, null, true ); |
62 |
| - $variableName = str_replace( [ "'", '"' ], '', $this->tokens[ $variableNamePtr ]['content'] ); |
| 65 | + $searchStart = $stackPtr; |
| 66 | + if ( $this->tokens[ $stackPtr ]['content'] === '$GLOBALS' ) { |
| 67 | + $globalsIndexPtr = $this->get_array_access_key( $stackPtr ); |
| 68 | + if ( $globalsIndexPtr === false ) { |
| 69 | + // Couldn't find an array index token usable for the purposes of this sniff. Bow out. |
| 70 | + return; |
| 71 | + } |
63 | 72 |
|
64 |
| - if ( isset( $this->restrictedVariables['authVariables'][ $variableName ] ) ) { |
| 73 | + $globalsIndexName = TextStrings::stripQuotes( $this->tokens[ $globalsIndexPtr ]['content'] ); |
| 74 | + if ( $globalsIndexName !== '_SERVER' ) { |
| 75 | + // Not access to `$GLOBALS['_SERVER']`. |
| 76 | + return; |
| 77 | + } |
| 78 | + |
| 79 | + // Set the start point for the next array access key search to the close bracket of this array index. |
| 80 | + // No need for defensive coding as we already know there will be a valid close bracket next. |
| 81 | + $searchStart = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $globalsIndexPtr + 1 ), null, true ); |
| 82 | + } |
| 83 | + |
| 84 | + $prevNonEmpty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); |
| 85 | + if ( $this->tokens[ $prevNonEmpty ]['code'] === T_DOUBLE_COLON ) { |
| 86 | + // Access to OO property mirroring the name of the superglobal. Not our concern. |
| 87 | + return; |
| 88 | + } |
| 89 | + |
| 90 | + $indexPtr = $this->get_array_access_key( $searchStart ); |
| 91 | + if ( $indexPtr === false ) { |
| 92 | + // Couldn't find an array index token usable for the purposes of this sniff. Bow out as undetermined. |
| 93 | + return; |
| 94 | + } |
| 95 | + |
| 96 | + $indexName = TextStrings::stripQuotes( $this->tokens[ $indexPtr ]['content'] ); |
| 97 | + |
| 98 | + if ( isset( $this->restrictedVariables['authVariables'][ $indexName ] ) ) { |
65 | 99 | $message = 'Basic authentication should not be handled via PHP code.';
|
66 | 100 | $this->phpcsFile->addError( $message, $stackPtr, 'BasicAuthentication' );
|
67 |
| - } elseif ( isset( $this->restrictedVariables['userControlledVariables'][ $variableName ] ) ) { |
| 101 | + } elseif ( isset( $this->restrictedVariables['userControlledVariables'][ $indexName ] ) ) { |
68 | 102 | $message = 'Header "%s" is user-controlled and should be properly validated before use.';
|
69 |
| - $data = [ $variableName ]; |
| 103 | + $data = [ $indexName ]; |
70 | 104 | $this->phpcsFile->addError( $message, $stackPtr, 'UserControlledHeaders', $data );
|
71 | 105 | }
|
72 | 106 | }
|
| 107 | + |
| 108 | + /** |
| 109 | + * Get the array access key. |
| 110 | + * |
| 111 | + * Find the array access key and check if it is: |
| 112 | + * - comprised of a single functional token. |
| 113 | + * - that token is a T_CONSTANT_ENCAPSED_STRING. |
| 114 | + * |
| 115 | + * @param int $stackPtr The position of either a variable or the close bracket of a previous array access. |
| 116 | + * |
| 117 | + * @return int|false Stack pointer to the index token; or FALSE for |
| 118 | + * live coding, non-indexed array assignment, or non plain text array keys. |
| 119 | + */ |
| 120 | + private function get_array_access_key( $stackPtr ) { |
| 121 | + $openBracket = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); |
| 122 | + if ( $openBracket === false |
| 123 | + || $this->tokens[ $openBracket ]['code'] !== T_OPEN_SQUARE_BRACKET |
| 124 | + || isset( $this->tokens[ $openBracket ]['bracket_closer'] ) === false |
| 125 | + ) { |
| 126 | + // If it isn't an open bracket, this isn't array access. And without closer, it is a parse error/live coding. |
| 127 | + return false; |
| 128 | + } |
| 129 | + |
| 130 | + $closeBracket = $this->tokens[ $openBracket ]['bracket_closer']; |
| 131 | + |
| 132 | + $indexPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $openBracket + 1 ), $closeBracket, true ); |
| 133 | + if ( $indexPtr === false |
| 134 | + || $this->tokens[ $indexPtr ]['code'] !== T_CONSTANT_ENCAPSED_STRING |
| 135 | + ) { |
| 136 | + // No array access (like for array assignment without key) or key is not plain text. |
| 137 | + return false; |
| 138 | + } |
| 139 | + |
| 140 | + $hasOtherTokens = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $indexPtr + 1 ), $closeBracket, true ); |
| 141 | + if ( $hasOtherTokens !== false ) { |
| 142 | + // The array index is comprised of multiple tokens. Bow out as undetermined. |
| 143 | + return false; |
| 144 | + } |
| 145 | + |
| 146 | + return $indexPtr; |
| 147 | + } |
73 | 148 | }
|
0 commit comments