Edgewall Software

Ticket #5: recursive_inclusion_detect.3.patch

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

Some cleanup, now uses RecursiveInclusionError? exception, text template is still a TODO

  • template/base.py

     
    3939        :param offset: the column number at which the error occurred
    4040        """
    4141        if filename is None:
    42             filename = '<string>'
     42            filename = 'undefined'
    4343        self.msg = message #: the error message string
    44         if filename != '<string>' or lineno >= 0:
     44        if filename != 'undefined' or lineno >= 0:
    4545            message = '%s (%s, line %d)' % (self.msg, filename, lineno)
    4646        Exception.__init__(self, message)
    4747        self.filename = filename #: the name of the template file
     
    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
    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                    if fpath == filepath:
     228                        raise RecursiveInclusionError(filepath, backtrace,
     229                                                  backtrace[-1][0],
     230                                                  include_pos)
     231                if not self.auto_reload:
    235232                    return tmpl
     233                uptodate = self._uptodate[cachekey]
     234                if uptodate is not None and uptodate():
     235                    return tmpl
     236            except (KeyError, OSError):
     237                pass
    236238
     239            try:
     240                tmpl = self._instantiate(cls, fileobj, filepath,
     241                                         filename, encoding=encoding,
     242                                         backtrace=backtrace,
     243                                         include_pos=include_pos)
     244                if self.callback:
     245                    self.callback(tmpl)
     246                self._cache[cachekey] = tmpl
     247            finally:
     248                if hasattr(fileobj, 'close'):
     249                    fileobj.close()
     250            return tmpl
     251
    237252            raise TemplateNotFound(filename, search_path)
    238253
    239254        finally:
    240255            self._lock.release()
    241256
    242     def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
     257    def _instantiate(self, cls, fileobj, filepath, filename, encoding=None,
     258                     backtrace=[], include_pos=(-1,-1)):
    243259        """Instantiate and return the `Template` object based on the given
    244260        class and parameters.
    245261       
     
    255271                         path
    256272        :param encoding: the encoding of the template to load; defaults to the
    257273                         ``default_encoding`` of the loader instance
     274        :param backtrace: a list of tuples of type (filepath, pos) used with
     275                          recursive inclusion detection
     276        :param include_pos: the position in the stream at which the include was
     277                            detected in the including template
    258278        :return: the loaded `Template` instance
    259279        :rtype: `Template`
    260280        """
     
    262282            encoding = self.default_encoding
    263283        return cls(fileobj, filepath=filepath, filename=filename, loader=self,
    264284                   encoding=encoding, lookup=self.variable_lookup,
    265                    allow_exec=self.allow_exec)
     285                   allow_exec=self.allow_exec, backtrace=backtrace,
     286                   include_pos=include_pos)
    266287
    267288    @staticmethod
    268289    def directory(path):