Always autoescape jinja environments unless overridden

We were only rendering .html files before, so the old _guess_autoescape function
had the effect of always autoescaping .render() (from a file) but never
autoescaping .from_string(). However, most places using .from_string() actually
render (Qt-)HTML via jinja, so they should escape stuff!

Now, we always autoescape, except when the caller uses the
jinja.environment.no_autoescape() context manager, which places rendering
stylesheets now do.

This impacted:

- Confirm quit texts (no HTML here)
- config.py loading errors
  (where this was found because of an error containing - a <keybinding>)
- Certificate error prompts
  (should be fine from what I can tell, as the only user-controllable output is
  the hostname, which cannot contain HTML)
This commit is contained in:
Florian Bruhin 2017-09-16 08:30:19 +02:00
parent 337d57b940
commit 3179e8c7b9
4 changed files with 22 additions and 23 deletions

View File

@ -194,6 +194,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
color: {{ conf.colors.completion.match.fg }}; color: {{ conf.colors.completion.match.fg }};
} }
""" """
with jinja.environment.no_autoescape():
template = jinja.environment.from_string(stylesheet) template = jinja.environment.from_string(stylesheet)
self._doc.setDefaultStyleSheet(template.render(conf=config.val)) self._doc.setDefaultStyleSheet(template.render(conf=config.val))

View File

@ -585,6 +585,7 @@ def _render_stylesheet(stylesheet):
"""Render the given stylesheet jinja template.""" """Render the given stylesheet jinja template."""
# Imported here to avoid a Python 3.4 circular import # Imported here to avoid a Python 3.4 circular import
from qutebrowser.utils import jinja from qutebrowser.utils import jinja
with jinja.environment.no_autoescape():
template = jinja.environment.from_string(stylesheet) template = jinja.environment.from_string(stylesheet)
return template.render(conf=val) return template.render(conf=val)

View File

@ -21,6 +21,7 @@
import os import os
import os.path import os.path
import contextlib
import traceback import traceback
import mimetypes import mimetypes
import html import html
@ -82,21 +83,19 @@ class Environment(jinja2.Environment):
def __init__(self): def __init__(self):
super().__init__(loader=Loader('html'), super().__init__(loader=Loader('html'),
autoescape=self._guess_autoescape, autoescape=lambda _name: self._autoescape,
undefined=jinja2.StrictUndefined) undefined=jinja2.StrictUndefined)
self.globals['resource_url'] = self._resource_url self.globals['resource_url'] = self._resource_url
self.globals['file_url'] = urlutils.file_url self.globals['file_url'] = urlutils.file_url
self.globals['data_url'] = self._data_url self.globals['data_url'] = self._data_url
self._autoescape = True
def _guess_autoescape(self, template_name): @contextlib.contextmanager
"""Turn auto-escape on/off based on the file type. def no_autoescape(self):
"""Context manager to temporarily turn off autoescaping."""
Based on http://jinja.pocoo.org/docs/dev/api/#autoescaping self._autoescape = False
""" yield
if template_name is None or '.' not in template_name: self._autoescape = True
return False
ext = template_name.rsplit('.', 1)[1]
return ext in ['html', 'htm', 'xml']
def _resource_url(self, path): def _resource_url(self, path):
"""Load images from a relative path (to qutebrowser). """Load images from a relative path (to qutebrowser).

View File

@ -146,14 +146,12 @@ def test_attribute_error():
jinja.render('attributeerror.html', obj=object()) jinja.render('attributeerror.html', obj=object())
@pytest.mark.parametrize('name, expected', [ @pytest.mark.parametrize('escape', [True, False])
(None, False), def test_autoescape(escape):
('foo', False), if not escape:
('foo.html', True), with jinja.environment.no_autoescape():
('foo.htm', True), template = jinja.environment.from_string("{{ v }}")
('foo.xml', True), assert template.render(v='<foo') == '<foo'
('blah/bar/foo.html', True),
('foo.bar.html', True), template = jinja.environment.from_string("{{ v }}")
]) assert template.render(v='<foo') == '&lt;foo'
def test_autoescape(name, expected):
assert jinja.environment._guess_autoescape(name) == expected