@@ -446,6 +446,218 @@ protected function is_sanitized( $stackPtr ) {
446
446
// Check if this is a sanitizing function.
447
447
return in_array ( $ functionName , WordPress_Sniffs_XSS_EscapeOutputSniff::$ sanitizingFunctions );
448
448
}
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
+ }
449
661
}
450
662
451
663
// EOF
0 commit comments