diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py
index 6dfe57a41d..8d3ebe08be 100644
--- a/cms/djangoapps/contentstore/views/course.py
+++ b/cms/djangoapps/contentstore/views/course.py
@@ -1148,7 +1148,7 @@ def advanced_settings_handler(request, course_key_string):
return render_to_response('settings_advanced.html', {
'context_course': course_module,
- 'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)),
+ 'advanced_dict': CourseMetadata.fetch(course_module),
'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key)
})
elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html
index eb108866a3..40f347ca30 100644
--- a/cms/templates/settings_advanced.html
+++ b/cms/templates/settings_advanced.html
@@ -4,7 +4,7 @@
<%!
from django.utils.translation import ugettext as _
from contentstore import utils
- from django.utils.html import escapejs
+ from openedx.core.lib.json_utils import escape_json_dumps
%>
<%block name="title">${_("Advanced Settings")}%block>
<%block name="bodyclass">is-signedin course advanced view-settings%block>
@@ -19,7 +19,7 @@
<%block name="requirejs">
require(["js/factories/settings_advanced"], function(SettingsAdvancedFactory) {
- SettingsAdvancedFactory(${advanced_dict | n}, "${advanced_settings_url}");
+ SettingsAdvancedFactory(${escape_json_dumps(advanced_dict) | n}, "${advanced_settings_url}");
});
%block>
diff --git a/openedx/core/lib/json_utils.py b/openedx/core/lib/json_utils.py
index 3c89321e99..076d0c1288 100644
--- a/openedx/core/lib/json_utils.py
+++ b/openedx/core/lib/json_utils.py
@@ -1,6 +1,7 @@
"""
Utilities for dealing with JSON.
"""
+import json
import simplejson
@@ -20,3 +21,50 @@ class EscapedEdxJSONEncoder(EdxJSONEncoder):
simplejson.loads(super(EscapedEdxJSONEncoder, self).encode(obj)),
cls=simplejson.JSONEncoderForHTML
)
+
+
+def _escape_json_for_html(json_str):
+ """
+ Escape JSON that is safe to be embedded in HTML.
+
+ This implementation is based on escaping performed in simplejson.JSONEncoderForHTML.
+
+ Arguments:
+ json_str (str): The JSON string to be escaped
+
+ Returns:
+ (str) Escaped JSON that is safe to be embedded in HTML.
+
+ """
+ json_str = json_str.replace("&", "\\u0026")
+ json_str = json_str.replace(">", "\\u003e")
+ json_str = json_str.replace("<", "\\u003c")
+ return json_str
+
+
+def escape_json_dumps(obj, cls=EdxJSONEncoder):
+ """
+ JSON dumps encoded JSON that is safe to be embedded in HTML.
+
+ Usage:
+ Can be used inside a Mako template inside a ': ''}
self.assertNotIn(
'',
json.dumps(malicious_json, cls=EscapedEdxJSONEncoder)
)
+
+ def test_escape_json_dumps_escapes_unsafe_html(self):
+ """
+ Test escape_json_dumps properly escapes &, <, and >.
+ """
+ malicious_json = {"": ""}
+ expected_encoded_json = (
+ r'''{"\u003c/script\u003e\u003cscript\u003ealert('hello, ');\u003c/script\u003e": '''
+ r'''"\u003c/script\u003e\u003cscript\u003ealert('\u0026world!');\u003c/script\u003e"}'''
+ )
+
+ encoded_json = escape_json_dumps(malicious_json)
+ self.assertEquals(expected_encoded_json, encoded_json)
+
+ def test_escape_json_dumps_with_custom_encoder_escapes_unsafe_html(self):
+ """
+ Test escape_json_dumps first encodes with custom JSNOEncoder before escaping &, <, and >
+
+ The test encoder class should first perform the replacement of "":
+ self.NoDefaultEncoding("")
+ }
+ expected_custom_encoded_json = (
+ r'''{"\u003c/script\u003e\u003cscript\u003ealert('hello, ');\u003c/script\u003e": '''
+ r'''"\u003c/script\u003esample-encoder-was-herealert('\u0026world!');\u003c/script\u003e"}'''
+ )
+
+ encoded_json = escape_json_dumps(malicious_json, cls=self.SampleJSONEncoder)
+ self.assertEquals(expected_custom_encoded_json, encoded_json)