Skip to content
Permalink
Browse files
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.
  • Loading branch information
The-Compiler committed Jun 13, 2017
1 parent 7a05d43 commit df631c5
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 44 deletions.
@@ -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 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 @@ def closeEvent(self, e):
"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 @@ def get_source(self, _env, template):
return source, path, lambda: True


def _guess_autoescape(template_name):
"""Turn auto-escape on/off based on the file type.
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 resource_url(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()
class Environment(jinja2.Environment):

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.
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 _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(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 _read_file(path, binary=False):
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

0 comments on commit df631c5

Please sign in to comment.