Ticket #5: recursive_inclusion_detect.4.patch
| File recursive_inclusion_detect.4.patch, 15.4 KB (added by Carsten Klein <carsten.klein@…>, 13 years ago) |
|---|
-
template/base.py
94 94 """ 95 95 96 96 97 class 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 97 113 class Context(object): 98 114 """Container for template input data. 99 115 … … 370 386 _number_conv = unicode # function used to convert numbers to event data 371 387 372 388 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)): 374 391 """Initialize a template from either a string, a file-like object, or 375 392 an already parsed markup stream. 376 393 … … 386 403 default), "lenient", or a custom lookup class 387 404 :param allow_exec: whether Python code blocks in templates should be 388 405 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 389 410 390 411 :note: Changed in 0.5: Added the `allow_exec` argument 391 412 """ … … 396 417 self.allow_exec = allow_exec 397 418 self._init_filters() 398 419 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)) 399 428 400 429 if isinstance(source, basestring): 401 430 source = StringIO(source) … … 468 497 yield event 469 498 else: 470 499 if kind is INCLUDE: 471 href, cls, fallback = data500 href, cls, backtrace, fallback = data 472 501 if isinstance(href, basestring) and \ 473 502 not getattr(self.loader, 'auto_reload', True): 474 503 # If the path to the included template is static, and … … 476 505 # the template is inlined into the stream 477 506 try: 478 507 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) 480 511 for event in tmpl.stream: 481 512 yield event 482 513 except TemplateNotFound: … … 490 521 data = href, cls, list(self._prepare(fallback)) 491 522 492 523 yield kind, data, pos 493 524 494 525 def generate(self, *args, **kwargs): 495 526 """Apply the template to the given context data. 496 527 … … 591 622 592 623 for event in stream: 593 624 if event[0] is INCLUDE: 594 href, cls, fallback = event[1]625 href, cls, backtrace, fallback = event[1] 595 626 if not isinstance(href, basestring): 596 627 parts = [] 597 628 for subkind, subdata, subpos in self._flatten(href, ctxt, … … 601 632 href = ''.join([x for x in parts if x is not None]) 602 633 try: 603 634 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]) 605 638 for event in tmpl.generate(ctxt, **vars): 606 639 yield event 607 640 except TemplateNotFound: -
template/markup.py
61 61 _number_conv = Markup 62 62 63 63 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)): 65 66 Template.__init__(self, source, filepath=filepath, filename=filename, 66 67 loader=loader, encoding=encoding, lookup=lookup, 67 allow_exec=allow_exec) 68 allow_exec=allow_exec, backtrace=backtrace, 69 include_pos=include_pos) 68 70 self.add_directives(self.DIRECTIVE_NAMESPACE, self) 69 71 70 72 def _init_filters(self): … … 251 253 raise TemplateSyntaxError('Invalid value for "parse" ' 252 254 'attribute of include', 253 255 self.filepath, *pos[1:]) 254 stream.append((INCLUDE, (href, cls, fallback), pos)) 256 stream.append((INCLUDE, (href, cls, self.backtrace, 257 fallback), pos)) 255 258 else: 256 259 stream.append((kind, data, pos)) 257 260 -
template/text.py
132 132 133 133 def __init__(self, source, filepath=None, filename=None, loader=None, 134 134 encoding=None, lookup='strict', allow_exec=False, 135 delims=('{%', '%}', '{#', '#}')): 135 delims=('{%', '%}', '{#', '#}'), backtrace=[], 136 include_pos=(-1,-1)): 136 137 self.delimiters = delims 137 138 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) 139 141 140 142 def _get_delims(self): 141 143 return self._delims … … 192 194 lookup=self.lookup)) 193 195 if len(value) == 1 and value[0][0] is TEXT: 194 196 value = value[0][1] 195 stream.append((INCLUDE, (value, None, []), pos)) 197 stream.append((INCLUDE, (value, (None, self.backtrace), []), 198 pos)) 196 199 197 200 elif command == 'python': 198 201 if not self.allow_exec: … … 310 313 (self.filepath, lineno, 0))] 311 314 elif command == 'include': 312 315 pos = (self.filename, lineno, 0) 313 stream.append((INCLUDE, (value.strip(), None, []), pos)) 316 stream.append((INCLUDE, (value.strip(), (None, self.backtrace), 317 []), pos)) 314 318 elif command != '#': 315 319 cls = self.get_directive(command) 316 320 if cls is None: -
template/loader.py
19 19 except ImportError: 20 20 import dummy_threading as threading 21 21 22 from genshi.template.base import TemplateError 22 from genshi.template.base import TemplateError, RecursiveInclusionError 23 23 from genshi.util import LRUCache 24 24 25 25 __all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package', … … 131 131 self._uptodate = {} 132 132 self._lock = threading.RLock() 133 133 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)): 135 136 """Load the template with the given name. 136 137 137 138 If the `filename` parameter is relative, this method searches the … … 158 159 :param cls: the class of the template object to instantiate 159 160 :param encoding: the encoding of the template to load; defaults to the 160 161 ``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 161 166 :return: the loaded `Template` instance 162 167 :raises TemplateNotFound: if a template with the given name could not 163 168 be found … … 173 178 filename = os.path.join(os.path.dirname(relative_to), filename) 174 179 175 180 filename = os.path.normpath(filename) 176 cachekey = filename181 filepath = None 177 182 178 183 self._lock.acquire() 179 184 try: 180 # First check the cache to avoid reparsing the same file181 try:182 tmpl = self._cache[cachekey]183 if not self.auto_reload:184 return tmpl185 uptodate = self._uptodate[cachekey]186 if uptodate is not None and uptodate():187 return tmpl188 except (KeyError, OSError):189 pass190 191 185 isabs = False 192 186 193 187 if os.path.isabs(filename): … … 203 197 search_path = list(search_path) + [dirname] 204 198 isabs = True 205 199 206 elif not search_path:207 # Uh oh, don't know where to look for the template208 raise TemplateError('Search path for templates not configured')209 210 200 for loadfunc in search_path: 211 201 if isinstance(loadfunc, basestring): 212 202 loadfunc = directory(loadfunc) 213 203 try: 214 204 filepath, filename, fileobj, uptodate = loadfunc(filename) 205 break 215 206 except IOError: 216 207 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: 235 238 return tmpl 239 uptodate = self._uptodate[cachekey] 240 if uptodate is not None and uptodate(): 241 return tmpl 242 except (KeyError, OSError): 243 pass 236 244 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 237 258 raise TemplateNotFound(filename, search_path) 238 259 239 260 finally: 240 261 self._lock.release() 241 262 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)): 243 265 """Instantiate and return the `Template` object based on the given 244 266 class and parameters. 245 267 … … 255 277 path 256 278 :param encoding: the encoding of the template to load; defaults to the 257 279 ``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 258 284 :return: the loaded `Template` instance 259 285 :rtype: `Template` 260 286 """ … … 262 288 encoding = self.default_encoding 263 289 return cls(fileobj, filepath=filepath, filename=filename, loader=self, 264 290 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) 266 293 267 294 @staticmethod 268 295 def directory(path):
