############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """HappyDoc Expression Engine HappyDoc-specific implementation of TALES, with handlers for Python expressions, string literals, and paths. *This file is derived from the file Zope/lib/python/Products/PageTemplates/Expressions.py distributed as part of the Zope application server (http://www.zope.org).* """ __version__='$Revision: 1.1 $'[11:-2] import re, sys from hdTALES import Engine, CompilerError, _valid_name, NAME_RE, \ TALESError, Undefined, Default, _parse_expr from string import strip, split, join, replace, lstrip #from Acquisition import aq_base, aq_inner, aq_parent _engine = None def getEngine(): global _engine if _engine is None: #from PathIterator import Iterator #_engine = Engine(Iterator) _engine = Engine() installHandlers(_engine) _engine._nocatch = (TALESError, 'Redirect') return _engine def installHandlers(engine): reg = engine.registerType pe = PathExpr for pt in ('standard', 'path', 'exists', 'nocall'): reg(pt, pe) reg('string', StringExpr) reg('python', PythonExpr) reg('not', NotExpr) reg('defer', DeferExpr) if sys.modules.has_key('Zope'): import AccessControl from AccessControl import getSecurityManager try: from AccessControl import Unauthorized except ImportError: Unauthorized = "Unauthorized" if hasattr(AccessControl, 'full_read_guard'): from ZRPythonExpr import PythonExpr, _SecureModuleImporter, \ call_with_ns else: from ZPythonExpr import PythonExpr, _SecureModuleImporter, \ call_with_ns else: from PythonExpr import getSecurityManager, PythonExpr try: from zExceptions import Unauthorized except ImportError: Unauthorized = "Unauthorized" def call_with_ns(f, ns, arg=1): if arg==2: return f(None, ns) else: return f(ns) class _SecureModuleImporter: """Simple version of the importer for use with trusted code.""" __allow_access_to_unprotected_subobjects__ = 1 def __getitem__(self, module): __import__(module) return sys.modules[module] SecureModuleImporter = _SecureModuleImporter() class _DocsetModuleImporter: def __getitem__(self, module): try: mod = __import__(module) except Exception, msg: # # Try to find it in the docset path. # try: module = 'happydoclib.docset.docset_TAL.%s' % module mod = __import__(module) except: raise msg path = module.split('.') for name in path[1:]: mod = getattr(mod, name) return mod DocsetModuleImporter = _DocsetModuleImporter() Undefs = (Undefined, AttributeError, KeyError, TypeError, IndexError, Unauthorized) def render(ob, ns): """ Calls the object, possibly a document template, or just returns it if not callable. (From DT_Util.py) """ if hasattr(ob, '__render_with_namespace__'): ob = call_with_ns(ob.__render_with_namespace__, ns) else: base = ob if callable(base): try: if getattr(base, 'isDocTemp', 0): ob = call_with_ns(ob, ns, 2) else: ob = ob() except AttributeError, n: if str(n) != '__call__': raise return ob class SubPathExpr: def __init__(self, path): #print 'SubPathExpr(%s)' % path self._path = path = split(strip(path), '/') self._base = base = path.pop(0) if not _valid_name(base): raise CompilerError, 'Invalid variable name "%s"' % base # Parse path self._dp = dp = [] for i in range(len(path)): e = path[i] if e[:1] == '?' and _valid_name(e[1:]): dp.append((i, e[1:])) dp.reverse() def _eval(self, econtext, list=list, isinstance=isinstance, StringType=type('')): vars = econtext.vars path = self._path if self._dp: path = list(path) # Copy! for i, varname in self._dp: val = vars[varname] if isinstance(val, StringType): path[i] = val else: # If the value isn't a string, assume it's a sequence # of path names. path[i:i+1] = list(val) __traceback_info__ = base = self._base if base == 'CONTEXTS': ob = econtext.contexts else: ob = vars[base] if isinstance(ob, DeferWrapper): ob = ob() if path: ob = restrictedTraverse(ob, path, getSecurityManager()) return ob class PathExpr: def __init__(self, name, expr, engine): #print 'PathExpr(%s, %s)' % (name, expr) self._s = expr self._name = name paths = split(expr, '|') self._subexprs = [] add = self._subexprs.append for i in range(len(paths)): path = lstrip(paths[i]) if _parse_expr(path): # This part is the start of another expression type, # so glue it back together and compile it. add(engine.compile(lstrip(join(paths[i:], '|')))) break add(SubPathExpr(path)._eval) def _exists(self, econtext): for expr in self._subexprs: try: expr(econtext) except Undefs: pass else: return 1 return 0 def _eval(self, econtext, isinstance=isinstance, StringType=type(''), render=render): for expr in self._subexprs[:-1]: # Try all but the last subexpression, skipping undefined ones try: ob = expr(econtext) except Undefs: pass else: break else: # On the last subexpression allow exceptions through, but # wrap ones that indicate that the subexpression was undefined try: ob = self._subexprs[-1](econtext) except Undefs[1:]: raise Undefined(self._s, sys.exc_info()) if self._name == 'nocall' or isinstance(ob, StringType): return ob # Return the rendered object return render(ob, econtext.vars) def __call__(self, econtext): if self._name == 'exists': return self._exists(econtext) return self._eval(econtext) def __str__(self): return '%s expression %s' % (self._name, `self._s`) def __repr__(self): return '%s:%s' % (self._name, `self._s`) _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE}) class StringExpr: def __init__(self, name, expr, engine): #print 'StringExpr(%s, %s)' % (name, expr) self._s = expr if '%' in expr: expr = replace(expr, '%', '%%') self._vars = vars = [] if '$' in expr: parts = [] for exp in split(expr, '$$'): if parts: parts.append('$') m = _interp.search(exp) while m is not None: parts.append(exp[:m.start()]) parts.append('%s') vars.append(PathExpr('path', m.group(1) or m.group(2), engine)) exp = exp[m.end():] m = _interp.search(exp) if '$' in exp: raise CompilerError, ( '$ must be doubled or followed by a simple path') parts.append(exp) expr = join(parts, '') self._expr = expr def __call__(self, econtext): vvals = [] for var in self._vars: v = var(econtext) if isinstance(v, Exception): raise v vvals.append(v) return self._expr % tuple(vvals) def __str__(self): return 'string expression %s' % `self._s` def __repr__(self): return 'string:%s' % `self._s` class NotExpr: def __init__(self, name, expr, compiler): #print 'NotExpr(%s, %s)' % (name, expr) self._s = expr = lstrip(expr) self._c = compiler.compile(expr) def __call__(self, econtext): return not econtext.evaluateBoolean(self._c) def __repr__(self): return 'not:%s' % `self._s` class DeferWrapper: def __init__(self, expr, econtext): #print 'DeferWrapper(%s, %s)' % (name, expr) self._expr = expr self._econtext = econtext def __str__(self): return str(self()) def __call__(self): return self._expr(self._econtext) class DeferExpr: def __init__(self, name, expr, compiler): #print 'DeferExpr(%s, %s)' % (name, expr) self._s = expr = lstrip(expr) self._c = compiler.compile(expr) def __call__(self, econtext): return DeferWrapper(self._c, econtext) def __repr__(self): return 'defer:%s' % `self._s` def restrictedTraverse(self, path, securityManager, get=getattr, has=hasattr, N=None, M=[], TupleType=type(()) ): REQUEST = {'path': path} REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy! if not path[0]: # If the path starts with an empty string, go to the root first. self = self.getPhysicalRoot() if not securityManager.validateValue(self): raise Unauthorized, name path.pop(0) path.reverse() validate = securityManager.validate object = self while path: __traceback_info__ = REQUEST name = path.pop() if isinstance(name, TupleType): object = apply(object, name) continue if name[0] == '_': # Never allowed in a URL. raise AttributeError, name if name=='..': o = get(object, 'aq_parent', M) if o is not M: if not validate(object, object, name, o): raise Unauthorized, name object=o continue t=get(object, '__bobo_traverse__', N) if t is not N: o=t(REQUEST, name) container = None if has(o, 'im_self'): container = o.im_self elif (has(get(object, 'aq_base', object), name) and get(object, name) == o): container = object if not validate(object, container, name, o): raise Unauthorized, name else: o=get(object, name, M) if o is not M: # Check security. if has(object, 'aq_acquire'): object.aq_acquire( name, validate2, validate) else: if not validate(object, object, name, o): raise Unauthorized, name else: try: o=object[name] except (AttributeError, TypeError): raise AttributeError, name if not validate(object, object, name, o): raise Unauthorized, name object = o return object def validate2(orig, inst, name, v, real_validate): if not real_validate(orig, inst, name, v): raise Unauthorized, name return 1