Skip to content

Commit 133d957

Browse files
authored
Merge pull request #5759 from rtfd/davidfischer/mkdocs-escape-vars
Escape variables in mkdocs data
2 parents 9acd72a + db27291 commit 133d957

File tree

4 files changed

+49
-9
lines changed

4 files changed

+49
-9
lines changed

readthedocs/core/templatetags/core_tags.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""Template tags for core app."""
42

53
import hashlib
4+
import json
65
from urllib.parse import urlencode
76

87
from django import template
98
from django.conf import settings
9+
from django.core.serializers.json import DjangoJSONEncoder
1010
from django.utils.encoding import force_bytes, force_text
1111
from django.utils.safestring import mark_safe
1212

@@ -112,3 +112,29 @@ def key(d, key_name):
112112
@register.simple_tag
113113
def readthedocs_version():
114114
return __version__
115+
116+
117+
@register.filter
118+
def escapejson(data, indent=None):
119+
"""
120+
Escape JSON correctly for inclusion in Django templates
121+
122+
This code was mostly taken from Django's implementation
123+
https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#json-script
124+
https://github.com/django/django/blob/2.2.2/django/utils/html.py#L74-L92
125+
126+
After upgrading to Django 2.1+, we could replace this with Django's implementation
127+
although the inputs and outputs are a bit different.
128+
129+
Example:
130+
131+
var jsvar = {{ dictionary_value | escapejson }}
132+
"""
133+
if indent:
134+
indent = int(indent)
135+
_json_script_escapes = {
136+
ord('>'): '\\u003E',
137+
ord('<'): '\\u003C',
138+
ord('&'): '\\u0026',
139+
}
140+
return mark_safe(json.dumps(data, cls=DjangoJSONEncoder, indent=indent).translate(_json_script_escapes))

readthedocs/doc_builder/backends/mkdocs.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,9 @@ def generate_rtd_data(self, docs_dir, mkdocs_config):
226226
'global_analytics_code': settings.GLOBAL_ANALYTICS_CODE,
227227
'user_analytics_code': analytics_code,
228228
}
229-
data_json = json.dumps(readthedocs_data, indent=4)
229+
230230
data_ctx = {
231-
'data_json': data_json,
231+
'readthedocs_data': readthedocs_data,
232232
'current_version': readthedocs_data['version'],
233233
'slug': readthedocs_data['project'],
234234
'html_theme': readthedocs_data['theme'],

readthedocs/doc_builder/templates/doc_builder/data.js.tmpl

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
var READTHEDOCS_DATA = {{ data_json|safe }}
1+
{% load core_tags %}
2+
3+
4+
var READTHEDOCS_DATA = {{ readthedocs_data|escapejson:4 }}
25

36
// Old variables
4-
var doc_version = "{{ current_version }}";
5-
var doc_slug = "{{ slug }}";
6-
var page_name = "{{ pagename }}";
7-
var html_theme = "{{ html_theme }}";
7+
var doc_version = "{{ current_version|escapejs }}";
8+
var doc_slug = "{{ slug|escapejs }}";
9+
var page_name = "{{ pagename|escapejs }}";
10+
var html_theme = "{{ html_theme|escapejs }}";
811

912
// mkdocs_page_input_path is only defined on the RTD mkdocs theme but it isn't
1013
// available on all pages (e.g. missing in search result)

readthedocs/rtd_tests/tests/test_core_tags.py

+11
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,14 @@ def test_restructured_text_invalid(self):
110110
)
111111
result = core_tags.restructuredtext(value)
112112
self.assertEqual(result, value)
113+
114+
def test_escapejson(self):
115+
tests = (
116+
({}, '{}'),
117+
({'a': 'b'}, '{"a": "b"}'),
118+
({"'; //": '""'}, '{"\'; //": "\\"\\""}'),
119+
({"<script>alert('hi')</script>": ''}, '{"\\u003Cscript\\u003Ealert(\'hi\')\\u003C/script\\u003E": ""}'),
120+
)
121+
122+
for in_value, out_value in tests:
123+
self.assertEqual(core_tags.escapejson(in_value), out_value)

0 commit comments

Comments
 (0)