fixed js escaper to be stricter, whilelist-based js escaper (closes #114)
authorArnaud Le Blanc <arnaud.lb@gmail.com>
Tue, 24 Aug 2010 06:22:05 +0000 (08:22 +0200)
committerFabien Potencier <fabien.potencier@gmail.com>
Tue, 14 Sep 2010 07:39:10 +0000 (09:39 +0200)
CHANGELOG
lib/Twig/Extension/Core.php
test/Twig/Tests/Fixtures/tags/autoescape/strategy.test

index b783fd1..d202eb8 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@ Backward incompatibilities:
  * the odd and even filters are now tests:
      {{ foo|odd }} must now be written {{ foo is odd }}
 
+ * fixed js escaper to be stricter (now uses a whilelist-based js escaper)
  * added a "constant" filter
  * added a "constant" test
  * fixed objects with __toString() not being autoescaped
index 695aa9f..6080e97 100644 (file)
@@ -224,16 +224,62 @@ function twig_escape_filter(Twig_Environment $env, $string, $type = 'html')
 
     switch ($type) {
         case 'js':
-            // a function the c-escapes a string, making it suitable to be placed in a JavaScript string
-            return str_replace(array("\\"  , "\n"  , "\r" , "\""  , "'"),
-                                                 array("\\\\", "\\n" , "\\r", "\\\"", "\\'"),
-                                                 $string);
+            // escape all non-alphanumeric characters
+            // into their \xHH or \uHHHH representations
+            $charset = $env->getCharset();
+
+            if ('UTF-8' != $charset) {
+                $string = _twig_convert_encoding($string, 'UTF-8', $charset);
+            }
+
+            if (null === $string = preg_replace_callback('#[^\p{L}\p{N} ]#u', '_twig_escape_js_callback', $string)) {
+                throw new InvalidArgumentException('The string to escape is not a valid UTF-8 string.');
+            }
+
+            if ('UTF-8' != $charset) {
+                $string = _twig_convert_encoding($string, $charset, 'UTF-8');
+            }
+
+            return $string;
+
         case 'html':
         default:
             return htmlspecialchars($string, ENT_QUOTES, $env->getCharset());
     }
 }
 
+if (function_exists('iconv')) {
+    function _twig_convert_encoding($string, $to, $from)
+    {
+        return iconv($from, $to, $string);
+    }
+} elseif (function_exists('mb_convert_encoding')) {
+    function _twig_convert_encoding($string, $to, $from)
+    {
+        return mb_convert_encoding($string, $to, $from);
+    }
+} else {
+    function _twig_convert_encoding($string, $to, $from)
+    {
+        throw new RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
+    }
+}
+
+function _twig_escape_js_callback($matches)
+{
+    $char = $matches[0];
+
+    // \xHH
+    if (!isset($char[1])) {
+        return '\\x'.substr('00'.bin2hex($char), -2);
+    }
+
+    // \uHHHH
+    $char = _twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
+
+    return '\\u'.substr('0000'.bin2hex($char), -4);
+}
+
 // add multibyte extensions if possible
 if (function_exists('mb_get_info')) {
     function twig_length_filter(Twig_Environment $env, $thing)
index f3e09dd..a2c9e40 100644 (file)
@@ -7,5 +7,5 @@
 --DATA--
 return array('var' => '<br />"')
 --EXPECT--
-<br />\"
+\x3cbr \x2f\x3e\x22
 &lt;br /&gt;&quot;