1 ##############################################################################
2 #
3 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE
11 #
12 ##############################################################################
13 # Modified for Roundup:
14 #
15 # 1. removed all Zope-specific code (doesn't even try to import that stuff now)
16 # 2. removed all Acquisition
17 # 3. removed blocking of leading-underscore URL components
19 """Page Template Expression Engine
21 Page Template-specific implementation of TALES, with handlers
22 for Python expressions, string literals, and paths.
23 """
25 __version__='$Revision: 1.12 $'[11:-2]
27 import re, sys
28 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
29 Undefined, Default, _parse_expr
32 _engine = None
33 def getEngine():
34 global _engine
35 if _engine is None:
36 from PathIterator import Iterator
37 _engine = Engine(Iterator)
38 installHandlers(_engine)
39 return _engine
41 def installHandlers(engine):
42 reg = engine.registerType
43 pe = PathExpr
44 for pt in ('standard', 'path', 'exists', 'nocall'):
45 reg(pt, pe)
46 reg('string', StringExpr)
47 reg('python', PythonExpr)
48 reg('not', NotExpr)
49 reg('defer', DeferExpr)
51 from PythonExpr import getSecurityManager, PythonExpr
52 guarded_getattr = getattr
53 try:
54 from zExceptions import Unauthorized
55 except ImportError:
56 class Unauthorized(Exception):
57 pass
59 def acquisition_security_filter(orig, inst, name, v, real_validate):
60 if real_validate(orig, inst, name, v):
61 return 1
62 raise Unauthorized, name
64 def call_with_ns(f, ns, arg=1):
65 if arg==2:
66 return f(None, ns)
67 else:
68 return f(ns)
70 class _SecureModuleImporter:
71 """Simple version of the importer for use with trusted code."""
72 __allow_access_to_unprotected_subobjects__ = 1
73 def __getitem__(self, module):
74 __import__(module)
75 return sys.modules[module]
77 SecureModuleImporter = _SecureModuleImporter()
79 Undefs = (Undefined, AttributeError, KeyError,
80 TypeError, IndexError, Unauthorized)
82 def render(ob, ns):
83 """
84 Calls the object, possibly a document template, or just returns it if
85 not callable. (From DT_Util.py)
86 """
87 if hasattr(ob, '__render_with_namespace__'):
88 ob = call_with_ns(ob.__render_with_namespace__, ns)
89 else:
90 base = ob
91 if callable(base):
92 try:
93 if getattr(base, 'isDocTemp', 0):
94 ob = call_with_ns(ob, ns, 2)
95 else:
96 ob = ob()
97 except AttributeError, n:
98 if str(n) != '__call__':
99 raise
100 return ob
102 class SubPathExpr:
103 def __init__(self, path):
104 self._path = path = path.strip().split('/')
105 self._base = base = path.pop(0)
106 if base and not _valid_name(base):
107 raise CompilerError, 'Invalid variable name "%s"' % base
108 # Parse path
109 self._dp = dp = []
110 for i in range(len(path)):
111 e = path[i]
112 if e[:1] == '?' and _valid_name(e[1:]):
113 dp.append((i, e[1:]))
114 dp.reverse()
116 def _eval(self, econtext,
117 list=list, isinstance=isinstance, StringType=type('')):
118 vars = econtext.vars
119 path = self._path
120 if self._dp:
121 path = list(path) # Copy!
122 for i, varname in self._dp:
123 val = vars[varname]
124 if isinstance(val, StringType):
125 path[i] = val
126 else:
127 # If the value isn't a string, assume it's a sequence
128 # of path names.
129 path[i:i+1] = list(val)
130 base = self._base
131 __traceback_info__ = 'path expression "%s"'%('/'.join(self._path))
132 if base == 'CONTEXTS' or not base:
133 ob = econtext.contexts
134 else:
135 ob = vars[base]
136 if isinstance(ob, DeferWrapper):
137 ob = ob()
138 if path:
139 ob = restrictedTraverse(ob, path, getSecurityManager())
140 return ob
142 class PathExpr:
143 def __init__(self, name, expr, engine):
144 self._s = expr
145 self._name = name
146 self._hybrid = 0
147 paths = expr.split('|')
148 self._subexprs = []
149 add = self._subexprs.append
150 for i in range(len(paths)):
151 path = paths[i].lstrip()
152 if _parse_expr(path):
153 # This part is the start of another expression type,
154 # so glue it back together and compile it.
155 add(engine.compile(('|'.join(paths[i:]).lstrip())))
156 self._hybrid = 1
157 break
158 add(SubPathExpr(path)._eval)
160 def _exists(self, econtext):
161 for expr in self._subexprs:
162 try:
163 expr(econtext)
164 except Undefs:
165 pass
166 else:
167 return 1
168 return 0
170 def _eval(self, econtext,
171 isinstance=isinstance, StringType=type(''), render=render):
172 for expr in self._subexprs[:-1]:
173 # Try all but the last subexpression, skipping undefined ones.
174 try:
175 ob = expr(econtext)
176 except Undefs:
177 pass
178 else:
179 break
180 else:
181 # On the last subexpression allow exceptions through, and
182 # don't autocall if the expression was not a subpath.
183 ob = self._subexprs[-1](econtext)
184 if self._hybrid:
185 return ob
187 if self._name == 'nocall' or isinstance(ob, StringType):
188 return ob
189 # Return the rendered object
190 return render(ob, econtext.vars)
192 def __call__(self, econtext):
193 if self._name == 'exists':
194 return self._exists(econtext)
195 return self._eval(econtext)
197 def __str__(self):
198 return '%s expression %s' % (self._name, `self._s`)
200 def __repr__(self):
201 return '%s:%s' % (self._name, `self._s`)
204 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
206 class StringExpr:
207 def __init__(self, name, expr, engine):
208 self._s = expr
209 if '%' in expr:
210 expr = expr.replace('%', '%%')
211 self._vars = vars = []
212 if '$' in expr:
213 parts = []
214 for exp in expr.split('$$'):
215 if parts: parts.append('$')
216 m = _interp.search(exp)
217 while m is not None:
218 parts.append(exp[:m.start()])
219 parts.append('%s')
220 vars.append(PathExpr('path', m.group(1) or m.group(2),
221 engine))
222 exp = exp[m.end():]
223 m = _interp.search(exp)
224 if '$' in exp:
225 raise CompilerError, (
226 '$ must be doubled or followed by a simple path')
227 parts.append(exp)
228 expr = ''.join(parts)
229 self._expr = expr
231 def __call__(self, econtext):
232 vvals = []
233 for var in self._vars:
234 v = var(econtext)
235 # I hope this isn't in use anymore.
236 ## if isinstance(v, Exception):
237 ## raise v
238 vvals.append(v)
239 return self._expr % tuple(vvals)
241 def __str__(self):
242 return 'string expression %s' % `self._s`
244 def __repr__(self):
245 return 'string:%s' % `self._s`
247 class NotExpr:
248 def __init__(self, name, expr, compiler):
249 self._s = expr = expr.lstrip()
250 self._c = compiler.compile(expr)
252 def __call__(self, econtext):
253 # We use the (not x) and 1 or 0 formulation to avoid changing
254 # the representation of the result in Python 2.3, where the
255 # result of "not" becomes an instance of bool.
256 return (not econtext.evaluateBoolean(self._c)) and 1 or 0
258 def __repr__(self):
259 return 'not:%s' % `self._s`
261 class DeferWrapper:
262 def __init__(self, expr, econtext):
263 self._expr = expr
264 self._econtext = econtext
266 def __str__(self):
267 return str(self())
269 def __call__(self):
270 return self._expr(self._econtext)
272 class DeferExpr:
273 def __init__(self, name, expr, compiler):
274 self._s = expr = expr.lstrip()
275 self._c = compiler.compile(expr)
277 def __call__(self, econtext):
278 return DeferWrapper(self._c, econtext)
280 def __repr__(self):
281 return 'defer:%s' % `self._s`
283 class TraversalError:
284 def __init__(self, path, name):
285 self.path = path
286 self.name = name
290 def restrictedTraverse(object, path, securityManager,
291 get=getattr, has=hasattr, N=None, M=[],
292 TupleType=type(()) ):
294 REQUEST = {'path': path}
295 REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
296 path.reverse()
297 validate = securityManager.validate
298 __traceback_info__ = REQUEST
299 done = []
300 while path:
301 name = path.pop()
302 __traceback_info__ = TraversalError(done, name)
304 if isinstance(name, TupleType):
305 object = object(*name)
306 continue
308 if not name:
309 # Skip directly to item access
310 o = object[name]
311 # Check access to the item.
312 if not validate(object, object, name, o):
313 raise Unauthorized, name
314 object = o
315 continue
317 # Try an attribute.
318 o = guarded_getattr(object, name, M)
319 if o is M:
320 # Try an item.
321 try:
322 # XXX maybe in Python 2.2 we can just check whether
323 # the object has the attribute "__getitem__"
324 # instead of blindly catching exceptions.
325 o = object[name]
326 except AttributeError, exc:
327 if str(exc).find('__getitem__') >= 0:
328 # The object does not support the item interface.
329 # Try to re-raise the original attribute error.
330 # XXX I think this only happens with
331 # ExtensionClass instances.
332 guarded_getattr(object, name)
333 raise
334 except TypeError, exc:
335 if str(exc).find('unsubscriptable') >= 0:
336 # The object does not support the item interface.
337 # Try to re-raise the original attribute error.
338 # XXX This is sooooo ugly.
339 guarded_getattr(object, name)
340 raise
341 done.append((name, o))
342 object = o
344 return object