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 ##############################################################################
14 """Page Template Expression Engine
16 Page Template-specific implementation of TALES, with handlers
17 for Python expressions, string literals, and paths.
18 """
20 __version__='$Revision: 1.1 $'[11:-2]
22 import re, sys
23 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
24 Undefined, Default, _parse_expr
25 from string import strip, split, join, replace, lstrip
26 from Acquisition import aq_base, aq_inner, aq_parent
29 _engine = None
30 def getEngine():
31 global _engine
32 if _engine is None:
33 from PathIterator import Iterator
34 _engine = Engine(Iterator)
35 installHandlers(_engine)
36 return _engine
38 def installHandlers(engine):
39 reg = engine.registerType
40 pe = PathExpr
41 for pt in ('standard', 'path', 'exists', 'nocall'):
42 reg(pt, pe)
43 reg('string', StringExpr)
44 reg('python', PythonExpr)
45 reg('not', NotExpr)
46 reg('defer', DeferExpr)
48 if sys.modules.has_key('Zope'):
49 import AccessControl
50 from AccessControl import getSecurityManager
51 try:
52 from AccessControl import Unauthorized
53 except ImportError:
54 Unauthorized = "Unauthorized"
55 if hasattr(AccessControl, 'full_read_guard'):
56 from ZRPythonExpr import PythonExpr, _SecureModuleImporter, \
57 call_with_ns
58 else:
59 from ZPythonExpr import PythonExpr, _SecureModuleImporter, \
60 call_with_ns
61 else:
62 from PythonExpr import getSecurityManager, PythonExpr
63 try:
64 from zExceptions import Unauthorized
65 except ImportError:
66 Unauthorized = "Unauthorized"
67 def call_with_ns(f, ns, arg=1):
68 if arg==2:
69 return f(None, ns)
70 else:
71 return f(ns)
73 class _SecureModuleImporter:
74 """Simple version of the importer for use with trusted code."""
75 __allow_access_to_unprotected_subobjects__ = 1
76 def __getitem__(self, module):
77 __import__(module)
78 return sys.modules[module]
80 SecureModuleImporter = _SecureModuleImporter()
82 Undefs = (Undefined, AttributeError, KeyError,
83 TypeError, IndexError, Unauthorized)
85 def render(ob, ns):
86 """
87 Calls the object, possibly a document template, or just returns it if
88 not callable. (From DT_Util.py)
89 """
90 if hasattr(ob, '__render_with_namespace__'):
91 ob = call_with_ns(ob.__render_with_namespace__, ns)
92 else:
93 base = aq_base(ob)
94 if callable(base):
95 try:
96 if getattr(base, 'isDocTemp', 0):
97 ob = call_with_ns(ob, ns, 2)
98 else:
99 ob = ob()
100 except AttributeError, n:
101 if str(n) != '__call__':
102 raise
103 return ob
105 class SubPathExpr:
106 def __init__(self, path):
107 self._path = path = split(strip(path), '/')
108 self._base = base = path.pop(0)
109 if not _valid_name(base):
110 raise CompilerError, 'Invalid variable name "%s"' % base
111 # Parse path
112 self._dp = dp = []
113 for i in range(len(path)):
114 e = path[i]
115 if e[:1] == '?' and _valid_name(e[1:]):
116 dp.append((i, e[1:]))
117 dp.reverse()
119 def _eval(self, econtext,
120 list=list, isinstance=isinstance, StringType=type('')):
121 vars = econtext.vars
122 path = self._path
123 if self._dp:
124 path = list(path) # Copy!
125 for i, varname in self._dp:
126 val = vars[varname]
127 if isinstance(val, StringType):
128 path[i] = val
129 else:
130 # If the value isn't a string, assume it's a sequence
131 # of path names.
132 path[i:i+1] = list(val)
133 __traceback_info__ = base = self._base
134 if base == 'CONTEXTS':
135 ob = econtext.contexts
136 else:
137 ob = vars[base]
138 if isinstance(ob, DeferWrapper):
139 ob = ob()
140 if path:
141 ob = restrictedTraverse(ob, path, getSecurityManager())
142 return ob
144 class PathExpr:
145 def __init__(self, name, expr, engine):
146 self._s = expr
147 self._name = name
148 paths = split(expr, '|')
149 self._subexprs = []
150 add = self._subexprs.append
151 for i in range(len(paths)):
152 path = lstrip(paths[i])
153 if _parse_expr(path):
154 # This part is the start of another expression type,
155 # so glue it back together and compile it.
156 add(engine.compile(lstrip(join(paths[i:], '|'))))
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.
182 ob = self._subexprs[-1](econtext)
184 if self._name == 'nocall' or isinstance(ob, StringType):
185 return ob
186 # Return the rendered object
187 return render(ob, econtext.vars)
189 def __call__(self, econtext):
190 if self._name == 'exists':
191 return self._exists(econtext)
192 return self._eval(econtext)
194 def __str__(self):
195 return '%s expression %s' % (self._name, `self._s`)
197 def __repr__(self):
198 return '%s:%s' % (self._name, `self._s`)
201 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/%(n)s)*)}' % {'n': NAME_RE})
203 class StringExpr:
204 def __init__(self, name, expr, engine):
205 self._s = expr
206 if '%' in expr:
207 expr = replace(expr, '%', '%%')
208 self._vars = vars = []
209 if '$' in expr:
210 parts = []
211 for exp in split(expr, '$$'):
212 if parts: parts.append('$')
213 m = _interp.search(exp)
214 while m is not None:
215 parts.append(exp[:m.start()])
216 parts.append('%s')
217 vars.append(PathExpr('path', m.group(1) or m.group(2),
218 engine))
219 exp = exp[m.end():]
220 m = _interp.search(exp)
221 if '$' in exp:
222 raise CompilerError, (
223 '$ must be doubled or followed by a simple path')
224 parts.append(exp)
225 expr = join(parts, '')
226 self._expr = expr
228 def __call__(self, econtext):
229 vvals = []
230 for var in self._vars:
231 v = var(econtext)
232 if isinstance(v, Exception):
233 raise v
234 vvals.append(v)
235 return self._expr % tuple(vvals)
237 def __str__(self):
238 return 'string expression %s' % `self._s`
240 def __repr__(self):
241 return 'string:%s' % `self._s`
243 class NotExpr:
244 def __init__(self, name, expr, compiler):
245 self._s = expr = lstrip(expr)
246 self._c = compiler.compile(expr)
248 def __call__(self, econtext):
249 return not econtext.evaluateBoolean(self._c)
251 def __repr__(self):
252 return 'not:%s' % `self._s`
254 class DeferWrapper:
255 def __init__(self, expr, econtext):
256 self._expr = expr
257 self._econtext = econtext
259 def __str__(self):
260 return str(self())
262 def __call__(self):
263 return self._expr(self._econtext)
265 class DeferExpr:
266 def __init__(self, name, expr, compiler):
267 self._s = expr = lstrip(expr)
268 self._c = compiler.compile(expr)
270 def __call__(self, econtext):
271 return DeferWrapper(self._c, econtext)
273 def __repr__(self):
274 return 'defer:%s' % `self._s`
277 def restrictedTraverse(self, path, securityManager,
278 get=getattr, has=hasattr, N=None, M=[],
279 TupleType=type(()) ):
281 REQUEST = {'path': path}
282 REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
283 if not path[0]:
284 # If the path starts with an empty string, go to the root first.
285 self = self.getPhysicalRoot()
286 if not securityManager.validateValue(self):
287 raise Unauthorized, name
288 path.pop(0)
290 path.reverse()
291 validate = securityManager.validate
292 object = self
293 #print 'TRAVERSE', (object, path)
294 while path:
295 __traceback_info__ = REQUEST
296 name = path.pop()
298 if isinstance(name, TupleType):
299 object = apply(object, name)
300 continue
302 if name[0] == '_':
303 # Never allowed in a URL.
304 raise AttributeError, name
306 if name=='..':
307 o = get(object, 'aq_parent', M)
308 if o is not M:
309 if not validate(object, object, name, o):
310 raise Unauthorized, name
311 object=o
312 continue
314 t = get(object, '__bobo_traverse__', N)
315 if t is not N:
316 o=t(REQUEST, name)
318 container = None
319 if has(o, 'im_self'):
320 container = o.im_self
321 elif (has(get(object, 'aq_base', object), name)
322 and get(object, name) == o):
323 container = object
324 if not validate(object, container, name, o):
325 raise Unauthorized, name
326 else:
327 # Try an attribute.
328 o = get(object, name, M)
329 # print '...', (object, name, M, o)
330 if o is not M:
331 # Check access to the attribute.
332 if has(object, 'aq_acquire'):
333 object.aq_acquire(
334 name, validate2, validate)
335 else:
336 if not validate(object, object, name, o):
337 raise Unauthorized, name
338 else:
339 # Try an item.
340 try:
341 # XXX maybe in Python 2.2 we can just check whether
342 # the object has the attribute "__getitem__"
343 # instead of blindly catching exceptions.
344 # print 'Try an item', (object, name)
345 o = object[name]
346 except AttributeError, exc:
347 if str(exc).find('__getitem__') >= 0:
348 # The object does not support the item interface.
349 # Try to re-raise the original attribute error.
350 # XXX I think this only happens with
351 # ExtensionClass instances.
352 get(object, name)
353 raise
354 except TypeError, exc:
355 if str(exc).find('unsubscriptable') >= 0:
356 # The object does not support the item interface.
357 # Try to re-raise the original attribute error.
358 # XXX This is sooooo ugly.
359 get(object, name)
360 raise
361 else:
362 # Check access to the item.
363 if not validate(object, object, name, o):
364 raise Unauthorized, name
365 #print '... object is now', `o`
366 object = o
368 return object
371 def validate2(orig, inst, name, v, real_validate):
372 if not real_validate(orig, inst, name, v):
373 raise Unauthorized, name
374 return 1