Edgewall Software

Ticket #5: recursive_inclusion_detect.5.patch

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

Forgot to also change the text templates. However, text templates are still untested, if someone would test this would be most kind.

  • template/base.py

     
    9494    """
    9595
    9696
     97class RecursiveInclusionError(TemplateError):
     98    """
     99    """
     100    def __init__(self, filepath, backtrace, includefile, includepos):
     101        # TODO include trace in output message where template was first
     102        # included in
     103        message = 'Recursive inclusion of "%s" detected in template "%s" in ' \
     104                  'line %d, column %d.' % (filepath, includefile,
     105                                           includepos[1], includepos[2])
     106        TemplateError.__init__(self, message)
     107        self.backtrace = backtrace
     108        self.filepath = filepath
     109        self.includefile = includefile
     110        self.includepos = includepos
     111
     112
    97113class Context(object):
    98114    """Container for template input data.
    99115   
     
    370386    _number_conv = unicode # function used to convert numbers to event data
    371387
    372388    def __init__(self, source, filepath=None, filename=None, loader=None,
    373                  encoding=None, lookup='strict', allow_exec=True):
     389                 encoding=None, lookup='strict', allow_exec=True,
     390                 backtrace=[], include_pos=(None, -1, -1)):
    374391        """Initialize a template from either a string, a file-like object, or
    375392        an already parsed markup stream.
    376393       
     
    386403                       default), "lenient", or a custom lookup class
    387404        :param allow_exec: whether Python code blocks in templates should be
    388405                           allowed
     406        :param backtrace: a list of tuples of type (filepath, pos) used with
     407                          recursive inclusion detection
     408        :param include_pos: a tuple containing the position in the stream at
     409                            which the include was detected
    389410       
    390411        :note: Changed in 0.5: Added the `allow_exec` argument
    391412        """
     
    396417        self.allow_exec = allow_exec
    397418        self._init_filters()
    398419        self._prepared = False
     420        self.backtrace = list(backtrace)
     421        self.include_pos = include_pos
     422        for fpath, pos in backtrace:
     423            if fpath == filepath:
     424                raise RecursiveInclusionError(self.filepath, self.backtrace,
     425                                              backtrace[-1][0],
     426                                              self.include_pos)
     427        self.backtrace.append((self.filepath, self.include_pos))
    399428
    400429        if isinstance(source, basestring):
    401430            source = StringIO(source)
     
    468497                        yield event
    469498            else:
    470499                if kind is INCLUDE:
    471                     href, cls, fallback = data
     500                    href, cls, backtrace, fallback = data
    472501                    if isinstance(href, basestring) and \
    473502                            not getattr(self.loader, 'auto_reload', True):
    474503                        # If the path to the included template is static, and
     
    476505                        # the template is inlined into the stream
    477506                        try:
    478507                            tmpl = self.loader.load(href, relative_to=pos[0],
    479                                                     cls=cls or self.__class__)
     508                                                    cls=cls or self.__class__,
     509                                                    backtrace=backtrace,
     510                                                    include_pos=pos)
    480511                            for event in tmpl.stream:
    481512                                yield event
    482513                        except TemplateNotFound:
     
    490521                        data = href, cls, list(self._prepare(fallback))
    491522
    492523                yield kind, data, pos
    493 
     524               
    494525    def generate(self, *args, **kwargs):
    495526        """Apply the template to the given context data.
    496527       
     
    591622
    592623        for event in stream:
    593624            if event[0] is INCLUDE:
    594                 href, cls, fallback = event[1]
     625                href, cls, backtrace, fallback = event[1]
    595626                if not isinstance(href, basestring):
    596627                    parts = []
    597628                    for subkind, subdata, subpos in self._flatten(href, ctxt,
     
    601632                    href = ''.join([x for x in parts if x is not None])
    602633                try:
    603634                    tmpl = self.loader.load(href, relative_to=event[2][0],
    604                                             cls=cls or self.__class__)
     635                                            cls=cls or self.__class__,
     636                                            backtrace=backtrace,
     637                                            include_pos=event[2])
    605638                    for event in tmpl.generate(ctxt, **vars):
    606639                        yield event
    607640                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/text.py

     
    132132
    133133    def __init__(self, source, filepath=None, filename=None, loader=None,
    134134                 encoding=None, lookup='strict', allow_exec=False,
    135                  delims=('{%', '%}', '{#', '#}')):
     135                 delims=('{%', '%}', '{#', '#}'), backtrace=[],
     136                 include_pos=(-1,-1)):
    136137        self.delimiters = delims
    137138        Template.__init__(self, source, filepath=filepath, filename=filename,
    138                           loader=loader, encoding=encoding, lookup=lookup)
     139                          loader=loader, encoding=encoding, lookup=lookup,
     140                          backtrace=backtrace, include_pos=include_pos)
    139141
    140142    def _get_delims(self):
    141143        return self._delims
     
    192194                                         lookup=self.lookup))
    193195                if len(value) == 1 and value[0][0] is TEXT:
    194196                    value = value[0][1]
    195                 stream.append((INCLUDE, (value, None, []), pos))
     197                stream.append((INCLUDE, (value, None, self.backtrace, []),
     198                               pos))
    196199
    197200            elif command == 'python':
    198201                if not self.allow_exec:
     
    310313                                              (self.filepath, lineno, 0))]
    311314            elif command == 'include':
    312315                pos = (self.filename, lineno, 0)
    313                 stream.append((INCLUDE, (value.strip(), None, []), pos))
     316                stream.append((INCLUDE, (value.strip(), None, self.backtrace,
     317                                         []), pos))
    314318            elif command != '#':
    315319                cls = self.get_directive(command)
    316320                if cls is None:
  • 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, RecursiveInclusionError
    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
     181        filepath = None
    177182
    178183        self._lock.acquire()
    179184        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 
    191185            isabs = False
    192186
    193187            if os.path.isabs(filename):
     
    203197                    search_path = list(search_path) + [dirname]
    204198                isabs = True
    205199
    206             elif not search_path:
    207                 # Uh oh, don't know where to look for the template
    208                 raise TemplateError('Search path for templates not configured')
    209 
    210200            for loadfunc in search_path:
    211201                if isinstance(loadfunc, basestring):
    212202                    loadfunc = directory(loadfunc)
    213203                try:
    214204                    filepath, filename, fileobj, uptodate = loadfunc(filename)
     205                    break
    215206                except IOError:
    216207                    continue
    217                 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()
     208
     209            if not search_path:
     210                # Uh oh, don't know where to look for the template
     211                raise TemplateError('Search path for templates not configured, '
     212                                    'template "%s" not found.' % filename)
     213
     214            if not filepath:
     215                # Uh oh, don't know where to look for the template
     216                raise TemplateError('Template "%s" not found.' % filename)
     217
     218            if isabs:
     219                # If the filename of either the included or the
     220                # including template is absolute, make sure the
     221                # included template gets an absolute path, too,
     222                # so that nested includes work properly without a
     223                # search path
     224                filename = filepath
     225
     226            cachekey = filepath
     227            self._uptodate[cachekey] = uptodate
     228
     229            # First check the cache to avoid reparsing the same file
     230            try:
     231                tmpl = self._cache[cachekey]
     232                for fpath, pos in backtrace:
     233                    if fpath == filepath:
     234                        raise RecursiveInclusionError(filepath, backtrace,
     235                                                      backtrace[-1][0],
     236                                                      include_pos)
     237                if not self.auto_reload:
    235238                    return tmpl
     239                uptodate = self._uptodate[cachekey]
     240                if uptodate is not None and uptodate():
     241                    return tmpl
     242            except (KeyError, OSError):
     243                pass
    236244
     245            try:
     246                tmpl = self._instantiate(cls, fileobj, filepath,
     247                                         filename, encoding=encoding,
     248                                         backtrace=backtrace,
     249                                         include_pos=include_pos)
     250                if self.callback:
     251                    self.callback(tmpl)
     252                self._cache[cachekey] = tmpl
     253            finally:
     254                if hasattr(fileobj, 'close'):
     255                    fileobj.close()
     256            return tmpl
     257
    237258            raise TemplateNotFound(filename, search_path)
    238259
    239260        finally:
    240261            self._lock.release()
    241262
    242     def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
     263    def _instantiate(self, cls, fileobj, filepath, filename, encoding=None,
     264                     backtrace=[], include_pos=(-1,-1)):
    243265        """Instantiate and return the `Template` object based on the given
    244266        class and parameters.
    245267       
     
    255277                         path
    256278        :param encoding: the encoding of the template to load; defaults to the
    257279                         ``default_encoding`` of the loader instance
     280        :param backtrace: a list of tuples of type (filepath, pos) used with
     281                          recursive inclusion detection
     282        :param include_pos: the position in the stream at which the include was
     283                            detected in the including template
    258284        :return: the loaded `Template` instance
    259285        :rtype: `Template`
    260286        """
     
    262288            encoding = self.default_encoding
    263289        return cls(fileobj, filepath=filepath, filename=filename, loader=self,
    264290                   encoding=encoding, lookup=self.variable_lookup,
    265                    allow_exec=self.allow_exec)
     291                   allow_exec=self.allow_exec, backtrace=backtrace,
     292                   include_pos=include_pos)
    266293
    267294    @staticmethod
    268295    def directory(path):