Commit b7698123 by Robert Raposa

Merge pull request #10484 from edx/robrap/TNL-2646

TNL-2646: Escape json in Studio
parents acf5bb57 3682fac8
...@@ -376,7 +376,7 @@ def certificates_list_handler(request, course_key_string): ...@@ -376,7 +376,7 @@ def certificates_list_handler(request, course_key_string):
'certificate_url': certificate_url, 'certificate_url': certificate_url,
'course_outline_url': course_outline_url, 'course_outline_url': course_outline_url,
'upload_asset_url': upload_asset_url, 'upload_asset_url': upload_asset_url,
'certificates': json.dumps(certificates), 'certificates': certificates,
'course_modes': course_modes, 'course_modes': course_modes,
'certificate_web_view_url': certificate_web_view_url, 'certificate_web_view_url': certificate_web_view_url,
'is_active': is_active, 'is_active': is_active,
......
from __future__ import absolute_import from __future__ import absolute_import
import json
import logging import logging
from django.http import HttpResponseBadRequest, Http404 from django.http import HttpResponseBadRequest, Http404
...@@ -74,66 +73,6 @@ def _advanced_component_types(): ...@@ -74,66 +73,6 @@ def _advanced_component_types():
return [c_type for c_type in ADVANCED_COMPONENT_TYPES if c_type not in settings.DEPRECATED_ADVANCED_COMPONENT_TYPES] return [c_type for c_type in ADVANCED_COMPONENT_TYPES if c_type not in settings.DEPRECATED_ADVANCED_COMPONENT_TYPES]
@require_GET
@login_required
def subsection_handler(request, usage_key_string):
"""
The restful handler for subsection-specific requests.
GET
html: return html page for editing a subsection
json: not currently supported
"""
if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
usage_key = UsageKey.from_string(usage_key_string)
try:
course, item, lms_link, preview_link = _get_item_in_course(request, usage_key)
except ItemNotFoundError:
return HttpResponseBadRequest()
# make sure that location references a 'sequential', otherwise return
# BadRequest
if item.location.category != 'sequential':
return HttpResponseBadRequest()
parent = get_parent_xblock(item)
# remove all metadata from the generic dictionary that is presented in a
# more normalized UI. We only want to display the XBlocks fields, not
# the fields from any mixins that have been added
fields = getattr(item, 'unmixed_class', item.__class__).fields
policy_metadata = dict(
(field.name, field.read_from(item))
for field
in fields.values()
if field.name not in ['display_name', 'start', 'due', 'format'] and field.scope == Scope.settings
)
can_view_live = False
subsection_units = item.get_children()
can_view_live = any([modulestore().has_published_version(unit) for unit in subsection_units])
return render_to_response(
'edit_subsection.html',
{
'subsection': item,
'context_course': course,
'new_unit_category': 'vertical',
'lms_link': lms_link,
'preview_link': preview_link,
'course_graders': json.dumps(CourseGradingModel.fetch(item.location.course_key).graders),
'parent_item': parent,
'locator': item.location,
'policy_metadata': policy_metadata,
'subsection_units': subsection_units,
'can_view_live': can_view_live
}
)
else:
return HttpResponseBadRequest("Only supports html requests")
def _load_mixed_class(category): def _load_mixed_class(category):
""" """
Load an XBlock by category name, and apply all defined mixins Load an XBlock by category name, and apply all defined mixins
...@@ -213,7 +152,7 @@ def container_handler(request, usage_key_string): ...@@ -213,7 +152,7 @@ def container_handler(request, usage_key_string):
'section': section, 'section': section,
'new_unit_category': 'vertical', 'new_unit_category': 'vertical',
'ancestor_xblocks': ancestor_xblocks, 'ancestor_xblocks': ancestor_xblocks,
'component_templates': json.dumps(component_templates), 'component_templates': component_templates,
'xblock_info': xblock_info, 'xblock_info': xblock_info,
'draft_preview_link': preview_lms_link, 'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link, 'published_preview_link': lms_link,
......
...@@ -36,6 +36,7 @@ from opaque_keys.edx.locations import Location ...@@ -36,6 +36,7 @@ from opaque_keys.edx.locations import Location
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from openedx.core.lib.js_utils import escape_json_dumps
from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update
from contentstore.course_group_config import ( from contentstore.course_group_config import (
GroupConfiguration, GroupConfiguration,
...@@ -317,10 +318,10 @@ def course_search_index_handler(request, course_key_string): ...@@ -317,10 +318,10 @@ def course_search_index_handler(request, course_key_string):
try: try:
reindex_course_and_check_access(course_key, request.user) reindex_course_and_check_access(course_key, request.user)
except SearchIndexingError as search_err: except SearchIndexingError as search_err:
return HttpResponse(json.dumps({ return HttpResponse(escape_json_dumps({
"user_message": search_err.error_list "user_message": search_err.error_list
}), content_type=content_type, status=500) }), content_type=content_type, status=500)
return HttpResponse(json.dumps({ return HttpResponse(escape_json_dumps({
"user_message": _("Course has been successfully reindexed.") "user_message": _("Course has been successfully reindexed.")
}), content_type=content_type, status=200) }), content_type=content_type, status=200)
...@@ -554,9 +555,6 @@ def course_index(request, course_key): ...@@ -554,9 +555,6 @@ def course_index(request, course_key):
'sections': sections, 'sections': sections,
'course_structure': course_structure, 'course_structure': course_structure,
'initial_state': course_outline_initial_state(locator_to_show, course_structure) if locator_to_show else None, 'initial_state': course_outline_initial_state(locator_to_show, course_structure) if locator_to_show else None,
'course_graders': json.dumps(
CourseGradingModel.fetch(course_key).graders
),
'rerun_notification_id': current_action.id if current_action else None, 'rerun_notification_id': current_action.id if current_action else None,
'course_release_date': course_release_date, 'course_release_date': course_release_date,
'settings_url': settings_url, 'settings_url': settings_url,
...@@ -1056,7 +1054,7 @@ def grading_handler(request, course_key_string, grader_index=None): ...@@ -1056,7 +1054,7 @@ def grading_handler(request, course_key_string, grader_index=None):
return render_to_response('settings_graders.html', { return render_to_response('settings_graders.html', {
'context_course': course_module, 'context_course': course_module,
'course_locator': course_key, 'course_locator': course_key,
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder), 'course_details': course_details,
'grading_url': reverse_course_url('grading_handler', course_key), 'grading_url': reverse_course_url('grading_handler', course_key),
'is_credit_course': is_credit_course(course_key), 'is_credit_course': is_credit_course(course_key),
}) })
......
...@@ -10,6 +10,7 @@ from django.contrib.auth.decorators import login_required ...@@ -10,6 +10,7 @@ from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from openedx.core.lib.js_utils import escape_json_dumps
from contentstore.views.helpers import create_xblock, remove_entrance_exam_graders from contentstore.views.helpers import create_xblock, remove_entrance_exam_graders
from contentstore.views.item import delete_item from contentstore.views.item import delete_item
from models.settings.course_metadata import CourseMetadata from models.settings.course_metadata import CourseMetadata
...@@ -185,7 +186,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613 ...@@ -185,7 +186,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
try: try:
exam_descriptor = modulestore().get_item(exam_key) exam_descriptor = modulestore().get_item(exam_key)
return HttpResponse( return HttpResponse(
_serialize_entrance_exam(exam_descriptor), escape_json_dumps({'locator': unicode(exam_descriptor.location)}),
status=200, mimetype='application/json') status=200, mimetype='application/json')
except ItemNotFoundError: except ItemNotFoundError:
return HttpResponse(status=404) return HttpResponse(status=404)
...@@ -241,15 +242,6 @@ def _delete_entrance_exam(request, course_key): ...@@ -241,15 +242,6 @@ def _delete_entrance_exam(request, course_key):
return HttpResponse(status=204) return HttpResponse(status=204)
def _serialize_entrance_exam(entrance_exam_module):
"""
Internal helper to convert an entrance exam module/object into JSON
"""
return json.dumps({
'locator': unicode(entrance_exam_module.location)
})
def add_entrance_exam_milestone(course_id, x_block): def add_entrance_exam_milestone(course_id, x_block):
# Add an entrance exam milestone if one does not already exist for given xBlock # Add an entrance exam milestone if one does not already exist for given xBlock
# As this is a standalone method for entrance exam, We should check that given xBlock should be an entrance exam. # As this is a standalone method for entrance exam, We should check that given xBlock should be an entrance exam.
......
...@@ -4,7 +4,7 @@ from django.http import (HttpResponse, HttpResponseServerError, ...@@ -4,7 +4,7 @@ from django.http import (HttpResponse, HttpResponseServerError,
HttpResponseNotFound) HttpResponseNotFound)
from edxmako.shortcuts import render_to_string, render_to_response from edxmako.shortcuts import render_to_string, render_to_response
import functools import functools
import json from openedx.core.lib.js_utils import escape_json_dumps
__all__ = ['not_found', 'server_error', 'render_404', 'render_500'] __all__ = ['not_found', 'server_error', 'render_404', 'render_500']
...@@ -18,7 +18,7 @@ def jsonable_error(status=500, message="The Studio servers encountered an error" ...@@ -18,7 +18,7 @@ def jsonable_error(status=500, message="The Studio servers encountered an error"
@functools.wraps(func) @functools.wraps(func)
def inner(request, *args, **kwargs): def inner(request, *args, **kwargs):
if request.is_ajax(): if request.is_ajax():
content = json.dumps({"error": message}) content = escape_json_dumps({"error": message})
return HttpResponse(content, content_type="application/json", return HttpResponse(content, content_type="application/json",
status=status) status=status)
else: else:
......
...@@ -853,7 +853,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -853,7 +853,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"due_date": get_default_time_display(xblock.due), "due_date": get_default_time_display(xblock.due),
"due": xblock.fields['due'].to_json(xblock.due), "due": xblock.fields['due'].to_json(xblock.due),
"format": xblock.format, "format": xblock.format,
"course_graders": json.dumps([grader.get('type') for grader in graders]), "course_graders": [grader.get('type') for grader in graders],
"has_changes": has_changes, "has_changes": has_changes,
"actions": xblock_actions, "actions": xblock_actions,
"explanatory_message": explanatory_message, "explanatory_message": explanatory_message,
......
...@@ -191,7 +191,7 @@ def library_blocks_view(library, user, response_format): ...@@ -191,7 +191,7 @@ def library_blocks_view(library, user, response_format):
return render_to_response('library.html', { return render_to_response('library.html', {
'can_edit': can_edit, 'can_edit': can_edit,
'context_library': library, 'context_library': library,
'component_templates': json.dumps(component_templates), 'component_templates': component_templates,
'xblock_info': xblock_info, 'xblock_info': xblock_info,
'templates': CONTAINER_TEMPLATES, 'templates': CONTAINER_TEMPLATES,
}) })
......
...@@ -1642,7 +1642,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1642,7 +1642,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['display_name'], 'Week 1') self.assertEqual(xblock_info['display_name'], 'Week 1')
self.assertTrue(xblock_info['published']) self.assertTrue(xblock_info['published'])
self.assertIsNone(xblock_info.get('edited_by', None)) self.assertIsNone(xblock_info.get('edited_by', None))
self.assertEqual(xblock_info['course_graders'], '["Homework", "Lab", "Midterm Exam", "Final Exam"]') self.assertEqual(xblock_info['course_graders'], ['Homework', 'Lab', 'Midterm Exam', 'Final Exam'])
self.assertEqual(xblock_info['start'], '2030-01-01T00:00:00Z') self.assertEqual(xblock_info['start'], '2030-01-01T00:00:00Z')
self.assertEqual(xblock_info['graded'], False) self.assertEqual(xblock_info['graded'], False)
self.assertEqual(xblock_info['due'], None) self.assertEqual(xblock_info['due'], None)
......
...@@ -65,7 +65,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u ...@@ -65,7 +65,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u
published: true, published: true,
edited_on: 'Jul 02, 2014 at 20:56 UTC', edited_on: 'Jul 02, 2014 at 20:56 UTC',
edited_by: 'MockUser', edited_by: 'MockUser',
course_graders: '["Lab", "Howework"]', course_graders: ["Lab", "Howework"],
has_explicit_staff_lock: false, has_explicit_staff_lock: false,
child_info: { child_info: {
category: 'vertical', category: 'vertical',
......
...@@ -410,7 +410,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', ...@@ -410,7 +410,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
getContext: function () { getContext: function () {
return { return {
graderTypes: JSON.parse(this.model.get('course_graders')) graderTypes: this.model.get('course_graders')
}; };
} }
}); });
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.template.defaultfilters import escapejs from openedx.core.lib.js_utils import (
import json escape_json_dumps, escape_js_string
)
%> %>
<!doctype html> <!doctype html>
<!--[if lte IE 9]><html class="ie9 lte9" lang="${LANGUAGE_CODE}"><![endif]--> <!--[if lte IE 9]><html class="ie9 lte9" lang="${LANGUAGE_CODE}"><![endif]-->
...@@ -41,7 +42,7 @@ import json ...@@ -41,7 +42,7 @@ import json
<a class="nav-skip" href="#content">${_("Skip to main content")}</a> <a class="nav-skip" href="#content">${_("Skip to main content")}</a>
<script type="text/javascript"> <script type="text/javascript">
window.baseUrl = ${json.dumps(settings.STATIC_URL)}; window.baseUrl = "${escape_js_string(settings.STATIC_URL) | n}";
var require = {baseUrl: window.baseUrl}; var require = {baseUrl: window.baseUrl};
</script> </script>
<script type="text/javascript" src="${static.url("js/vendor/require.js")}"></script> <script type="text/javascript" src="${static.url("js/vendor/require.js")}"></script>
...@@ -79,14 +80,14 @@ import json ...@@ -79,14 +80,14 @@ import json
% if context_course: % if context_course:
require(['js/factories/course'], function(CourseFactory) { require(['js/factories/course'], function(CourseFactory) {
CourseFactory({ CourseFactory({
id: "${context_course.id | escapejs}", id: "${escape_js_string(context_course.id) | n}",
name: "${context_course.display_name_with_default | h}", name: "${context_course.display_name_with_default | h}",
url_name: "${context_course.location.name | h}", url_name: "${context_course.location.name | h}",
org: "${context_course.location.org | h}", org: "${context_course.location.org | h}",
num: "${context_course.location.course | h}", num: "${context_course.location.course | h}",
display_course_number: "${_(context_course.display_coursenumber)}", display_course_number: "${_(context_course.display_coursenumber)}",
revision: "${context_course.location.revision | h}", revision: "${context_course.location.revision | h}",
self_paced: ${json.dumps(context_course.self_paced)} self_paced: ${escape_json_dumps(context_course.self_paced) | n}
}); });
}); });
% endif % endif
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
<%def name="online_help_token()"><% return "certificates" %></%def> <%def name="online_help_token()"><% return "certificates" %></%def>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
import json
from contentstore import utils from contentstore import utils
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title">${_("Course Certificates")}</%block> <%block name="title">${_("Course Certificates")}</%block>
...@@ -30,7 +30,7 @@ CMS.User.isGlobalStaff = '${is_global_staff}'=='True' ? true : false; ...@@ -30,7 +30,7 @@ CMS.User.isGlobalStaff = '${is_global_staff}'=='True' ? true : false;
<%block name="requirejs"> <%block name="requirejs">
require(["js/certificates/factories/certificates_page_factory"], function(CertificatesPageFactory) { require(["js/certificates/factories/certificates_page_factory"], function(CertificatesPageFactory) {
CertificatesPageFactory(${json.dumps(certificates)}, "${certificate_url}", "${course_outline_url}", ${json.dumps(course_modes)}, ${json.dumps(certificate_web_view_url)}, ${json.dumps(is_active)}, ${json.dumps(certificate_activation_handler_url)} ); CertificatesPageFactory(${escape_json_dumps(certificates) | n}, "${certificate_url}", "${course_outline_url}", ${escape_json_dumps(course_modes) | n}, ${escape_json_dumps(certificate_web_view_url) | n}, ${escape_json_dumps(is_active) | n}, ${escape_json_dumps(certificate_activation_handler_url) | n} );
}); });
</%block> </%block>
......
...@@ -8,10 +8,9 @@ else: ...@@ -8,10 +8,9 @@ else:
%> %>
</%def> </%def>
<%! <%!
import json
from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title">${xblock.display_name_with_default} ${xblock_type_display_name(xblock) | h}</%block> <%block name="title">${xblock.display_name_with_default} ${xblock_type_display_name(xblock) | h}</%block>
<%block name="bodyclass">is-signedin course container view-container</%block> <%block name="bodyclass">is-signedin course container view-container</%block>
...@@ -33,10 +32,11 @@ from django.utils.translation import ugettext as _ ...@@ -33,10 +32,11 @@ from django.utils.translation import ugettext as _
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/container"], function(ContainerFactory) { require(["js/factories/container"], function(ContainerFactory) {
ContainerFactory( ContainerFactory(
${component_templates | n}, ${json.dumps(xblock_info) | n}, ${ escape_json_dumps(component_templates) | n },
${ escape_json_dumps(xblock_info) | n },
"${action | h}", "${action | h}",
{ {
isUnitPage: ${json.dumps(is_unit_page)}, isUnitPage: ${ escape_json_dumps(is_unit_page) | n },
canEdit: true canEdit: true
} }
); );
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
<%def name="online_help_token()"><% return "updates" %></%def> <%def name="online_help_token()"><% return "updates" %></%def>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.template.defaultfilters import escapejs from django.template.defaultfilters import escapejs
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
## TODO decode course # from context_course into title. ## TODO decode course # from context_course into title.
...@@ -26,7 +26,7 @@ from django.template.defaultfilters import escapejs ...@@ -26,7 +26,7 @@ from django.template.defaultfilters import escapejs
"${updates_url}", "${updates_url}",
"${handouts_locator | escapejs}", "${handouts_locator | escapejs}",
"${base_asset_url}", "${base_asset_url}",
${json.dumps(push_notification_enabled)} ${escape_json_dumps(push_notification_enabled) | n}
); );
}); });
</%block> </%block>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%def name="online_help_token()"><% return "outline" %></%def> <%def name="online_help_token()"><% return "outline" %></%def>
<%! <%!
import json
import logging import logging
from util.date_utils import get_default_time_display from util.date_utils import get_default_time_display
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from contentstore.utils import reverse_usage_url from contentstore.utils import reverse_usage_url
from microsite_configuration import microsite from microsite_configuration import microsite
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
...@@ -16,7 +16,7 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration ...@@ -16,7 +16,7 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/outline"], function (OutlineFactory) { require(["js/factories/outline"], function (OutlineFactory) {
OutlineFactory(${json.dumps(course_structure) | n}, ${json.dumps(initial_state) | n}); OutlineFactory(${escape_json_dumps(course_structure) | n}, ${escape_json_dumps(initial_state) | n});
}); });
</%block> </%block>
......
...@@ -11,7 +11,7 @@ else: ...@@ -11,7 +11,7 @@ else:
<%! <%!
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
import json from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title"> <%block name="title">
%if library: %if library:
...@@ -24,11 +24,11 @@ else: ...@@ -24,11 +24,11 @@ else:
<%block name="requirejs"> <%block name="requirejs">
% if in_err: % if in_err:
var hasUnit = ${json.dumps(bool(unit))}, var hasUnit = ${escape_json_dumps(bool(unit)) | n},
editUnitUrl = "${edit_unit_url or ""}", editUnitUrl = "${edit_unit_url or ""}",
courselikeHomeUrl = "${courselike_home_url or ""}", courselikeHomeUrl = "${courselike_home_url or ""}",
is_library = ${json.dumps(library)} is_library = ${escape_json_dumps(library) | n}
errMsg = ${json.dumps(raw_err_msg or "")}; errMsg = ${escape_json_dumps(raw_err_msg or "") | n};
require(["js/factories/export"], function(ExportFactory) { require(["js/factories/export"], function(ExportFactory) {
ExportFactory(hasUnit, editUnitUrl, courselikeHomeUrl, is_library, errMsg); ExportFactory(hasUnit, editUnitUrl, courselikeHomeUrl, is_library, errMsg);
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
<%def name="experiment_group_configurations_help_token()"><% return "group_configurations" %></%def> <%def name="experiment_group_configurations_help_token()"><% return "group_configurations" %></%def>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
import json
from contentstore import utils from contentstore import utils
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title">${_("Group Configurations")}</%block> <%block name="title">${_("Group Configurations")}</%block>
...@@ -21,7 +21,7 @@ from django.utils.translation import ugettext as _ ...@@ -21,7 +21,7 @@ from django.utils.translation import ugettext as _
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/group_configurations"], function(GroupConfigurationsFactory) { require(["js/factories/group_configurations"], function(GroupConfigurationsFactory) {
GroupConfigurationsFactory(${json.dumps(should_show_experiment_groups)}, ${json.dumps(experiment_group_configurations)}, ${json.dumps(content_group_configuration)}, "${group_configuration_url}", "${course_outline_url}"); GroupConfigurationsFactory(${escape_json_dumps(should_show_experiment_groups) | n}, ${escape_json_dumps(experiment_group_configurations) | n}, ${escape_json_dumps(content_group_configuration) | n}, "${group_configuration_url}", "${course_outline_url}");
}); });
</%block> </%block>
......
...@@ -10,7 +10,7 @@ else: ...@@ -10,7 +10,7 @@ else:
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
import json from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title"> <%block name="title">
%if library: %if library:
...@@ -239,6 +239,6 @@ else: ...@@ -239,6 +239,6 @@ else:
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/import"], function(ImportFactory) { require(["js/factories/import"], function(ImportFactory) {
ImportFactory("${import_status_url}", ${json.dumps(library)}); ImportFactory("${import_status_url}", ${escape_json_dumps(library) | n});
}); });
</%block> </%block>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%def name="online_help_token()"><% return "content_libraries" %></%def> <%def name="online_help_token()"><% return "content_libraries" %></%def>
<%! <%!
import json
from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title">${context_library.display_name_with_default} ${xblock_type_display_name(context_library)}</%block> <%block name="title">${context_library.display_name_with_default} ${xblock_type_display_name(context_library)}</%block>
<%block name="bodyclass">is-signedin course container view-container view-library</%block> <%block name="bodyclass">is-signedin course container view-container view-library</%block>
...@@ -25,8 +24,8 @@ from django.utils.translation import ugettext as _ ...@@ -25,8 +24,8 @@ from django.utils.translation import ugettext as _
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/library"], function(LibraryFactory) { require(["js/factories/library"], function(LibraryFactory) {
LibraryFactory( LibraryFactory(
${component_templates | n}, ${escape_json_dumps(component_templates) | n},
${json.dumps(xblock_info) | n}, ${escape_json_dumps(xblock_info) | n},
{ {
isUnitPage: false, isUnitPage: false,
page_size: 10, page_size: 10,
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! <%!
import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%def name="online_help_token()"><% return "team_course" %></%def> <%def name="online_help_token()"><% return "team_course" %></%def>
<%block name="title">${_("Course Team Settings")}</%block> <%block name="title">${_("Course Team Settings")}</%block>
...@@ -115,7 +115,7 @@ from django.core.urlresolvers import reverse ...@@ -115,7 +115,7 @@ from django.core.urlresolvers import reverse
require(["js/factories/manage_users"], function(ManageCourseUsersFactory) { require(["js/factories/manage_users"], function(ManageCourseUsersFactory) {
ManageCourseUsersFactory( ManageCourseUsersFactory(
"${context_course.display_name | h}", "${context_course.display_name | h}",
${json.dumps(users)}, ${escape_json_dumps(users) | n},
"${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': unicode(context_course.id), 'email': '@@EMAIL@@'})}", "${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': unicode(context_course.id), 'email': '@@EMAIL@@'})}",
${ request.user.id }, ${ request.user.id },
${str(allow_actions).lower()} ${str(allow_actions).lower()}
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! <%!
import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%def name="online_help_token()"><% return "team_library" %></%def> <%def name="online_help_token()"><% return "team_library" %></%def>
<%block name="title">${_("Library User Access")}</%block> <%block name="title">${_("Library User Access")}</%block>
...@@ -108,7 +108,7 @@ from django.core.urlresolvers import reverse ...@@ -108,7 +108,7 @@ from django.core.urlresolvers import reverse
require(["js/factories/manage_users_lib"], function(ManageLibraryUsersFactory) { require(["js/factories/manage_users_lib"], function(ManageLibraryUsersFactory) {
ManageLibraryUsersFactory( ManageLibraryUsersFactory(
"${context_library.display_name_with_default | h}", "${context_library.display_name_with_default | h}",
${json.dumps(users)}, ${escape_json_dumps(users) | n},
"${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': library_key, 'email': '@@EMAIL@@'})}", "${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': library_key, 'email': '@@EMAIL@@'})}",
${ request.user.id }, ${ request.user.id },
${str(allow_actions).lower()} ${str(allow_actions).lower()}
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
import json
import urllib import urllib
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from contentstore import utils from contentstore import utils
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="header_extras"> <%block name="header_extras">
...@@ -31,7 +31,7 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}'; ...@@ -31,7 +31,7 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/settings"], function(SettingsFactory) { require(["js/factories/settings"], function(SettingsFactory) {
SettingsFactory("${details_url}", ${json.dumps(show_min_grade_warning)}); SettingsFactory("${details_url}", ${escape_json_dumps(show_min_grade_warning) | n});
}); });
</%block> </%block>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<%! <%!
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from contentstore import utils from contentstore import utils
from openedx.core.lib.json_utils import escape_json_dumps from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title">${_("Advanced Settings")}</%block> <%block name="title">${_("Advanced Settings")}</%block>
<%block name="bodyclass">is-signedin course advanced view-settings</%block> <%block name="bodyclass">is-signedin course advanced view-settings</%block>
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
import json import json
from contentstore import utils from contentstore import utils
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from models.settings.course_details import CourseSettingsEncoder
%> %>
<%block name="header_extras"> <%block name="header_extras">
...@@ -23,7 +25,7 @@ ...@@ -23,7 +25,7 @@
</%block> </%block>
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/settings_graders"], function(SettingsGradersFactory) { require(["js/factories/settings_graders"], function(SettingsGradersFactory) {
SettingsGradersFactory(_.extend(${course_details|n}, {is_credit_course: ${json.dumps(is_credit_course)}}), "${grading_url}"); SettingsGradersFactory(_.extend(${escape_json_dumps(course_details, cls=CourseSettingsEncoder) | n}, {is_credit_course: ${escape_json_dumps(is_credit_course) | n}}), "${grading_url}");
}); });
</%block> </%block>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from contentstore.views.helpers import xblock_studio_url from contentstore.views.helpers import xblock_studio_url
from contentstore.utils import is_visible_to_specific_content_groups from contentstore.utils import is_visible_to_specific_content_groups
import json from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<% <%
xblock_url = xblock_studio_url(xblock) xblock_url = xblock_studio_url(xblock)
...@@ -10,7 +10,7 @@ show_inline = xblock.has_children and not xblock_url ...@@ -10,7 +10,7 @@ show_inline = xblock.has_children and not xblock_url
section_class = "level-nesting" if show_inline else "level-element" section_class = "level-nesting" if show_inline else "level-element"
collapsible_class = "is-collapsible" if xblock.has_children else "" collapsible_class = "is-collapsible" if xblock.has_children else ""
label = xblock.display_name_with_default or xblock.scope_ids.block_type label = xblock.display_name_with_default or xblock.scope_ids.block_type
messages = json.dumps(xblock.validate().to_json()) messages = xblock.validate().to_json()
%> %>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
...@@ -24,7 +24,7 @@ messages = json.dumps(xblock.validate().to_json()) ...@@ -24,7 +24,7 @@ messages = json.dumps(xblock.validate().to_json())
<script> <script>
require(["jquery", "js/factories/xblock_validation"], function($, XBlockValidationFactory) { require(["jquery", "js/factories/xblock_validation"], function($, XBlockValidationFactory) {
XBlockValidationFactory( XBlockValidationFactory(
${messages}, ${escape_json_dumps(messages) | n},
$.parseJSON("${bool(xblock_url)}".toLowerCase()), // xblock_url will be None or a string $.parseJSON("${bool(xblock_url)}".toLowerCase()), // xblock_url will be None or a string
$.parseJSON("${bool(is_root)}".toLowerCase()), // is_root will be None or a boolean $.parseJSON("${bool(is_root)}".toLowerCase()), // is_root will be None or a boolean
$('div.xblock-validation-messages[data-locator="${xblock.location | h}"]') $('div.xblock-validation-messages[data-locator="${xblock.location | h}"]')
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<%def name="online_help_token()"><% return "textbooks" %></%def> <%def name="online_help_token()"><% return "textbooks" %></%def>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="title">${_("Textbooks")}</%block> <%block name="title">${_("Textbooks")}</%block>
...@@ -28,7 +28,7 @@ CMS.URL.LMS_BASE = "${settings.LMS_BASE}" ...@@ -28,7 +28,7 @@ CMS.URL.LMS_BASE = "${settings.LMS_BASE}"
</%block> </%block>
<%block name="requirejs"> <%block name="requirejs">
require(["js/factories/textbooks"], function(TextbooksFactory) { require(["js/factories/textbooks"], function(TextbooksFactory) {
TextbooksFactory(${json.dumps(textbooks)}); TextbooksFactory(${escape_json_dumps(textbooks) | n});
}); });
</%block> </%block>
......
...@@ -9,11 +9,10 @@ from mock import patch ...@@ -9,11 +9,10 @@ from mock import patch
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from third_party_auth.tasks import fetch_saml_metadata from third_party_auth.tasks import fetch_saml_metadata
from third_party_auth.tests import testutil from third_party_auth.tests import testutil
from openedx.core.lib.js_utils import escape_json_dumps
TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth' TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth'
...@@ -190,7 +189,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): ...@@ -190,7 +189,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
response = self.client.get(self.login_page_url) response = self.client.get(self.login_page_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content) self.assertIn("TestShib", response.content)
self.assertIn(json.dumps(TPA_TESTSHIB_LOGIN_URL, cls=EscapedEdxJSONEncoder), response.content) self.assertIn(escape_json_dumps(TPA_TESTSHIB_LOGIN_URL), response.content)
return response return response
def _check_register_page(self): def _check_register_page(self):
...@@ -198,7 +197,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): ...@@ -198,7 +197,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
response = self.client.get(self.register_page_url) response = self.client.get(self.register_page_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content) self.assertIn("TestShib", response.content)
self.assertIn(json.dumps(TPA_TESTSHIB_REGISTER_URL, cls=EscapedEdxJSONEncoder), response.content) self.assertIn(escape_json_dumps(TPA_TESTSHIB_REGISTER_URL), response.content)
return response return response
def _configure_testshib_provider(self, **kwargs): def _configure_testshib_provider(self, **kwargs):
......
...@@ -19,7 +19,7 @@ from django.test.client import RequestFactory ...@@ -19,7 +19,7 @@ from django.test.client import RequestFactory
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder from openedx.core.lib.js_utils import escape_json_dumps
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from student_account.views import account_settings_context from student_account.views import account_settings_context
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
...@@ -385,7 +385,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi ...@@ -385,7 +385,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
"finishAuthUrl": finish_auth_url, "finishAuthUrl": finish_auth_url,
"errorMessage": None, "errorMessage": None,
} }
auth_info = json.dumps(auth_info, cls=EscapedEdxJSONEncoder) auth_info = escape_json_dumps(auth_info)
expected_data = '"third_party_auth": {auth_info}'.format( expected_data = '"third_party_auth": {auth_info}'.format(
auth_info=auth_info auth_info=auth_info
......
## mako ## mako
<%! import json %> <%! import json %>
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<%! from openedx.core.lib.json_utils import EscapedEdxJSONEncoder %> <%! from openedx.core.lib.js_utils import escape_json_dumps %>
<%namespace name='static' file='/static_content.html'/> <%namespace name='static' file='/static_content.html'/>
<%inherit file="/main.html" /> <%inherit file="/main.html" />
...@@ -34,8 +34,8 @@ ...@@ -34,8 +34,8 @@
<%static:require_module module_name="teams/js/teams_tab_factory" class_name="TeamsTabFactory"> <%static:require_module module_name="teams/js/teams_tab_factory" class_name="TeamsTabFactory">
TeamsTabFactory({ TeamsTabFactory({
courseID: '${ unicode(course.id) }', courseID: '${ unicode(course.id) }',
topics: ${ json.dumps(topics, cls=EscapedEdxJSONEncoder) }, topics: ${ escape_json_dumps(topics) | n },
userInfo: ${ json.dumps(user_info, cls=EscapedEdxJSONEncoder) }, userInfo: ${ escape_json_dumps(user_info) | n },
topicUrl: '${ topic_url }', topicUrl: '${ topic_url }',
topicsUrl: '${ topics_url }', topicsUrl: '${ topics_url }',
teamsUrl: '${ teams_url }', teamsUrl: '${ teams_url }',
...@@ -44,8 +44,8 @@ ...@@ -44,8 +44,8 @@
teamMembershipDetailUrl: '${ team_membership_detail_url }', teamMembershipDetailUrl: '${ team_membership_detail_url }',
myTeamsUrl: '${ my_teams_url }', myTeamsUrl: '${ my_teams_url }',
maxTeamSize: ${ course.teams_max_size }, maxTeamSize: ${ course.teams_max_size },
languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) }, languages: ${ escape_json_dumps(languages) | n },
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) }, countries: ${ escape_json_dumps(countries) | n },
teamsBaseUrl: '${ teams_base_url }' teamsBaseUrl: '${ teams_base_url }'
}); });
</%static:require_module> </%static:require_module>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import json import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from microsite_configuration import microsite from microsite_configuration import microsite
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<% <%
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
% endfor % endfor
<%static:require_module module_name="js/discovery/discovery_factory" class_name="DiscoveryFactory"> <%static:require_module module_name="js/discovery/discovery_factory" class_name="DiscoveryFactory">
DiscoveryFactory( DiscoveryFactory(
${json.dumps(course_discovery_meanings, cls=EscapedEdxJSONEncoder)}, ${ escape_json_dumps(course_discovery_meanings) | n },
getParameterByName('search_query') getParameterByName('search_query')
); );
</%static:require_module> </%static:require_module>
......
<%! <%!
import json import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%namespace name='static' file='/static_content.html'/> <%namespace name='static' file='/static_content.html'/>
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<%block name="js_extra"> <%block name="js_extra">
<%static:require_module module_name="js/student_account/logistration_factory" class_name="LogistrationFactory"> <%static:require_module module_name="js/student_account/logistration_factory" class_name="LogistrationFactory">
var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) }; var options = ${ escape_json_dumps(data) | n };
LogistrationFactory(options); LogistrationFactory(options);
</%static:require_module> </%static:require_module>
</%block> </%block>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import json import json
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder from openedx.core.lib.js_utils import escape_json_dumps
%> %>
<%block name="pagetitle">${_("Learner Profile")}</%block> <%block name="pagetitle">${_("Learner Profile")}</%block>
...@@ -24,7 +24,7 @@ from openedx.core.lib.json_utils import EscapedEdxJSONEncoder ...@@ -24,7 +24,7 @@ from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
<%block name="js_extra"> <%block name="js_extra">
<%static:require_module module_name="js/student_profile/views/learner_profile_factory" class_name="LearnerProfileFactory"> <%static:require_module module_name="js/student_profile/views/learner_profile_factory" class_name="LearnerProfileFactory">
var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) }; var options = ${ escape_json_dumps(data) | n };
LearnerProfileFactory(options); LearnerProfileFactory(options);
</%static:require_module> </%static:require_module>
</%block> </%block>
""" """
Utilities for dealing with JSON. Utilities for dealing with Javascript and JSON.
""" """
import json import json
import simplejson from django.template.defaultfilters import escapejs
from mako.filters import decode
from xmodule.modulestore import EdxJSONEncoder from xmodule.modulestore import EdxJSONEncoder
class EscapedEdxJSONEncoder(EdxJSONEncoder): def _escape_json_for_html(json_string):
"""
Class for encoding edx JSON which will be printed inline into HTML
templates.
"""
def encode(self, obj):
"""
Encodes JSON that is safe to be embedded in HTML.
"""
return simplejson.dumps(
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. Escape JSON that is safe to be embedded in HTML.
This implementation is based on escaping performed in simplejson.JSONEncoderForHTML. This implementation is based on escaping performed in simplejson.JSONEncoderForHTML.
Arguments: Arguments:
json_str (str): The JSON string to be escaped json_string (string): The JSON string to be escaped
Returns: Returns:
(str) Escaped JSON that is safe to be embedded in HTML. (string) Escaped JSON that is safe to be embedded in HTML.
""" """
json_str = json_str.replace("&", "\\u0026") json_string = json_string.replace("&", "\\u0026")
json_str = json_str.replace(">", "\\u003e") json_string = json_string.replace(">", "\\u003e")
json_str = json_str.replace("<", "\\u003c") json_string = json_string.replace("<", "\\u003c")
return json_str return json_string
def escape_json_dumps(obj, cls=EdxJSONEncoder): def escape_json_dumps(obj, cls=EdxJSONEncoder):
""" """
JSON dumps encoded JSON that is safe to be embedded in HTML. JSON dumps and escapes JSON that is safe to be embedded in HTML.
Usage: Usage:
Can be used inside a Mako template inside a <SCRIPT> as follows: Can be used inside a Mako template inside a <SCRIPT> as follows:
var my_json = ${escape_json_dumps(my_object) | n} var my_json = ${escape_json_dumps(my_object) | n}
Use the "n" Mako filter above. It is possible that the Use the "n" Mako filter above. It is possible that the
default filter may include html encoding in the future, and default filter may include html escaping in the future, and
we must make sure to get the proper escaping. we must make sure to get the proper escaping.
Ensure ascii in json.dumps (ensure_ascii=True) allows safe skipping of Mako's Ensure ascii in json.dumps (ensure_ascii=True) allows safe skipping of Mako's
...@@ -62,9 +46,38 @@ def escape_json_dumps(obj, cls=EdxJSONEncoder): ...@@ -62,9 +46,38 @@ def escape_json_dumps(obj, cls=EdxJSONEncoder):
cls (class): The JSON encoder class (defaults to EdxJSONEncoder) cls (class): The JSON encoder class (defaults to EdxJSONEncoder)
Returns: Returns:
str: Escaped encoded JSON (string) Escaped encoded JSON
""" """
encoded_json = json.dumps(obj, ensure_ascii=True, cls=cls) encoded_json = json.dumps(obj, ensure_ascii=True, cls=cls)
encoded_json = _escape_json_for_html(encoded_json) encoded_json = _escape_json_for_html(encoded_json)
return encoded_json return encoded_json
def escape_js_string(js_string):
"""
Escape a javascript string that is safe to be embedded in HTML.
Usage:
Can be used inside a Mako template inside a <SCRIPT> as follows:
var my_js_string = "${escape_js_string(my_js_string) | n}"
Must include the surrounding quotes for the string.
Use the "n" Mako filter above. It is possible that the
default filter may include html escaping in the future, and
we must make sure to get the proper escaping.
Mako's default filter decode.utf8 is applied here since this default
filter is skipped in the Mako template with "n".
Arguments:
js_string (string): The javascript string to be escaped
Returns:
(string) Escaped javascript as unicode
"""
js_string = decode.utf8(js_string)
js_string = escapejs(js_string)
return js_string
""" """
Tests for json_utils.py Tests for js_utils.py
""" """
import json import json
from unittest import TestCase from unittest import TestCase
from openedx.core.lib.json_utils import ( from openedx.core.lib.js_utils import (
escape_json_dumps, EscapedEdxJSONEncoder escape_json_dumps, escape_js_string
) )
class TestJsonUtils(TestCase): class TestJSUtils(TestCase):
""" """
Test JSON Utils Test JS utils
""" """
class NoDefaultEncoding(object): class NoDefaultEncoding(object):
...@@ -28,16 +28,6 @@ class TestJsonUtils(TestCase): ...@@ -28,16 +28,6 @@ class TestJsonUtils(TestCase):
def default(self, noDefaultEncodingObj): def default(self, noDefaultEncodingObj):
return noDefaultEncodingObj.value.replace("<script>", "sample-encoder-was-here") return noDefaultEncodingObj.value.replace("<script>", "sample-encoder-was-here")
def test_escapes_forward_slashes(self):
"""
Verify that we escape forward slashes with backslashes.
"""
malicious_json = {'</script><script>alert("hello, ");</script>': '</script><script>alert("world!");</script>'}
self.assertNotIn(
'</script>',
json.dumps(malicious_json, cls=EscapedEdxJSONEncoder)
)
def test_escape_json_dumps_escapes_unsafe_html(self): def test_escape_json_dumps_escapes_unsafe_html(self):
""" """
Test escape_json_dumps properly escapes &, <, and >. Test escape_json_dumps properly escapes &, <, and >.
...@@ -70,3 +60,15 @@ class TestJsonUtils(TestCase): ...@@ -70,3 +60,15 @@ class TestJsonUtils(TestCase):
encoded_json = escape_json_dumps(malicious_json, cls=self.SampleJSONEncoder) encoded_json = escape_json_dumps(malicious_json, cls=self.SampleJSONEncoder)
self.assertEquals(expected_custom_encoded_json, encoded_json) self.assertEquals(expected_custom_encoded_json, encoded_json)
def test_escape_js_string_escapes_unsafe_html(self):
"""
Test escape_js_string escapes &, <, and >, as well as returns a unicode type
"""
malicious_js_string = "</script><script>alert('hello, ');</script>"
expected_escaped_js_string = unicode(
r"\u003C/script\u003E\u003Cscript\u003Ealert(\u0027hello, \u0027)\u003B\u003C/script\u003E"
)
escaped_js_string = escape_js_string(malicious_js_string)
self.assertEquals(expected_escaped_js_string, escaped_js_string)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment