diff --git a/CHANGELOG b/CHANGELOG index 5d869ca2b..98c031bd3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ * 1.35.4 (2018-XX-XX) - * n/a + * "js" filter now produces valid JSON * 1.35.3 (2018-03-20) diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index 6e5569a28..0e35e36ed 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -1040,7 +1040,7 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', case 'js': // escape all non-alphanumeric characters - // into their \xHH or \uHHHH representations + // into their \x or \uHHHH representations if ('UTF-8' !== $charset) { $string = twig_convert_encoding($string, 'UTF-8', $charset); } @@ -1152,9 +1152,23 @@ function _twig_escape_js_callback($matches) { $char = $matches[0]; - // \xHH - if (!isset($char[1])) { - return '\\x'.strtoupper(substr('00'.bin2hex($char), -2)); + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are ommitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = array( + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ); + + if (isset($shortMap[$char])) { + return $shortMap[$char]; } // \uHHHH diff --git a/test/Twig/Tests/EnvironmentTest.php b/test/Twig/Tests/EnvironmentTest.php index ca9f2cf8a..dd8dac9ad 100644 --- a/test/Twig/Tests/EnvironmentTest.php +++ b/test/Twig/Tests/EnvironmentTest.php @@ -61,7 +61,7 @@ class Twig_Tests_EnvironmentTest extends \PHPUnit\Framework\TestCase )); $this->assertEquals('foo<br/ > foo<br/ >', $twig->render('html', array('foo' => 'foo
'))); - $this->assertEquals('foo\x3Cbr\x2F\x20\x3E foo\x3Cbr\x2F\x20\x3E', $twig->render('js', array('bar' => 'foo
'))); + $this->assertEquals('foo\u003Cbr\/\u0020\u003E foo\u003Cbr\/\u0020\u003E', $twig->render('js', array('bar' => 'foo
'))); } public function escapingStrategyCallback($name) diff --git a/test/Twig/Tests/Fixtures/autoescape/name.test b/test/Twig/Tests/Fixtures/autoescape/name.test index 04299bed3..98e89399a 100644 --- a/test/Twig/Tests/Fixtures/autoescape/name.test +++ b/test/Twig/Tests/Fixtures/autoescape/name.test @@ -17,6 +17,6 @@ return array('br' => '
') return array('autoescape' => 'name') --EXPECT-- <br /> -\x3Cbr\x20\x2F\x3E +\u003Cbr\u0020\/\u003E <br />
diff --git a/test/Twig/Tests/Fixtures/filters/escape_javascript.test b/test/Twig/Tests/Fixtures/filters/escape_javascript.test index 647147a43..8e7278119 100644 --- a/test/Twig/Tests/Fixtures/filters/escape_javascript.test +++ b/test/Twig/Tests/Fixtures/filters/escape_javascript.test @@ -5,4 +5,4 @@ --DATA-- return array() --EXPECT-- -\u00E9\x20\u265C\x20\uD834\uDF06 +\u00E9\u0020\u265C\u0020\uD834\uDF06 diff --git a/test/Twig/Tests/Fixtures/filters/force_escape.test b/test/Twig/Tests/Fixtures/filters/force_escape.test index 85a9b7172..eb9cba7cf 100644 --- a/test/Twig/Tests/Fixtures/filters/force_escape.test +++ b/test/Twig/Tests/Fixtures/filters/force_escape.test @@ -14,5 +14,5 @@ return array() --EXPECT-- foo<br /> -\x20\x20\x20\x20foo\x3Cbr\x20\x2F\x3E\x0A +\u0020\u0020\u0020\u0020foo\u003Cbr\u0020\/\u003E\n foo
diff --git a/test/Twig/Tests/Fixtures/tags/autoescape/functions.test b/test/Twig/Tests/Fixtures/tags/autoescape/functions.test index ce7ea789e..653c41b8e 100644 --- a/test/Twig/Tests/Fixtures/tags/autoescape/functions.test +++ b/test/Twig/Tests/Fixtures/tags/autoescape/functions.test @@ -80,4 +80,4 @@ unsafe_br()|escape autoescape js safe_br -\x3Cbr\x20\x2F\x3E +\u003Cbr\u0020\/\u003E diff --git a/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test b/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test index bbf1356e7..c3f8eddfa 100644 --- a/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test +++ b/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test @@ -7,5 +7,5 @@ --DATA-- return array('var' => '
"') --EXPECT-- -\x3Cbr\x20\x2F\x3E\x22 +\u003Cbr\u0020\/\u003E\u0022 <br />" diff --git a/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test b/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test index e496f6081..5b69449c2 100644 --- a/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test +++ b/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test @@ -7,5 +7,5 @@ --DATA-- return array('var' => '
"') --EXPECT-- -\x3Cbr\x20\x2F\x3E\x22 +\u003Cbr\u0020\/\u003E\u0022 <br />" diff --git a/test/Twig/Tests/Fixtures/tags/autoescape/type.test b/test/Twig/Tests/Fixtures/tags/autoescape/type.test index 4f415201d..1250f0db1 100644 --- a/test/Twig/Tests/Fixtures/tags/autoescape/type.test +++ b/test/Twig/Tests/Fixtures/tags/autoescape/type.test @@ -44,15 +44,15 @@ return array('msg' => "<>\n'\"") 1. autoescape 'html' |escape('js') - + 2. autoescape 'html' |escape('js') - + 3. autoescape 'js' |escape('js') - + 4. no escape @@ -61,9 +61,9 @@ return array('msg' => "<>\n'\"") 5. |escape('js')|escape('html') - + 6. autoescape 'html' |escape('js')|escape('html') - + diff --git a/test/Twig/Tests/escapingTest.php b/test/Twig/Tests/escapingTest.php index 9b98dddcf..9c2e12038 100644 --- a/test/Twig/Tests/escapingTest.php +++ b/test/Twig/Tests/escapingTest.php @@ -51,13 +51,15 @@ class Twig_Test_EscapingTest extends \PHPUnit\Framework\TestCase protected $jsSpecialChars = array( /* HTML special chars - escape without exception to hex */ - '<' => '\\x3C', - '>' => '\\x3E', - '\'' => '\\x27', - '"' => '\\x22', - '&' => '\\x26', + '<' => '\\u003C', + '>' => '\\u003E', + '\'' => '\\u0027', + '"' => '\\u0022', + '&' => '\\u0026', + '/' => '\\/', /* Characters beyond ASCII value 255 to unicode escape */ 'Ā' => '\\u0100', + '😀' => '\\uD83D\\uDE00', /* Immune chars excluded */ ',' => ',', '.' => '.', @@ -70,12 +72,14 @@ class Twig_Test_EscapingTest extends \PHPUnit\Framework\TestCase '0' => '0', '9' => '9', /* Basic control characters and null */ - "\r" => '\\x0D', - "\n" => '\\x0A', - "\t" => '\\x09', - "\0" => '\\x00', + "\r" => '\r', + "\n" => '\n', + "\x08" => '\b', + "\t" => '\t', + "\x0C" => '\f', + "\0" => '\\u0000', /* Encode spaces for quoteless attribute protection */ - ' ' => '\\x20', + ' ' => '\\u0020', ); protected $urlSpecialChars = array(