@@ -446,6 +446,218 @@ protected function is_sanitized( $stackPtr ) {
446446 // Check if this is a sanitizing function.
447447 return in_array ( $ functionName , WordPress_Sniffs_XSS_EscapeOutputSniff::$ sanitizingFunctions );
448448 }
449+
450+ /**
451+ * Get the index key of an array variable.
452+ *
453+ * E.g., "bar" in $foo['bar'].
454+ *
455+ * @since 0.5.0
456+ *
457+ * @param int $stackPtr The index of the token in the stack.
458+ *
459+ * @return string|false The array index key whose value is being accessed.
460+ */
461+ protected function get_array_access_key ( $ stackPtr ) {
462+
463+ // Find the next non-empty token.
464+ $ open_bracket = $ this ->phpcsFile ->findNext (
465+ PHP_CodeSniffer_Tokens::$ emptyTokens ,
466+ $ stackPtr + 1 ,
467+ null ,
468+ true
469+ );
470+
471+ // If it isn't a bracket, this isn't an array-access.
472+ if ( T_OPEN_SQUARE_BRACKET !== $ this ->tokens [ $ open_bracket ]['code ' ] ) {
473+ return false ;
474+ }
475+
476+ $ key = $ this ->phpcsFile ->getTokensAsString (
477+ $ open_bracket + 1
478+ , $ this ->tokens [ $ open_bracket ]['bracket_closer ' ] - $ open_bracket - 1
479+ );
480+
481+ return trim ( $ key );
482+ }
483+
484+ /**
485+ * Check if the existence of a variable is validated with isset() or empty().
486+ *
487+ * When $in_condition_only is false, (which is the default), this is considered
488+ * valid:
489+ *
490+ * ```php
491+ * if ( isset( $var ) ) {
492+ * // Do stuff, like maybe return or exit (but could be anything)
493+ * }
494+ *
495+ * foo( $var );
496+ * ```
497+ *
498+ * When it is true, that would be invalid, the use of the variable must be within
499+ * the scope of the validating condition, like this:
500+ *
501+ * ```php
502+ * if ( isset( $var ) ) {
503+ * foo( $var );
504+ * }
505+ * ```
506+ *
507+ * @since 0.5.0
508+ *
509+ * @param int $stackPtr The index of this token in the stack.
510+ * @param string $array_key An array key to check for ("bar" in $foo['bar']).
511+ * @param bool $in_condition_only Whether to require that this use of the
512+ * variable occur within the scope of the
513+ * validating condition, or just in the same
514+ * scope as it (default).
515+ *
516+ * @return bool Whether the var is validated.
517+ */
518+ protected function is_validated ( $ stackPtr , $ array_key = null , $ in_condition_only = false ) {
519+
520+ if ( $ in_condition_only ) {
521+
522+ // This is a stricter check, requiring the variable to be used only
523+ // within the validation condition.
524+
525+ // If there are no conditions, there's no validation.
526+ if ( empty ( $ this ->tokens [ $ stackPtr ]['conditions ' ] ) ) {
527+ return false ;
528+ }
529+
530+ $ conditions = $ this ->tokens [ $ stackPtr ]['conditions ' ];
531+ end ( $ conditions ); // Get closest condition
532+ $ conditionPtr = key ( $ conditions );
533+ $ condition = $ this ->tokens [ $ conditionPtr ];
534+
535+ if ( ! isset ( $ condition ['parenthesis_opener ' ] ) ) {
536+
537+ $ this ->phpcsFile ->addError (
538+ 'Possible parse error, condition missing open parenthesis. ' ,
539+ $ conditionPtr ,
540+ 'IsValidatedMissingConditionOpener '
541+ );
542+
543+ return false ;
544+ }
545+
546+ $ scope_start = $ condition ['parenthesis_opener ' ];
547+ $ scope_end = $ condition ['parenthesis_closer ' ];
548+
549+ } else {
550+
551+ // We are are more loose, requiring only that the variable be validated
552+ // in the same function/file scope as it is used.
553+
554+ // Check if we are in a function.
555+ $ function = $ this ->phpcsFile ->findPrevious ( T_FUNCTION , $ stackPtr );
556+
557+ // If so, we check only within the function, otherwise the whole file.
558+ if ( false !== $ function && $ stackPtr < $ this ->tokens [ $ function ]['scope_closer ' ] ) {
559+ $ scope_start = $ this ->tokens [ $ function ]['scope_opener ' ];
560+ } else {
561+ $ scope_start = 0 ;
562+ }
563+
564+ $ scope_end = $ stackPtr ;
565+ }
566+
567+ for ( $ i = $ scope_start + 1 ; $ i < $ scope_end ; $ i ++ ) {
568+
569+ if ( ! in_array ( $ this ->tokens [ $ i ]['code ' ], array ( T_ISSET , T_EMPTY , T_UNSET ) ) ) {
570+ continue ;
571+ }
572+
573+ $ issetOpener = $ this ->phpcsFile ->findNext ( T_OPEN_PARENTHESIS , $ i );
574+ $ issetCloser = $ this ->tokens [ $ issetOpener ]['parenthesis_closer ' ];
575+
576+ // Look for this variable. We purposely stomp $i from the parent loop.
577+ for ( $ i = $ issetOpener + 1 ; $ i < $ issetCloser ; $ i ++ ) {
578+
579+ if ( T_VARIABLE !== $ this ->tokens [ $ i ]['code ' ] ) {
580+ continue ;
581+ }
582+
583+ // If we're checking for a specific array key (ex: 'hello' in
584+ // $_POST['hello']), that mush match too.
585+ if ( $ array_key && $ this ->get_array_access_key ( $ i ) !== $ array_key ) {
586+ continue ;
587+ }
588+
589+ return true ;
590+ }
591+ }
592+
593+ return false ;
594+ }
595+
596+ /**
597+ * Check whether a variable is being compared to another value.
598+ *
599+ * E.g., $var === 'foo', 1 <= $var, etc.
600+ *
601+ * Also recognizes `switch ( $var )`.
602+ *
603+ * @since 0.5.0
604+ *
605+ * @param int $stackPtr The index of this token in the stack.
606+ *
607+ * @return bool Whether this is a comparison.
608+ */
609+ protected function is_comparison ( $ stackPtr ) {
610+
611+ // We first check if this is a switch statement (switch ( $var )).
612+ if ( isset ( $ this ->tokens [ $ stackPtr ]['nested_parenthesis ' ] ) ) {
613+ $ close_parenthesis = end ( $ this ->tokens [ $ stackPtr ]['nested_parenthesis ' ] );
614+
615+ if (
616+ isset ( $ this ->tokens [ $ close_parenthesis ]['parenthesis_owner ' ] )
617+ && T_SWITCH === $ this ->tokens [ $ this ->tokens [ $ close_parenthesis ]['parenthesis_owner ' ] ]['code ' ]
618+ ) {
619+ return true ;
620+ }
621+ }
622+
623+ // Find the previous non-empty token. We check before the var first because
624+ // yoda conditions are usually expected.
625+ $ previous_token = $ this ->phpcsFile ->findPrevious (
626+ PHP_CodeSniffer_Tokens::$ emptyTokens ,
627+ $ stackPtr - 1 ,
628+ null ,
629+ true
630+ );
631+
632+ if ( in_array ( $ this ->tokens [ $ previous_token ]['code ' ], PHP_CodeSniffer_Tokens::$ comparisonTokens ) ) {
633+ return true ;
634+ }
635+
636+ // Maybe the comparison operator is after this.
637+ $ next_token = $ this ->phpcsFile ->findNext (
638+ PHP_CodeSniffer_Tokens::$ emptyTokens ,
639+ $ stackPtr + 1 ,
640+ null ,
641+ true
642+ );
643+
644+ // This might be an opening square bracket in the case of arrays ($var['a']).
645+ while ( T_OPEN_SQUARE_BRACKET === $ this ->tokens [ $ next_token ]['code ' ] ) {
646+
647+ $ next_token = $ this ->phpcsFile ->findNext (
648+ PHP_CodeSniffer_Tokens::$ emptyTokens ,
649+ $ this ->tokens [ $ next_token ]['bracket_closer ' ] + 1 ,
650+ null ,
651+ true
652+ );
653+ }
654+
655+ if ( in_array ( $ this ->tokens [ $ next_token ]['code ' ], PHP_CodeSniffer_Tokens::$ comparisonTokens ) ) {
656+ return true ;
657+ }
658+
659+ return false ;
660+ }
449661}
450662
451663// EOF
0 commit comments