Skip to content

Commit c3c2bce

Browse files
committed
ProperEscapingFunction: allow for comma's in first short open echo tag expression
Short open echo tags will act as an echo for the first expression and allow for passing multiple comma-separated parameters. However, short open echo tags also allow for additional statements after, but those have to be full PHP statements, not expressions. So, the `T_COMMA` token should be allowed and skipped over in the first expression, but not for subsequent statements following a short open echo tag. `$phpcsFile->findStartOfStatement()` unfortunately is useless - even in its fixed-up form as will be in PHPCS 3.6.1-, as it will return the first token in the statement, which can be anything - variable, text string - without any indication of whether this is the start of a normal statement or a short open echo expression. So, if we used that, we'd still need to walk back from every start of statement to the previous non-empty to see if it is the short open echo tag. So to solve this conundrum, I've implemented a simple tracking system which will keep track of whether we have seen a short open echo tag and are within the first statement (expression) after this tag and will add the `T_COMMA` token if those conditions are fulfilled. Fixes 671
1 parent 23ec390 commit c3c2bce

File tree

3 files changed

+91
-34
lines changed

3 files changed

+91
-34
lines changed

WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ class ProperEscapingFunctionSniff extends Sniff {
8989
'=\\"',
9090
];
9191

92+
/**
93+
* Keep track of whether or not we're currently in the first statement of a short open echo tag.
94+
*
95+
* @var int|false Integer stack pointer to the end of the first statement in the current
96+
* short open echo tag or false when not in a short open echo tag.
97+
*/
98+
private $in_short_echo = false;
99+
92100
/**
93101
* Returns an array of tokens this test wants to listen for.
94102
*
@@ -97,7 +105,10 @@ class ProperEscapingFunctionSniff extends Sniff {
97105
public function register() {
98106
$this->echo_or_concat_tokens += Tokens::$emptyTokens;
99107

100-
return [ T_STRING ];
108+
return [
109+
T_STRING,
110+
T_OPEN_TAG_WITH_ECHO,
111+
];
101112
}
102113

103114
/**
@@ -108,6 +119,35 @@ public function register() {
108119
* @return void
109120
*/
110121
public function process_token( $stackPtr ) {
122+
/*
123+
* Short open echo tags will act as an echo for the first expression and
124+
* allow for passing multiple comma-separated parameters.
125+
* However, short open echo tags also allow for additional statements after, but
126+
* those have to be full PHP statements, not expressions.
127+
*
128+
* This snippet of code will keep track of whether or not we're in the first
129+
* expression in a short open echo tag.
130+
* $phpcsFile->findStartOfStatement() unfortunately is useless, as it will return
131+
* the first token in the statement, which can be anything - variable, text string -
132+
* without any indication of whether this is the start of a normal statement or
133+
* a short open echo expression.
134+
* So, if we used that, we'd need to walk back from every start of statement to
135+
* the previous non-empty to see if it is the short open echo tag.
136+
*/
137+
if ( $this->tokens[ $stackPtr ]['code'] === T_OPEN_TAG_WITH_ECHO ) {
138+
$end_of_echo = $this->phpcsFile->findNext( [ T_SEMICOLON, T_CLOSE_TAG ], ( $stackPtr + 1 ) );
139+
if ( $end_of_echo === false ) {
140+
$this->in_short_echo = $this->phpcsFile->numTokens;
141+
} else {
142+
$this->in_short_echo = $end_of_echo;
143+
}
144+
145+
return;
146+
}
147+
148+
if ( $this->in_short_echo !== false && $this->in_short_echo < $stackPtr ) {
149+
$this->in_short_echo = false;
150+
}
111151

112152
$function_name = strtolower( $this->tokens[ $stackPtr ]['content'] );
113153

@@ -121,10 +161,14 @@ public function process_token( $stackPtr ) {
121161
return;
122162
}
123163

124-
$ignore = $this->echo_or_concat_tokens;
125-
$start_of_statement = $this->phpcsFile->findStartOfStatement( $stackPtr, T_COMMA );
126-
if ( $this->tokens[ $start_of_statement ]['code'] === T_ECHO ) {
164+
$ignore = $this->echo_or_concat_tokens;
165+
if ( $this->in_short_echo !== false ) {
127166
$ignore[ T_COMMA ] = T_COMMA;
167+
} else {
168+
$start_of_statement = $this->phpcsFile->findStartOfStatement( $stackPtr, T_COMMA );
169+
if ( $this->tokens[ $start_of_statement ]['code'] === T_ECHO ) {
170+
$ignore[ T_COMMA ] = T_COMMA;
171+
}
128172
}
129173

130174
$html = $this->phpcsFile->findPrevious( $ignore, $stackPtr - 1, null, true );

WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,14 @@ echo '<input class="something something-else something-more"
9595
action="', esc_url( $my_var ), '">'; // OK.
9696
echo '<input class="something something-else something-more"
9797
action="', esc_attr( $my_var ), '">'; // Error.
98+
99+
// Verify correct handling of comma's in short open echo tags, without affecting subsequent statements.
100+
?>
101+
<div>html</div>
102+
<?= '<h1>' , esc_attr( $test ) , '</h1>'; // Error.
103+
printf( '<meta name="generator" content="%s">', esc_attr( $content ) ); // OK.
104+
echo '<a href="', esc_html($url), '">'; // Error.
105+
?>
106+
<div>html</div>
107+
<?= '<h1 class="', esc_attr( $test ), '">'; ?><!-- OK -->
108+
<div>html</div>

WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,38 @@ class ProperEscapingFunctionUnitTest extends AbstractSniffUnitTest {
2525
*/
2626
public function getErrorList() {
2727
return [
28-
3 => 1,
29-
5 => 1,
30-
15 => 1,
31-
17 => 1,
32-
21 => 1,
33-
23 => 1,
34-
33 => 1,
35-
37 => 1,
36-
41 => 1,
37-
45 => 1,
38-
48 => 1,
39-
62 => 1,
40-
63 => 1,
41-
64 => 1,
42-
65 => 1,
43-
67 => 1,
44-
68 => 1,
45-
69 => 1,
46-
72 => 1,
47-
73 => 1,
48-
74 => 1,
49-
75 => 1,
50-
76 => 1,
51-
77 => 1,
52-
78 => 1,
53-
79 => 1,
54-
80 => 1,
55-
82 => 1,
56-
92 => 1,
57-
97 => 1,
28+
3 => 1,
29+
5 => 1,
30+
15 => 1,
31+
17 => 1,
32+
21 => 1,
33+
23 => 1,
34+
33 => 1,
35+
37 => 1,
36+
41 => 1,
37+
45 => 1,
38+
48 => 1,
39+
62 => 1,
40+
63 => 1,
41+
64 => 1,
42+
65 => 1,
43+
67 => 1,
44+
68 => 1,
45+
69 => 1,
46+
72 => 1,
47+
73 => 1,
48+
74 => 1,
49+
75 => 1,
50+
76 => 1,
51+
77 => 1,
52+
78 => 1,
53+
79 => 1,
54+
80 => 1,
55+
82 => 1,
56+
92 => 1,
57+
97 => 1,
58+
102 => 1,
59+
104 => 1,
5860
];
5961
}
6062

0 commit comments

Comments
 (0)