Edgewall Software

Ticket #5: recursive_inclusion_detect.patch

File recursive_inclusion_detect.patch, 12.7 KB (added by Carsten Klein <carsten.klein@…>, 13 years ago)

initial proposal. TODO text templates, cleanup and remaining issues

  • template/base.py

     
    9494    """
    9595
    9696
     97class RecursiveInclusionError(TemplateRuntimeError):
     98    """
     99    """
     100    def __init__(self, filepath, backtrace, includefile, includepos):
     101        pass
     102
     103
    97104class Context(object):
    98105    """Container for template input data.
    99106   
     
    370377    _number_conv = unicode # function used to convert numbers to event data
    371378
    372379    def __init__(self, source, filepath=None, filename=None, loader=None,
    373                  encoding=None, lookup='strict', allow_exec=True):
     380                 encoding=None, lookup='strict', allow_exec=True,
     381                 backtrace=[], include_pos=(None, -1, -1)):
    374382        """Initialize a template from either a string, a file-like object, or
    375383        an already parsed markup stream.
    376384       
     
    386394                       default), "lenient", or a custom lookup class
    387395        :param allow_exec: whether Python code blocks in templates should be
    388396                           allowed
     397        :param backtrace: a list of tuples of type (filepath, pos) used with
     398                          recursive inclusion detection
     399        :param include_pos: a tuple containing the position in the stream at
     400                            which the include was detected
    389401       
    390402        :note: Changed in 0.5: Added the `allow_exec` argument
    391403        """
     
    396408        self.allow_exec = allow_exec
    397409        self._init_filters()
    398410        self._prepared = False
     411        self.backtrace = backtrace
     412        self.include_pos = include_pos
     413        for fpath, pos in backtrace:
     414            # TODO include trace in output message where template was first
     415            # included in
     416            if fpath == filepath:
     417                raise TemplateSyntaxError('Recursive inclusion of '
     418                                          '"%s" detected. Backtrace: "%s"' % (self.filepath, self.backtrace),
     419                                          backtrace[-1][0], *self.include_pos[1:])
     420        self.backtrace.append((self.filepath, self.include_pos))
    399421
    400422        if isinstance(source, basestring):
    401423            source = StringIO(source)
     
    468490                        yield event
    469491            else:
    470492                if kind is INCLUDE:
    471                     href, cls, fallback = data
     493                    href, cls, backtrace, fallback = data
    472494                    if isinstance(href, basestring) and \
    473495                            not getattr(self.loader, 'auto_reload', True):
    474496                        # If the path to the included template is static, and
     
    476498                        # the template is inlined into the stream
    477499                        try:
    478500                            tmpl = self.loader.load(href, relative_to=pos[0],
    479                                                     cls=cls or self.__class__)
     501                                                    cls=cls or self.__class__,
     502                                                    backtrace=backtrace,
     503                                                    include_pos=pos)
    480504                            for event in tmpl.stream:
    481505                                yield event
    482506                        except TemplateNotFound:
     
    490514                        data = href, cls, list(self._prepare(fallback))
    491515
    492516                yield kind, data, pos
     517               
     518                #self.file.close()
    493519
    494520    def generate(self, *args, **kwargs):
    495521        """Apply the template to the given context data.
     
    591617
    592618        for event in stream:
    593619            if event[0] is INCLUDE:
    594                 href, cls, fallback = event[1]
     620                href, cls, backtrace, fallback = event[1]
    595621                if not isinstance(href, basestring):
    596622                    parts = []
    597623                    for subkind, subdata, subpos in self._flatten(href, ctxt,
     
    601627                    href = ''.join([x for x in parts if x is not None])
    602628                try:
    603629                    tmpl = self.loader.load(href, relative_to=event[2][0],
    604                                             cls=cls or self.__class__)
     630                                            cls=cls or self.__class__,
     631                                            backtrace=backtrace,
     632                                            include_pos=event[2])
    605633                    for event in tmpl.generate(ctxt, **vars):
    606634                        yield event
    607635                except TemplateNotFound:
  • template/markup.py

     
    6161    _number_conv = Markup
    6262
    6363    def __init__(self, source, filepath=None, filename=None, loader=None,
    64                  encoding=None, lookup='strict', allow_exec=True):
     64                 encoding=None, lookup='strict', allow_exec=True,
     65                 backtrace=[], include_pos=(None, -1, -1)):
    6566        Template.__init__(self, source, filepath=filepath, filename=filename,
    6667                          loader=loader, encoding=encoding, lookup=lookup,
    67                           allow_exec=allow_exec)
     68                          allow_exec=allow_exec, backtrace=backtrace,
     69                          include_pos=include_pos)
    6870        self.add_directives(self.DIRECTIVE_NAMESPACE, self)
    6971
    7072    def _init_filters(self):
     
    251253                        raise TemplateSyntaxError('Invalid value for "parse" '
    252254                                                  'attribute of include',
    253255                                                  self.filepath, *pos[1:])
    254                     stream.append((INCLUDE, (href, cls, fallback), pos))
     256                    stream.append((INCLUDE, (href, cls, self.backtrace,
     257                                             fallback), pos))
    255258                else:
    256259                    stream.append((kind, data, pos))
    257260
  • template/loader.py

     
    1919except ImportError:
    2020    import dummy_threading as threading
    2121
    22 from genshi.template.base import TemplateError
     22from genshi.template.base import TemplateError, TemplateSyntaxError
    2323from genshi.util import LRUCache
    2424
    2525__all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package',
     
    131131        self._uptodate = {}
    132132        self._lock = threading.RLock()
    133133
    134     def load(self, filename, relative_to=None, cls=None, encoding=None):
     134    def load(self, filename, relative_to=None, cls=None, encoding=None,
     135             backtrace=[], include_pos=(-1,-1)):
    135136        """Load the template with the given name.
    136137       
    137138        If the `filename` parameter is relative, this method searches the
     
    158159        :param cls: the class of the template object to instantiate
    159160        :param encoding: the encoding of the template to load; defaults to the
    160161                         ``default_encoding`` of the loader instance
     162        :param backtrace: a list of tuples of type (filepath, pos) used with
     163                          recursive inclusion detection
     164        :param include_pos: the position in the stream at which the include was
     165                            detected in the including template
    161166        :return: the loaded `Template` instance
    162167        :raises TemplateNotFound: if a template with the given name could not
    163168                                  be found
     
    173178            filename = os.path.join(os.path.dirname(relative_to), filename)
    174179
    175180        filename = os.path.normpath(filename)
    176         cachekey = filename
    177181
    178182        self._lock.acquire()
    179183        try:
    180             # First check the cache to avoid reparsing the same file
    181             try:
    182                 tmpl = self._cache[cachekey]
    183                 if not self.auto_reload:
    184                     return tmpl
    185                 uptodate = self._uptodate[cachekey]
    186                 if uptodate is not None and uptodate():
    187                     return tmpl
    188             except (KeyError, OSError):
    189                 pass
    190 
    191184            isabs = False
    192185
    193186            if os.path.isabs(filename):
     
    212205                    loadfunc = directory(loadfunc)
    213206                try:
    214207                    filepath, filename, fileobj, uptodate = loadfunc(filename)
     208                    break
    215209                except IOError:
    216210                    continue
    217211                else:
    218                     try:
    219                         if isabs:
    220                             # If the filename of either the included or the
    221                             # including template is absolute, make sure the
    222                             # included template gets an absolute path, too,
    223                             # so that nested includes work properly without a
    224                             # search path
    225                             filename = filepath
    226                         tmpl = self._instantiate(cls, fileobj, filepath,
    227                                                  filename, encoding=encoding)
    228                         if self.callback:
    229                             self.callback(tmpl)
    230                         self._cache[cachekey] = tmpl
    231                         self._uptodate[cachekey] = uptodate
    232                     finally:
    233                         if hasattr(fileobj, 'close'):
    234                             fileobj.close()
     212                    if isabs:
     213                        # If the filename of either the included or the
     214                        # including template is absolute, make sure the
     215                        # included template gets an absolute path, too,
     216                        # so that nested includes work properly without a
     217                        # search path
     218                        filename = filepath
     219
     220            cachekey = filepath
     221            self._uptodate[cachekey] = uptodate
     222
     223            # First check the cache to avoid reparsing the same file
     224            try:
     225                tmpl = self._cache[cachekey]
     226                for fpath, pos in backtrace:
     227                    # TODO include trace in output message where template was first
     228                    # included in
     229                    if fpath == filepath:
     230                        raise TemplateSyntaxError('Recursive inclusion of '
     231                                                  '"%s" detected. Backtrace: "%s"' % (filename, backtrace),
     232                                                  backtrace[-1][0], *include_pos[1:])
     233                if not self.auto_reload:
    235234                    return tmpl
     235                uptodate = self._uptodate[cachekey]
     236                if uptodate is not None and uptodate():
     237                    return tmpl
     238            except (KeyError, OSError):
     239                pass
    236240
     241            try:
     242                tmpl = self._instantiate(cls, fileobj, filepath,
     243                                         filename, encoding=encoding,
     244                                         backtrace=backtrace,
     245                                         include_pos=include_pos)
     246                if self.callback:
     247                    self.callback(tmpl)
     248                self._cache[cachekey] = tmpl
     249            finally:
     250                if hasattr(fileobj, 'close'):
     251                    fileobj.close()
     252            return tmpl
     253
    237254            raise TemplateNotFound(filename, search_path)
    238255
    239256        finally:
    240257            self._lock.release()
    241258
    242     def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
     259    def _instantiate(self, cls, fileobj, filepath, filename, encoding=None,
     260                     backtrace=[], include_pos=(-1,-1)):
    243261        """Instantiate and return the `Template` object based on the given
    244262        class and parameters.
    245263       
     
    255273                         path
    256274        :param encoding: the encoding of the template to load; defaults to the
    257275                         ``default_encoding`` of the loader instance
     276        :param backtrace: a list of tuples of type (filepath, pos) used with
     277                          recursive inclusion detection
     278        :param include_pos: the position in the stream at which the include was
     279                            detected in the including template
    258280        :return: the loaded `Template` instance
    259281        :rtype: `Template`
    260282        """
     
    262284            encoding = self.default_encoding
    263285        return cls(fileobj, filepath=filepath, filename=filename, loader=self,
    264286                   encoding=encoding, lookup=self.variable_lookup,
    265                    allow_exec=self.allow_exec)
     287                   allow_exec=self.allow_exec, backtrace=backtrace,
     288                   include_pos=include_pos)
    266289
    267290    @staticmethod
    268291    def directory(path):