From 2cb7b6893afb51db5c86837347ae55671fdce1f1 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Mon, 3 Jun 2019 09:54:03 -0700 Subject: [PATCH 1/3] Escape variables in mkdocs data --- .../doc_builder/templates/doc_builder/data.js.tmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl index 29ab61b0e65..6b2b673a604 100644 --- a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl @@ -1,10 +1,10 @@ var READTHEDOCS_DATA = {{ data_json|safe }} // Old variables -var doc_version = "{{ current_version }}"; -var doc_slug = "{{ slug }}"; -var page_name = "{{ pagename }}"; -var html_theme = "{{ html_theme }}"; +var doc_version = "{{ current_version|escapejs }}"; +var doc_slug = "{{ slug|escapejs }}"; +var page_name = "{{ pagename|escapejs }}"; +var html_theme = "{{ html_theme|escapejs }}"; // mkdocs_page_input_path is only defined on the RTD mkdocs theme but it isn't // available on all pages (e.g. missing in search result) From d7910fca0a7eacda75476940647356a86b549879 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Mon, 3 Jun 2019 11:58:30 -0700 Subject: [PATCH 2/3] Properly escape JSON in a template --- readthedocs/core/templatetags/core_tags.py | 29 +++++++++++++++++-- readthedocs/doc_builder/backends/mkdocs.py | 4 +-- .../templates/doc_builder/data.js.tmpl | 5 +++- readthedocs/rtd_tests/tests/test_core_tags.py | 11 +++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index d37f4d2671d..69e5f58946d 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- - """Template tags for core app.""" import hashlib +import json from urllib.parse import urlencode from django import template from django.conf import settings +from django.core.serializers.json import DjangoJSONEncoder from django.utils.encoding import force_bytes, force_text from django.utils.safestring import mark_safe @@ -112,3 +112,28 @@ def key(d, key_name): @register.simple_tag def readthedocs_version(): return __version__ + + +@register.filter +def escapejson(data, indent=None): + """ + Escape JSON correctly for inclusion in Django templates + + This code was mostly taken from Django's implementation + https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#json-script + + After upgrading to Django 2.1+, we could replace this with Django's implementation + although the inputs and outputs are a bit different. + + Example: + + var jsvar = {{ dictionary_value | escapejson }} + """ + if indent: + indent = int(indent) + _json_script_escapes = { + ord('>'): '\\u003E', + ord('<'): '\\u003C', + ord('&'): '\\u0026', + } + return mark_safe(json.dumps(data, cls=DjangoJSONEncoder, indent=indent).translate(_json_script_escapes)) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 3cb28f791e3..d87df6ea0db 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -226,9 +226,9 @@ def generate_rtd_data(self, docs_dir, mkdocs_config): 'global_analytics_code': settings.GLOBAL_ANALYTICS_CODE, 'user_analytics_code': analytics_code, } - data_json = json.dumps(readthedocs_data, indent=4) + data_ctx = { - 'data_json': data_json, + 'readthedocs_data': readthedocs_data, 'current_version': readthedocs_data['version'], 'slug': readthedocs_data['project'], 'html_theme': readthedocs_data['theme'], diff --git a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl index 6b2b673a604..6b3c407263c 100644 --- a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl @@ -1,4 +1,7 @@ -var READTHEDOCS_DATA = {{ data_json|safe }} +{% load core_tags %} + + +var READTHEDOCS_DATA = {{ readthedocs_data|escapejson:4 }} // Old variables var doc_version = "{{ current_version|escapejs }}"; diff --git a/readthedocs/rtd_tests/tests/test_core_tags.py b/readthedocs/rtd_tests/tests/test_core_tags.py index 3b9abd547c2..48c5fa85bf7 100644 --- a/readthedocs/rtd_tests/tests/test_core_tags.py +++ b/readthedocs/rtd_tests/tests/test_core_tags.py @@ -110,3 +110,14 @@ def test_restructured_text_invalid(self): ) result = core_tags.restructuredtext(value) self.assertEqual(result, value) + + def test_escapejson(self): + tests = ( + ({}, '{}'), + ({'a': 'b'}, '{"a": "b"}'), + ({"'; //": '""'}, '{"\'; //": "\\"\\""}'), + ({"": ''}, '{"\\u003Cscript\\u003Ealert(\'hi\')\\u003C/script\\u003E": ""}'), + ) + + for in_value, out_value in tests: + self.assertEqual(core_tags.escapejson(in_value), out_value) From db27291410d8da0c0adebfedd227903084c24567 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Mon, 3 Jun 2019 16:30:15 -0700 Subject: [PATCH 3/3] Add link to implementation --- readthedocs/core/templatetags/core_tags.py | 1 + 1 file changed, 1 insertion(+) diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py index 69e5f58946d..4f33c67bda9 100644 --- a/readthedocs/core/templatetags/core_tags.py +++ b/readthedocs/core/templatetags/core_tags.py @@ -121,6 +121,7 @@ def escapejson(data, indent=None): This code was mostly taken from Django's implementation https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#json-script + https://github.com/django/django/blob/2.2.2/django/utils/html.py#L74-L92 After upgrading to Django 2.1+, we could replace this with Django's implementation although the inputs and outputs are a bit different.