55namespace NthRoot \PhpSecuritySniffs \Security \Sniffs ;
66
77use PHP_CodeSniffer \Files \File ;
8+ use PHP_CodeSniffer \Util \Tokens ;
89
910use function array_slice ;
1011use function in_array ;
12+ use function range ;
1113
14+ use const T_EQUAL ;
1215use const T_OPEN_PARENTHESIS ;
1316use const T_VARIABLE ;
1417
@@ -31,45 +34,83 @@ public function containsVariableInput(File $file, int $start): bool
3134
3235 public function containsUserInput (File $ file , int $ start ): bool
3336 {
34- $ tokens = $ this -> getArgumentTokens ( $ file , $ start );
37+ $ tokens = $ file -> getTokens ( );
3538
36- foreach ($ tokens as $ token ) {
37- if ($ this ->isUserInput ($ token )) {
39+ if ($ tokens [$ start ]['code ' ] === T_OPEN_PARENTHESIS ) {
40+ $ closingParenthesis = $ tokens [$ start ]['parenthesis_closer ' ];
41+
42+ $ tokenPositions = range ($ start + 1 , $ closingParenthesis - 1 );
43+ } else {
44+ $ tokenPositions = range ($ start , $ file ->findEndOfStatement ($ start ) - 1 );
45+ }
46+
47+ foreach ($ tokenPositions as $ token ) {
48+ if ($ this ->isTainted ($ file , $ token )) {
3849 return true ;
3950 }
4051 }
4152
4253 return false ;
4354 }
4455
45- private function getArgumentTokens (File $ file , int $ start ): array
56+ /**
57+ * Returns true if the token at the given position contains user input.
58+ */
59+ public function isTainted (File $ file , int $ tokenPosition ): bool
4660 {
4761 $ tokens = $ file ->getTokens ();
62+ $ token = $ tokens [$ tokenPosition ];
4863
49- if ($ tokens [$ start ]['code ' ] === T_OPEN_PARENTHESIS ) {
50- $ closingParenthesis = $ tokens [$ start ]['parenthesis_closer ' ];
51-
52- return array_slice ($ tokens , $ start + 1 , $ closingParenthesis - $ start );
64+ if ($ token ['code ' ] === T_VARIABLE && in_array ($ token ['content ' ], self ::SUPERGLOBALS , true )) {
65+ return true ;
5366 }
5467
55- $ endOfStatement = $ file ->findEndOfStatement ($ start );
68+ if ($ token ['code ' ] === T_VARIABLE ) {
69+ $ variableName = $ token ['content ' ];
5670
57- return array_slice ($ tokens , $ start , $ endOfStatement - $ start );
71+ $ definition = $ this ->getVariableDefinition ($ file , $ tokenPosition , $ variableName );
72+
73+ if (!$ definition ) {
74+ // Cannot find the definition of the variable
75+ return false ;
76+ }
77+
78+ $ value = $ file ->findNext (T_VARIABLE , $ definition + 1 );
79+
80+ return $ this ->isTainted ($ file , $ value );
81+ }
82+
83+ return false ;
5884 }
5985
60- /**
61- * @param array{code: int, content: string} $token
62- */
63- private function isUserInput (array $ token ): bool
86+ private function getVariableDefinition (File $ file , int $ tokenPosition , string $ name ): int |false
6487 {
65- if ($ token ['code ' ] !== T_VARIABLE ) {
66- return false ;
67- }
88+ $ tokens = $ file ->getTokens ();
6889
69- if (in_array ($ token ['content ' ], self ::SUPERGLOBALS , true )) {
70- return true ;
90+ while ($ tokenPosition = $ file ->findPrevious (T_VARIABLE , $ tokenPosition - 1 , null , false , $ name )) {
91+ $ nextToken = $ file ->findNext (Tokens::$ emptyTokens , $ tokenPosition + 1 , null , true , null , true );
92+ $ nextToken = $ tokens [$ nextToken ];
93+
94+ if ($ nextToken ['code ' ] === T_EQUAL ) {
95+ return $ tokenPosition ;
96+ }
7197 }
7298
7399 return false ;
74100 }
101+
102+ private function getArgumentTokens (File $ file , int $ start ): array
103+ {
104+ $ tokens = $ file ->getTokens ();
105+
106+ if ($ tokens [$ start ]['code ' ] === T_OPEN_PARENTHESIS ) {
107+ $ closingParenthesis = $ tokens [$ start ]['parenthesis_closer ' ];
108+
109+ return array_slice ($ tokens , $ start + 1 , $ closingParenthesis - $ start );
110+ }
111+
112+ $ endOfStatement = $ file ->findEndOfStatement ($ start );
113+
114+ return array_slice ($ tokens , $ start , $ endOfStatement - $ start );
115+ }
75116}
0 commit comments