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 Unauthorized = "Unauthorized"
58 def acquisition_security_filter(orig, inst, name, v, real_validate):
59 if real_validate(orig, inst, name, v):
60 return 1
61 raise Unauthorized, name
63 def call_with_ns(f, ns, arg=1):
64 if arg==2:
65 return f(None, ns)
66 else:
67 return f(ns)
69 class _SecureModuleImporter:
70 """Simple version of the importer for use with trusted code."""
71 __allow_access_to_unprotected_subobjects__ = 1
72 def __getitem__(self, module):
73 __import__(module)
74 return sys.modules[module]
76 SecureModuleImporter = _SecureModuleImporter()
78 Undefs = (Undefined, AttributeError, KeyError,
79 TypeError, IndexError, Unauthorized)
81 def render(ob, ns):
82 """
83 Calls the object, possibly a document template, or just returns it if
84 not callable. (From DT_Util.py)
85 """
86 if hasattr(ob, '__render_with_namespace__'):
87 ob = call_with_ns(ob.__render_with_namespace__, ns)
88 else:
89 base = ob
90 if callable(base):
91 try:
92 if getattr(base, 'isDocTemp', 0):
93 ob = call_with_ns(ob, ns, 2)
94 else:
95 ob = ob()
96 except AttributeError, n:
97 if str(n) != '__call__':
98 raise
99 return ob
101 class SubPathExpr:
102 def __init__(self, path):
103 self._path = path = path.strip().split('/')
104 self._base = base = path.pop(0)
105 if base and not _valid_name(base):
106 raise CompilerError, 'Invalid variable name "%s"' % base
107 # Parse path
108 self._dp = dp = []
109 for i in range(len(path)):
110 e = path[i]
111 if e[:1] == '?' and _valid_name(e[1:]):
112 dp.append((i, e[1:]))
113 dp.reverse()
115 def _eval(self, econtext,
116 list=list, isinstance=isinstance, StringType=type('')):
117 vars = econtext.vars
118 path = self._path
119 if self._dp:
120 path = list(path) # Copy!
121 for i, varname in self._dp:
122 val = vars[varname]
123 if isinstance(val, StringType):
124 path[i] = val
125 else:
126 # If the value isn't a string, assume it's a sequence
127 # of path names.
128 path[i:i+1] = list(val)
129 base = self._base
130 __traceback_info__ = 'path expression "%s"'%('/'.join(self._path))
131 if base == 'CONTEXTS' or not base:
132 ob = econtext.contexts
133 else:
134 ob = vars[base]
135 if isinstance(ob, DeferWrapper):
136 ob = ob()
137 if path:
138 ob = restrictedTraverse(ob, path, getSecurityManager())
139 return ob
141 class PathExpr:
142 def __init__(self, name, expr, engine):
143 self._s = expr
144 self._name = name
145 self._hybrid = 0
146 paths = expr.split('|')
147 self._subexprs = []
148 add = self._subexprs.append
149 for i in range(len(paths)):
150 path = paths[i].lstrip()
151 if _parse_expr(path):
152 # This part is the start of another expression type,
153 # so glue it back together and compile it.
154 add(engine.compile(('|'.join(paths[i:]).lstrip())))
155 self._hybrid = 1
156 break
157 add(SubPathExpr(path)._eval)
159 def _exists(self, econtext):
160 for expr in self._subexprs:
161 try:
162 expr(econtext)
163 except Undefs:
164 pass
165 else:
166 return 1
167 return 0
169 def _eval(self, econtext,
170 isinstance=isinstance, StringType=type(''), render=render):
171 for expr in self._subexprs[:-1]:
172 # Try all but the last subexpression, skipping undefined ones.
173 try:
174 ob = expr(econtext)
175 except Undefs:
176 pass
177 else:
178 break
179 else:
180 # On the last subexpression allow exceptions through, and
181 # don't autocall if the expression was not a subpath.
182 ob = self._subexprs[-1](econtext)
183 if self._hybrid:
184 return ob
186 if self._name == 'nocall' or isinstance(ob, StringType):
187 return ob
188 # Return the rendered object
189 return render(ob, econtext.vars)
191 def __call__(self, econtext):
192 if self._name == 'exists':
193 return self._exists(econtext)
194 return self._eval(econtext)
196 def __str__(self):
197 return '%s expression %s' % (self._name, `self._s`)
199 def __repr__(self):
200 return '%s:%s' % (self._name, `self._s`)
203 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
205 class StringExpr:
206 def __init__(self, name, expr, engine):
207 self._s = expr
208 if '%' in expr:
209 expr = expr.replace('%', '%%')
210 self._vars = vars = []
211 if '$' in expr:
212 parts = []
213 for exp in expr.split('$$'):
214 if parts: parts.append('$')
215 m = _interp.search(exp)
216 while m is not None:
217 parts.append(exp[:m.start()])
218 parts.append('%s')
219 vars.append(PathExpr('path', m.group(1) or m.group(2),
220 engine))
221 exp = exp[m.end():]
222 m = _interp.search(exp)
223 if '$' in exp:
224 raise CompilerError, (
225 '$ must be doubled or followed by a simple path')
226 parts.append(exp)
227 expr = ''.join(parts)
228 self._expr = expr
230 def __call__(self, econtext):
231 vvals = []
232 for var in self._vars:
233 v = var(econtext)
234 # I hope this isn't in use anymore.
235 ## if isinstance(v, Exception):
236 ## raise v
237 vvals.append(v)
238 return self._expr % tuple(vvals)
240 def __str__(self):
241 return 'string expression %s' % `self._s`
243 def __repr__(self):
244 return 'string:%s' % `self._s`
246 class NotExpr:
247 def __init__(self, name, expr, compiler):
248 self._s = expr = expr.lstrip()
249 self._c = compiler.compile(expr)
251 def __call__(self, econtext):
252 # We use the (not x) and 1 or 0 formulation to avoid changing
253 # the representation of the result in Python 2.3, where the
254 # result of "not" becomes an instance of bool.
255 return (not econtext.evaluateBoolean(self._c)) and 1 or 0
257 def __repr__(self):
258 return 'not:%s' % `self._s`
260 class DeferWrapper:
261 def __init__(self, expr, econtext):
262 self._expr = expr
263 self._econtext = econtext
265 def __str__(self):
266 return str(self())
268 def __call__(self):
269 return self._expr(self._econtext)
271 class DeferExpr:
272 def __init__(self, name, expr, compiler):
273 self._s = expr = expr.lstrip()
274 self._c = compiler.compile(expr)
276 def __call__(self, econtext):
277 return DeferWrapper(self._c, econtext)
279 def __repr__(self):
280 return 'defer:%s' % `self._s`
282 class TraversalError:
283 def __init__(self, path, name):
284 self.path = path
285 self.name = name
289 def restrictedTraverse(object, path, securityManager,
290 get=getattr, has=hasattr, N=None, M=[],
291 TupleType=type(()) ):
293 REQUEST = {'path': path}
294 REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
295 path.reverse()
296 validate = securityManager.validate
297 __traceback_info__ = REQUEST
298 done = []
299 while path:
300 name = path.pop()
301 __traceback_info__ = TraversalError(done, name)
303 if isinstance(name, TupleType):
304 object = object(*name)
305 continue
307 if not name:
308 # Skip directly to item access
309 o = object[name]
310 # Check access to the item.
311 if not validate(object, object, name, o):
312 raise Unauthorized, name
313 object = o
314 continue
316 # Try an attribute.
317 o = guarded_getattr(object, name, M)
318 if o is M:
319 # Try an item.
320 try:
321 # XXX maybe in Python 2.2 we can just check whether
322 # the object has the attribute "__getitem__"
323 # instead of blindly catching exceptions.
324 o = object[name]
325 except AttributeError, exc:
326 if str(exc).find('__getitem__') >= 0:
327 # The object does not support the item interface.
328 # Try to re-raise the original attribute error.
329 # XXX I think this only happens with
330 # ExtensionClass instances.
331 guarded_getattr(object, name)
332 raise
333 except TypeError, exc:
334 if str(exc).find('unsubscriptable') >= 0:
335 # The object does not support the item interface.
336 # Try to re-raise the original attribute error.
337 # XXX This is sooooo ugly.
338 guarded_getattr(object, name)
339 raise
340 done.append((name, o))
341 object = o
343 return object