Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding missing escaping check for <?= #858

Conversation

david-binda
Copy link
Contributor

This commit registers T_OPEN_TAG_WITH_ECHO in EscapeOutputSniff and adds new test for those cases

Fixes #857

cc @westonruter

This commit registers `T_OPEN_TAG_WITH_ECHO` in EscapeOutputSniff and adds new test for those cases
Copy link
Member

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@david-binda Looks good to me! Thanks.

FYI: I ran some additional tests with some slightly more complicated code within the short open tags and the sniff seems to hold its own.
<?= ( isset( $something ) ? $something : '' ?>, function calls within the tags etc.

@westonruter
Copy link
Member

Travis CI may not be responding for awhile due to the AWS outage.

<?php

// Ok.
<?= esc_html( $var ); ?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a PHP closing tag is missing here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing the build failure: you could just change the comment to an HTML comment instead ?

<?php

// Ok.
<?= esc_html( $var ); ?>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The immediately-preceding <?php can be removed.

Since this replacement changed the line numbering, this commit also updates the line number for related test in the *UnitTest.php file
@jrfnl
Copy link
Member

jrfnl commented Mar 1, 2017

@david-binda Just checking: do you know how to fix the PHP 5.3 build failure or do you need a hand with that ?

@david-binda
Copy link
Contributor Author

@jrfnl thanks for checking! I'll need to fire up some 5.3 environment and run the phpunit tests in there. My guess is that the shorttag is not being properly parsed in that version. I'll figure that out.

@jrfnl
Copy link
Member

jrfnl commented Mar 1, 2017

@david-binda I suspect you may need to look at the short_open_tag ini setting which is quite likely the culprit.

@david-binda
Copy link
Contributor Author

@jrfnl right. It actually is the culprit. I'm looking into whether there might be some way to report on the short open tags even when that short_open_tag ini is not set to true|"1"

@jrfnl
Copy link
Member

jrfnl commented Mar 1, 2017

@david-binda You may want to have a look here for inspiration: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Generic/Sniffs/PHP/DisallowAlternativePHPTagsSniff.php

As for the unit tests, you can provide an ini settings file to travis to use. See here for an example: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/.travis.yml
And here for the docs on this: https://docs.travis-ci.com/user/languages/php#Custom-PHP-configuration

@david-binda
Copy link
Contributor Author

@david-binda You may want to have a look here for inspiration: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Generic/Sniffs/PHP/DisallowAlternativePHPTagsSniff.php

Thanks, that's helpful. I have something similar in place.

As for the unit tests, you can provide an ini settings file to travis to use. See here for an example: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/.travis.yml
And here for the docs on this: https://docs.travis-ci.com/user/languages/php#Custom-PHP-configuration

@jrfnl I don't think that the failing test is what should be fixed. Or at least not the only thing which should be fixed.

The sniff, imho, should definitely report all <?= occurrences as Warnings with prompt for manual inspection in case the ini_get( 'short_open_tag' ) returns false|'' and I'm also playing with catching some obvious direct variable outputs as errors, but it involves regex and I'm not quite sure whether that is the best approach. Any thoughts on this?

@jrfnl
Copy link
Member

jrfnl commented Mar 1, 2017

@david-binda

@jrfnl I don't think that the failing test is what should be fixed. Or at least not the only thing which should be fixed.

Very true, but both situations - short_open_tags on|off - should probably be tested, that's why I pointed to the ini file option which can be used to do just that. You could use an ENV variable to set which build you'd want to test against the ini file.

I'm also playing with catching some obvious direct variable outputs as errors, but it involves regex and I'm not quite sure whether that is the best approach. Any thoughts on this?

I'd need to see some example code before I can give an opinion on the approach, but in case you decide to go with regex and need help with that, you're welcome to ping me.

…rt_open_tag in case they are disabled in PHP

In case the string is not obvisouly unespcaed, Warning is thrown instead asking for manual inspection.

This commit also adjusts the tests accordingly - conditionally adds Warning on line 211 for case short_open_tag is disabled
…ith short_open_tag set to On (it's off by default)
@david-binda
Copy link
Contributor Author

Whoops. Looks like I have broken the Sniff outside the PHP 5.3. I'll look into it.

);

// We can successfully report on unescaped open_short_tags only if they are enabled.
if ( true === (bool) ini_get( 'short_open_tag' ) ) {
Copy link
Member

@jrfnl jrfnl Mar 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@david-binda This is the issue for PHP 5.4+ as the T_OPEN_TAG_WITH_ECHO is supported independently of the short_open_tag setting from PHP 5.4+ upwards, so basically, leave the token in the base array and only use the else clause of this part with the reversed condition and you should be fine.

See changelog here: http://php.net/manual/en/language.basic-syntax.phptags.php

Copy link
Member

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build is only failing on some small code style issues now, so we're nearly there.

* which is how open_short_tags are being handled in that case.
*/
if ( false === $this->is_short_open_tag_enabled() ) {
var_export( 'Short open tag is disabled!' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the debug code.

*
* @return bool False if short_open_tag is not enabled, true otherwise
*/
public function is_short_open_tag_enabled() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned value of this function is not going to change during the PHPCS run, so I'd like to suggest adding a class (private) property instead, assigning the value to the property at the start of the register() function and referring to the property later on for efficiency.

* @return bool False if short_open_tag is not enabled, true otherwise
*/
public function is_short_open_tag_enabled() {
if ( true === version_compare(phpversion(), '5.4', '<' ) && false === (bool) ini_get( 'short_open_tag' ) ) {
Copy link
Member

@jrfnl jrfnl Mar 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the PHP_VERSION_ID constant instead for the comparison (which is backfilled in PHPCS).
PHP_VERSION_ID < 50400 is two less functions calls for the same result.

@@ -77,7 +77,7 @@ public function getWarningList() {
$list = array();

//Adding Warning which is triggerred in case open_short_tag is set to Off
if ( false === (bool) ini_get( 'short_open_tag' ) ) {
if ( true === version_compare(phpversion(), '5.4', '<' ) && false === (bool) ini_get( 'short_open_tag' ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the PHP_VERSION_ID constant instead (see above)

* which is how open_short_tags are being handled in that case.
*/
/**
* In case open_short_tag are turned off, we can attempt to regex T_INLINE_HTML
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are => is

*/
/**
* In case open_short_tag are turned off, we can attempt to regex T_INLINE_HTML
* which is how open_short_tags are being handled in that case.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open_short_tags => short open tags

Copy link
Member

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two more small remarks

}

// Throw warning in case the T_INLINE_HTML looks like a open_short_tag
if ( false !== strpos( $this->tokens[$stackPtr]['content'], '<?=' ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a separate sniff for this, so no need to throw that warning here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrfnl what Sniff do you have in mind, please?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both short open tags as well as alternative open tags are sniffed for in the WordPress-Core ruleset:
https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/blob/develop/WordPress-Core/ruleset.xml#L104-L105

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrfnl I'm happy to get rid of his Warning, but I have noticed that in PHP 5.3 with disabled short_open_tag the referenced sniff is not producing any warnings (as it's hitting the same issue with T_OPEN_TAG_WITH_ECHO as we are ).

So as long as we already have something in place for checking on T_INLINE_HTML, it might be good to take an advantage of that and report on those as well.

Copy link
Member

@jrfnl jrfnl Mar 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked and you're right. I thought I'd pulled that upstream, but seems I only included that logic when I pulled the Generic.PHP.DisallowAlternativePHPTags sniff.

So, yes. let's leave the warning in place for now until the upstream sniff has been improved.
You may want to open an issue for this upstream.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream issue opened: squizlabs/PHP_CodeSniffer#1398

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opened an upstream PR to fix the Generic.PHP.DisallowAlternativePHPTags sniff squizlabs/PHP_CodeSniffer#1400

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and before I forget: the upstream PR was merged and will be included in the next version of PHPCS, so the additional message can be removed once we up the minimum PHPCS version.

}

// Report on what very likely is a PHP short open tag outputing variable
if ( preg_match( '/\<\?\=[\s\t]*(\$[a-zA-Z_][[:alnum:]_]+)[\s\t]*\;?[\s\t]*\?\>/', $this->tokens[ $stackPtr ]['content'], $matches ) ) {
Copy link
Member

@jrfnl jrfnl Mar 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the regex does not detect the echo-ing out of array based values, i.e. $var['something'].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good call on the array based values. Outputting object properties might suffer from the same issue. I'll look into those cases later in the day. Thanks for pointing that out!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my suggestion below

Since the returned value is not going to be changed during the PHPCS run, we can store the value in a private proprty in order to improve sniff's efficiency
…VERSION_ID check since it being backfilled in PHPCS and thus can be used for our needs.
…bled

* Using the variable regex mentioned in the PHP manual: http://php.net/manual/en/language.variables.basics.php
* Not specifically mentioning `\t` as it's already included in `\s`
* Not escaping `;` as it's not necessary

The regex still needs to be improved in order to catch array based values and possibly object based values.
Copy link
Member

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for being a pain in the butt...

/**
* In case open_short_tag is turned off, we can attempt to regex T_INLINE_HTML
* which is how open short tags are being handled in that case.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open_short_tag => short_open_tag
open short tags => short open tags

*
* @var bool
*/
private $short_open_tag_enabled = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a @since tag

* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
// Check whether short_open_tag is disabled on PHP version < 5.4 (it''s enabled by default in later versions).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it''s => it's

}

// Report on what very likely is a PHP short open tag outputing variable.
if ( preg_match( '/\<\?\=[\s]*(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\s]*;?[\s]*\?\>/', $this->tokens[ $stackPtr ]['content'], $matches ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: your remark that the regex still needs improving for array and object based variables:

What about changing the regex to:
/\<\?\=[\s]*(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:(?:->\S+|\[[^\]]+\]))*)[\s]*;?[\s]*\?\>/

);

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a docblock, so it should only have one *.

}

// Report on what very likely is a PHP short open tag outputing variable.
if ( preg_match( '/\<\?\=[\s]*(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\s]*;?[\s]*\?\>/', $this->tokens[ $stackPtr ]['content'], $matches ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking out loud, I wonder if there is a way to replace each <?= with <?php echo and then reparse that part of the string as PHP. That would avoid regex entirely. But perhaps it really isn't such a good idea, or isn't really worth it even if it is possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is definitely possible, but might be more complicated and I wonder if it's really worth it as it would only be necessary for PHP <= 5.3 with short_open_tag off.

What would make it more complicated is that the T_INLINE_HTML token is split at line-end and you may need to join several T_INLINE_HTML tokens together if the php code is split over several lines as otherwise the tokenizer might fail.

Think:

<p>some text<?=
$variable;
?></p>

N.B.: the php code split over several T_INLINE_HTML tokens issue is not being addressed with the regex either, but as it's an edge-case, I don't have a problem with leaving that as is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it sounds like it isn't worth it.

@david-binda
Copy link
Contributor Author

I'll look into the failing regex, tho phpunit is processing that w/o issues on the PHP 5.3 vargrant I'm using :-/

@david-binda
Copy link
Contributor Author

Gah, turned out to be a bad copy-pasting 😊

return;
}

// Report on what very likely is a PHP short open tag outputing variable.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Darn.. missed this before: outputing => outputting

}

// Throw warning in case the T_INLINE_HTML looks like a open_short_tag
if ( false !== strpos( $this->tokens[$stackPtr]['content'], '<?=' ) ) {
Copy link
Member

@jrfnl jrfnl Mar 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked and you're right. I thought I'd pulled that upstream, but seems I only included that logic when I pulled the Generic.PHP.DisallowAlternativePHPTags sniff.

So, yes. let's leave the warning in place for now until the upstream sniff has been improved.
You may want to open an issue for this upstream.

@jrfnl
Copy link
Member

jrfnl commented Mar 9, 2017

@david-binda Thanks for all your efforts on this. This PR can now be merged.

As we're about to release 0.11.0, however, I suggest waiting to merge this PR until after the release so it can be thoroughly tested before being included in the next release.

@JDGrimes
Copy link
Contributor

JDGrimes commented Mar 9, 2017

As we're about to release 0.11.0, however, I suggest waiting to merge this PR until after the release so it can be thoroughly tested before being included in the next release.

+1

Great work @david-binda.

@jrfnl jrfnl added this to the 0.12.0 milestone Mar 9, 2017
@jrfnl
Copy link
Member

jrfnl commented Mar 20, 2017

@david-binda Could you please rebase the PR on the current develop branch ? I expect the unit tests to pass without problem after that and will merge the PR.

@@ -152,6 +175,24 @@ public function process_token( $stackPtr ) {
if ( in_array( $function, array( 'trigger_error', 'user_error' ), true ) ) {
$end_of_statement = $this->phpcsFile->findEndOfStatement( $open_paren + 1 );
}
} else if ( false === $this->short_open_tag_enabled && T_INLINE_HTML === $this->tokens[ $stackPtr ]['code'] ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed one last minor thingie: the first part of this if condition is not necessary as T_INLINE_HTML is only registered to this sniff if short_open_tag_enabled is true.

Having said that, that also means that the property can be removed and turned into a local variable within the register() method.

@jrfnl
Copy link
Member

jrfnl commented Apr 8, 2017

Hi @david-binda, just wondering if you saw the last few messages. Would be great if you could rebase the PR and fix the tiny last note. I look forward to having this in WPCS 😻

@pdufour
Copy link

pdufour commented Jun 5, 2017

Wanted to see if I could help in any way to push this through.

@jrfnl
Copy link
Member

jrfnl commented Jun 30, 2017

PR #1010 supersedes this PR and takes care of the issues which were still open.

@GaryJones GaryJones removed this from the 0.12.0 milestone Aug 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants