Repository: django-cms/django-cms

Issue:
Added template filter "json" which is not vulnerable to XSS attacks
Added template tag `...|json` which shall be used instead of `...|js`, because the latter is vulnerable to XSS attacks when used in combination with unvalidated data.

For details see https://code.djangoproject.com/ticket/17419



Discussion:

Resolving patch:
diff --git a/cms/templatetags/cms_js_tags.py b/cms/templatetags/cms_js_tags.py
--- a/cms/templatetags/cms_js_tags.py
+++ b/cms/templatetags/cms_js_tags.py
@@ -1,9 +1,12 @@
 # -*- coding: utf-8 -*-
+import warnings
 from classytags.core import Tag, Options
+from cms.utils.encoder import SafeJSONEncoder
 from cms.utils.compat import DJANGO_1_4
 from django import template
 from django.core.serializers.json import DjangoJSONEncoder
 from django.utils.text import javascript_quote
+from django.utils.safestring import mark_safe
 if DJANGO_1_4:
     from django.utils import simplejson as json
 else:
@@ -13,9 +16,19 @@
 
 @register.filter
 def js(value):
+    warnings.warn("The template filter '...|js' is vulnerable to XSS attacks, please use '...|json' instead.",
+                  DeprecationWarning, stacklevel=2)
     return json.dumps(value, cls=DjangoJSONEncoder)
 
 
+@register.filter('json')
+def json_filter(value):
+    """
+    Returns the JSON representation of ``value`` in a safe manner.
+    """
+    return mark_safe(json.dumps(value, cls=SafeJSONEncoder))
+
+
 @register.filter
 def bool(value):
     if value:
@@ -23,17 +36,16 @@ def bool(value):
     else:
         return 'false'
 
-   
+
 class JavascriptString(Tag):
     name = 'javascript_string'
-    
     options = Options(
         blocks=[
             ('end_javascript_string', 'nodelist'),
         ]
     )
-    
+
     def render_tag(self, context, **kwargs):
         rendered = self.nodelist.render(context)
         return u"'%s'" % javascript_quote(rendered.strip())
-register.tag(JavascriptString)
\ No newline at end of file
+register.tag(JavascriptString)
diff --git a/cms/utils/__init__.py b/cms/utils/__init__.py
--- a/cms/utils/__init__.py
+++ b/cms/utils/__init__.py
@@ -3,7 +3,6 @@
 from django.conf import settings
 from django.core.files.storage import get_storage_class
 from django.utils.functional import LazyObject
-
 from cms import constants
 from cms.utils.conf import get_cms_setting
 from cms.utils.conf import get_site_id  # nopyflakes
@@ -33,7 +32,7 @@ def get_template_from_request(request, obj=None, no_current_page=False):
             # Happens on admin's request when changing the template for a page
             # to "inherit".
             return obj.get_template()
-        return template    
+        return template
     return get_cms_setting('TEMPLATES')[0][0]
 
 
diff --git a/cms/utils/encoder.py b/cms/utils/encoder.py
new file mode 100644
--- /dev/null
+++ b/cms/utils/encoder.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from django.utils.html import conditional_escape
+from django.core.serializers.json import DjangoJSONEncoder
+
+
+class SafeJSONEncoder(DjangoJSONEncoder):
+    def _recursive_escape(self, o, esc=conditional_escape):
+        if isinstance(o, dict):
+            return type(o)((esc(k), self._recursive_escape(v)) for (k, v) in o.iteritems())
+        if isinstance(o, (list, tuple)):
+            return type(o)(self._recursive_escape(v) for v in o)
+        try:
+            return type(o)(esc(o))
+        except ValueError:
+            return esc(o)
+
+    def encode(self, o):
+        value = self._recursive_escape(o)
+        return super(SafeJSONEncoder, self).encode(value)


Test patch: