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

Add new sniff InlineScriptEscaping #623

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
/**
* WordPressVIPMinimum Coding Standard.
*
* @package VIPCS\WordPressVIPMinimum
* @link https://github.com/Automattic/VIP-Coding-Standards
*/

namespace WordPressVIPMinimum\Sniffs\Security;

use WordPressVIPMinimum\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;

/**
* Checks whether proper escaping function is used between script tags.
*
* @package VIPCS\WordPressVIPMinimum
*/
class InlineScriptEscapingSniff extends Sniff {

/**
* Property to keep track of between start and close script tags.
*
* @var array
*/
private $in_script = false;

/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return [
'T_INLINE_HTML' => T_INLINE_HTML,
'T_STRING' => T_STRING,
];
}

/**
* Process this test when one of its tokens is encountered
*
* @param int $stackPtr The position of the current token in the stack passed in $tokens.
*
* @return void
*/
public function process_token( $stackPtr ) {
$content = trim( $this->tokens[ $stackPtr ]['content'] );

if ( $content === '' ) {
return;
}

if ( $this->has_open_script_tag( $content ) === true ) {
$this->in_script = true;
} elseif ( strpos( '</script>', $content ) !== false ) {
$this->in_script = false;
}

if ( $this->in_script === true && $content === 'esc_js' ) {
$message = 'Please do not use `esc_js()` for inline script escaping. See our code repository for
examples on how to escape within: https://github.com/Automattic/vip-code-samples/blob/master/10-security/js-dynamic.php';
$this->phpcsFile->addError( $message, $stackPtr, 'InlineScriptEsc' );
return;
}
}

/**
* Check if a content string contains start <script> tag without closing one.
*
* @param string $content Haystack where we look for <script> tag.
*
* @return bool True if the string contains only start <script> tag, false otherwise.
*/
public function has_open_script_tag( $content ) {
if ( substr( $content, -1 ) !== '>' || strpos( $content, '</script>' ) !== false ) {
// Incomplete or has closing tag, bail.
return false;
}

return strpos( $content, '<script' ) !== false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
$color = 'blue';
$url = 'https://test.com';
$themeURI = get_template_directory_uri();
$ga_ua = 'foo';
$page_title = '<h1>Test</h1>';
?>
<script type="text/javascript">
var name = decodeURIComponent( '<?php echo esc_js( $name ); ?>' ); // Error.
var html = document.createElement('h1');
html.innerText = name;
</script>
<script>
window.location.replace('<?php echo esc_js(site_url());?>/foo/#bar'); // Error.
ga('create', <?php echo (esc_js($ga_ua)); ?>, 'auto'); // Error.
</script>
<script type="text/javascript" src="<?php echo esc_js($themeURI.'/js/lib.min.js' ); // OK - Not in script scope.
?>"></script>
<script>
var url = <?php echo wp_json_encode( esc_url( $url ) ) ?>; // OK.
var title = decodeURIComponent( '<?php echo rawurlencode( (string) $page_title ); ?>' ); // OK.
</script>

<style>
h1 {
color: <?php echo esc_js( $color ) ?>; /* OK - Not in script scope. */
font-family: verdana;
font-size: 300%;
}
</style>
<script src="http://someurl/somefile.js"></script> <!-- Random comment here -->
<?php esc_js( _deprecated_argument() ); // OK.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* Unit test class for WordPressVIPMinimum Coding Standard.
*
* @package VIPCS\WordPressVIPMinimum
*/

namespace WordPressVIPMinimum\Tests\Security;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit test class for the InlineScriptEscaping sniff.
*
* @package VIPCS\WordPressVIPMinimum
*
* @covers \WordPressVIPMinimum\Sniffs\Security\InlineScriptEscapingSniff
*/
class InlineScriptEscapingUnitTest extends AbstractSniffUnitTest {

/**
* Returns the lines where errors should occur.
*
* @return array <int line number> => <int number of errors>
*/
public function getErrorList() {
return [
9 => 1,
14 => 1,
15 => 1,
];
}

/**
* Returns the lines where warnings should occur.
*
* @return array <int line number> => <int number of warnings>
*/
public function getWarningList() {
return [];
}

}