}
/**
- * Output a "<script>" tag with the given contents.
+ * Output an HTML script tag with the given contents.
*
- * @todo do some useful escaping as well, like if $contents contains
- * literal "</script>" or (for XML) literal "]]>".
+ * It is unsupported for the contents to contain the sequence `<script` or `</script`
+ * (case-insensitive). This ensures the script can be terminated easily and consistently.
+ * It is the responsibility of the caller to avoid such character sequence by escaping
+ * or avoiding it. If found at run-time, the contents are replaced with a comment, and
+ * a warning is logged server-side.
*
* @param string $contents JavaScript
* @param string|null $nonce Nonce for CSP header, from OutputPage::getCSPNonce()
}
}
- if ( preg_match( '/[<&]/', $contents ) ) {
- $contents = "/*<![CDATA[*/$contents/*]]>*/";
+ if ( preg_match( '/<\/?script/i', $contents ) ) {
+ wfLogWarning( __METHOD__ . ': Illegal character sequence found in inline script.' );
+ $contents = '/* ERROR: Invalid script */';
}
return self::rawElement( 'script', $attrs, $contents );
<?php
class HtmlTest extends MediaWikiTestCase {
+ private $restoreWarnings;
protected function setUp() {
parent::setUp();
] );
$this->setUserLang( $langObj );
$this->setContentLang( $langObj );
+ $this->restoreWarnings = false;
+ }
+
+ protected function tearDown() {
+ if ( $this->restoreWarnings ) {
+ $this->restoreWarnings = false;
+ Wikimedia\restoreWarnings();
+ }
+ parent::tearDown();
}
/**
],
'Ampersand' => [
'EXAMPLE.is(a && b);',
- '<script>/*<![CDATA[*/EXAMPLE.is(a && b);/*]]>*/</script>'
+ '<script>EXAMPLE.is(a && b);</script>'
],
'HTML' => [
'EXAMPLE.label("<a>");',
- '<script>/*<![CDATA[*/EXAMPLE.label("<a>");/*]]>*/</script>'
+ '<script>EXAMPLE.label("<a>");</script>'
],
- 'Script closing string' => [
+ 'Script closing string (lower)' => [
'EXAMPLE.label("</script>");',
- // Broken: First </script> ends the script in HTML
- '<script>/*<![CDATA[*/EXAMPLE.label("</script>");/*]]>*/</script>'
+ '<script>/* ERROR: Invalid script */</script>',
+ true,
],
- 'CDATA string' => [
- 'EXAMPLE.label("&> CDATA ]]>");',
- // Broken: Works in HTML, but is invalid XML.
- '<script>/*<![CDATA[*/EXAMPLE.label("&> CDATA ]]>");/*]]>*/</script>'
+ 'Script closing with non-standard attributes (mixed)' => [
+ 'EXAMPLE.label("</SCriPT and STyLE>");',
+ '<script>/* ERROR: Invalid script */</script>',
+ true,
+ ],
+ 'HTML-comment-open and script-open' => [
+ // In HTML, <script> contents aren't just plain CDATA until </script>,
+ // there are levels of escaping modes, and the below sequence puts an
+ // HTML parser in a state where </script> would *not* close the script.
+ // https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escape-end-state
+ 'var a = "<!--<script>";',
+ '<script>/* ERROR: Invalid script */</script>',
+ true,
],
];
}
* @dataProvider provideInlineScript
* @covers Html::inlineScript
*/
- public function testInlineScript( $code, $expected ) {
+ public function testInlineScript( $code, $expected, $error = false ) {
+ if ( $error ) {
+ Wikimedia\suppressWarnings();
+ $this->restoreWarnings = true;
+ }
$this->assertSame( Html::inlineScript( $code ), $expected );
}
}