From: Armin Ronacher Date: Sat, 10 Nov 2007 23:35:36 +0000 (+0100) Subject: this fixes #277. X-Git-Tag: 2.0rc1~238 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=c9fe16ed8d727b01cd13f4c0c8939e00ee174d7b;p=jinja2.git this fixes #277. --HG-- branch : trunk --- diff --git a/jinja/exceptions.py b/jinja/exceptions.py index 1e7c632..d24dc92 100644 --- a/jinja/exceptions.py +++ b/jinja/exceptions.py @@ -82,3 +82,10 @@ class TemplateRuntimeError(TemplateError): Raised by the template engine if a tag encountered an error when rendering. """ + + +class TemplateIncludeError(TemplateError): + """ + Raised by the `ControlledLoader` if recursive includes where + detected. + """ diff --git a/jinja/loaders.py b/jinja/loaders.py index 24eaa47..ffb4789 100644 --- a/jinja/loaders.py +++ b/jinja/loaders.py @@ -19,7 +19,8 @@ from os import path from threading import Lock from jinja.parser import Parser from jinja.translators.python import PythonTranslator, Template -from jinja.exceptions import TemplateNotFound, TemplateSyntaxError +from jinja.exceptions import TemplateNotFound, TemplateSyntaxError, \ + TemplateIncludeError from jinja.utils import CacheDict @@ -100,6 +101,13 @@ class LoaderWrapper(object): from jinja.debugger import raise_syntax_error raise_syntax_error(e, self.environment) + def get_controlled_loader(self): + """ + Return a loader that runs in a controlled environment. (Keeps + track of templates that it loads and is not thread safe). + """ + return ControlledLoader(self.environment, self.loader) + def _loader_missing(self, *args, **kwargs): """Helper method that overrides all other methods if no loader is defined.""" @@ -109,6 +117,46 @@ class LoaderWrapper(object): return self.available +class ControlledLoader(LoaderWrapper): + """ + Used for template extending and including. + """ + + def __init__(self, environment, loader): + LoaderWrapper.__init__(self, environment, loader) + self._stack = [] + + def get_controlled_loader(self): + raise TypeError('Cannot get new controlled loader from an already ' + 'controlled loader.') + + def mark_as_processed(self): + """Mark the last parsed/sourced/included template as processed.""" + if not self._stack: + raise RuntimeError('No template for marking found') + self._stack.pop() + + def _controlled(method): + def new_method(self, name, *args, **kw): + if name in self._stack: + raise TemplateIncludeError('Circular imports/extends ' + 'detected. %r appeared twice.' % + name) + self._stack.append(name) + return method(self, name, *args, **kw) + try: + new_method.__name__ = method.__name__ + new_method.__doc__ = method.__doc__ + except AttributeError: + pass + return new_method + + get_source = _controlled(LoaderWrapper.get_source) + parse = _controlled(LoaderWrapper.parse) + load = _controlled(LoaderWrapper.load) + del _controlled + + class BaseLoader(object): """ Use this class to implement loaders. diff --git a/jinja/translators/python.py b/jinja/translators/python.py index c367d4c..d7c77e3 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -144,6 +144,7 @@ class PythonTranslator(Translator): def __init__(self, environment, node, source): self.environment = environment + self.loader = environment.loader.get_controlled_loader() self.node = node self.source = source self.closed = False @@ -278,7 +279,7 @@ class PythonTranslator(Translator): Clean up stuff. """ self.closed = True - self.handlers = self.node = self.environment = None + self.handlers = self.node = self.environment = self.loader = None def translate(self): """ @@ -325,9 +326,14 @@ class PythonTranslator(Translator): if child.__class__ not in (nodes.Text, nodes.Block)] - # load the template we inherit from and add not known blocks - parent = self.environment.loader.parse(node.extends, - node.filename) + # load the template we inherit from and add not known blocks. + # this also marks the templates on the controlled loader but + # are never removed. that's no problem because we don't allow + # parents we extend from as includes and the controlled loader + # is only used for this templated + parent = self.loader.parse(node.extends, + node.filename) + # look up all block nodes in the current template and # add them to the override dict. for n in get_nodes(nodes.Block, node): @@ -492,6 +498,7 @@ class PythonTranslator(Translator): result.append('%s = %r' % (file_id, filename)) result.append('debug_info = %s' % self.to_tuple(debug_mapping)) result.append('template_source = %r' % self.source) + return '\n'.join(result) def handle_template_text(self, node): @@ -834,9 +841,12 @@ class PythonTranslator(Translator): """ Include another template at the current position. """ - tmpl = self.environment.loader.parse(node.template, - node.filename) - return self.handle_node(tmpl.body) + tmpl = self.loader.parse(node.template, + node.filename) + try: + return self.handle_node(tmpl.body) + finally: + self.loader.mark_as_processed() def handle_trans(self, node): """