Make jinja templating more strict
This ensures we actually know when an AttributeError happens. It also changes most external code to use the correct environment, rather than simply creating a jinja2.Template, which wouldn't use the more tightened environment.
This commit is contained in:
parent
4b4acc5f5a
commit
1022b7ea32
@ -21,10 +21,8 @@
|
||||
|
||||
import html
|
||||
|
||||
import jinja2
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
||||
|
||||
|
||||
class CallSuper(Exception):
|
||||
@ -137,7 +135,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
assert error.is_overridable(), repr(error)
|
||||
|
||||
if ssl_strict == 'ask':
|
||||
err_template = jinja2.Template("""
|
||||
err_template = jinja.environment.from_string("""
|
||||
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
|
@ -22,12 +22,11 @@
|
||||
import functools
|
||||
import collections
|
||||
|
||||
import jinja2
|
||||
import sip
|
||||
from PyQt5.QtGui import QColor
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, objreg
|
||||
from qutebrowser.utils import log, objreg, jinja
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=16)
|
||||
@ -40,7 +39,7 @@ def get_stylesheet(template_str):
|
||||
Return:
|
||||
The formatted template as string.
|
||||
"""
|
||||
template = jinja2.Template(template_str)
|
||||
template = jinja.environment.from_string(template_str)
|
||||
return template.render(conf=config.val)
|
||||
|
||||
|
||||
|
@ -24,13 +24,13 @@ import base64
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
import jinja2
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
|
||||
|
||||
from qutebrowser.commands import runners, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes, qtutils, objreg, utils
|
||||
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
|
||||
jinja)
|
||||
from qutebrowser.mainwindow import tabbedbrowser, messageview, prompt
|
||||
from qutebrowser.mainwindow.statusbar import bar
|
||||
from qutebrowser.completion import completionwidget, completer
|
||||
@ -552,7 +552,7 @@ class MainWindow(QWidget):
|
||||
"download is" if download_count == 1 else "downloads are"))
|
||||
# Process all quit messages that user must confirm
|
||||
if quit_texts or 'always' in config.val.confirm_quit:
|
||||
msg = jinja2.Template("""
|
||||
msg = jinja.environment.from_string("""
|
||||
<ul>
|
||||
{% for text in quit_texts %}
|
||||
<li>{{text}}</li>
|
||||
|
@ -76,40 +76,56 @@ class Loader(jinja2.BaseLoader):
|
||||
return source, path, lambda: True
|
||||
|
||||
|
||||
def _guess_autoescape(template_name):
|
||||
"""Turn auto-escape on/off based on the file type.
|
||||
class Environment(jinja2.Environment):
|
||||
|
||||
Based on http://jinja.pocoo.org/docs/dev/api/#autoescaping
|
||||
"""
|
||||
if template_name is None or '.' not in template_name:
|
||||
return False
|
||||
ext = template_name.rsplit('.', 1)[1]
|
||||
return ext in ['html', 'htm', 'xml']
|
||||
def __init__(self):
|
||||
super().__init__(loader=Loader('html'),
|
||||
autoescape=self._guess_autoescape,
|
||||
undefined=jinja2.StrictUndefined)
|
||||
self.globals['resource_url'] = self._resource_url
|
||||
self.globals['file_url'] = urlutils.file_url
|
||||
self.globals['data_url'] = self._data_url
|
||||
|
||||
def _guess_autoescape(self, template_name):
|
||||
"""Turn auto-escape on/off based on the file type.
|
||||
|
||||
def resource_url(path):
|
||||
"""Load images from a relative path (to qutebrowser).
|
||||
Based on http://jinja.pocoo.org/docs/dev/api/#autoescaping
|
||||
"""
|
||||
if template_name is None or '.' not in template_name:
|
||||
return False
|
||||
ext = template_name.rsplit('.', 1)[1]
|
||||
return ext in ['html', 'htm', 'xml']
|
||||
|
||||
Arguments:
|
||||
path: The relative path to the image
|
||||
"""
|
||||
image = utils.resource_filename(path)
|
||||
return QUrl.fromLocalFile(image).toString(QUrl.FullyEncoded)
|
||||
def _resource_url(self, path):
|
||||
"""Load images from a relative path (to qutebrowser).
|
||||
|
||||
Arguments:
|
||||
path: The relative path to the image
|
||||
"""
|
||||
image = utils.resource_filename(path)
|
||||
return QUrl.fromLocalFile(image).toString(QUrl.FullyEncoded)
|
||||
|
||||
def data_url(path):
|
||||
"""Get a data: url for the broken qutebrowser logo."""
|
||||
data = utils.read_file(path, binary=True)
|
||||
filename = utils.resource_filename(path)
|
||||
mimetype = mimetypes.guess_type(filename)
|
||||
assert mimetype is not None, path
|
||||
return urlutils.data_url(mimetype[0], data).toString()
|
||||
def _data_url(self, path):
|
||||
"""Get a data: url for the broken qutebrowser logo."""
|
||||
data = utils.read_file(path, binary=True)
|
||||
filename = utils.resource_filename(path)
|
||||
mimetype = mimetypes.guess_type(filename)
|
||||
assert mimetype is not None, path
|
||||
return urlutils.data_url(mimetype[0], data).toString()
|
||||
|
||||
def getattr(self, obj, attribute):
|
||||
"""Override jinja's getattr() to be less clever.
|
||||
|
||||
This means it doesn't fall back to __getitem__, and it doesn't hide
|
||||
AttributeError.
|
||||
"""
|
||||
return getattr(obj, attribute)
|
||||
|
||||
|
||||
def render(template, **kwargs):
|
||||
"""Render the given template and pass the given arguments to it."""
|
||||
try:
|
||||
return _env.get_template(template).render(**kwargs)
|
||||
return environment.get_template(template).render(**kwargs)
|
||||
except jinja2.exceptions.UndefinedError:
|
||||
log.misc.exception("UndefinedError while rendering " + template)
|
||||
err_path = os.path.join('html', 'undef_error.html')
|
||||
@ -118,7 +134,4 @@ def render(template, **kwargs):
|
||||
return err_template.format(pagename=template, traceback=tb)
|
||||
|
||||
|
||||
_env = jinja2.Environment(loader=Loader('html'), autoescape=_guess_autoescape)
|
||||
_env.globals['resource_url'] = resource_url
|
||||
_env.globals['file_url'] = urlutils.file_url
|
||||
_env.globals['data_url'] = data_url
|
||||
environment = Environment()
|
||||
|
@ -55,6 +55,9 @@ def patch_read_file(monkeypatch):
|
||||
elif path == os.path.join('html', 'undef_error.html'):
|
||||
assert not binary
|
||||
return real_read_file(path)
|
||||
elif path == os.path.join('html', 'attributeerror.html'):
|
||||
assert not binary
|
||||
return """{{ obj.foobar }}"""
|
||||
else:
|
||||
raise IOError("Invalid path {}!".format(path))
|
||||
|
||||
@ -137,6 +140,12 @@ def test_undefined_function(caplog):
|
||||
assert caplog.records[0].msg == "UndefinedError while rendering undef.html"
|
||||
|
||||
|
||||
def test_attribute_error():
|
||||
"""Make sure accessing an unknown attribute fails."""
|
||||
with pytest.raises(AttributeError):
|
||||
jinja.render('attributeerror.html', obj=object())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, expected', [
|
||||
(None, False),
|
||||
('foo', False),
|
||||
@ -147,4 +156,4 @@ def test_undefined_function(caplog):
|
||||
('foo.bar.html', True),
|
||||
])
|
||||
def test_autoescape(name, expected):
|
||||
assert jinja._guess_autoescape(name) == expected
|
||||
assert jinja.environment._guess_autoescape(name) == expected
|
||||
|
Loading…
Reference in New Issue
Block a user