/
Traversable.py
371 lines (322 loc) · 14.9 KB
/
Traversable.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
"""This module implements a mix-in for traversable objects.
"""
from urllib.parse import quote
from AccessControl.class_init import InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from AccessControl.unauthorized import Unauthorized
from AccessControl.ZopeGuards import guarded_getattr
from Acquisition import Acquired
from Acquisition import aq_acquire
from Acquisition import aq_base
from Acquisition import aq_inner
from Acquisition import aq_parent
from Acquisition.interfaces import IAcquirer
from OFS.interfaces import IApplication
from OFS.interfaces import ITraversable
from zExceptions import NotFound
from ZODB.POSException import ConflictError
from zope.component import queryMultiAdapter
from zope.interface import Interface
from zope.interface import implementer
from zope.location.interfaces import LocationError
from zope.traversing.namespace import namespaceLookup
from zope.traversing.namespace import nsParse
from ZPublisher.interfaces import UseTraversalDefault
_marker = object()
@implementer(ITraversable)
class Traversable:
security = ClassSecurityInfo()
@security.public
def absolute_url(self, relative=0):
"""Return the absolute URL of the object.
This a canonical URL based on the object's physical
containment path. It is affected by the virtual host
configuration, if any, and can be used by external
agents, such as a browser, to address the object.
If the relative argument is provided, with a true value, then
the value of virtual_url_path() is returned.
Some Products incorrectly use '/'+absolute_url(1) as an
absolute-path reference. This breaks in certain virtual
hosting situations, and should be changed to use
absolute_url_path() instead.
"""
if relative:
return self.virtual_url_path()
spp = self.getPhysicalPath()
try:
toUrl = aq_acquire(self, 'REQUEST').physicalPathToURL
except AttributeError:
return path2url(spp[1:])
return toUrl(spp)
@security.public
def absolute_url_path(self):
"""Return the path portion of the absolute URL of the object.
This includes the leading slash, and can be used as an
'absolute-path reference' as defined in RFC 2396.
"""
spp = self.getPhysicalPath()
try:
toUrl = aq_acquire(self, 'REQUEST').physicalPathToURL
except AttributeError:
return path2url(spp) or '/'
return toUrl(spp, relative=1) or '/'
@security.public
def virtual_url_path(self):
"""Return a URL for the object, relative to the site root.
If a virtual host is configured, the URL is a path relative to
the virtual host's root object. Otherwise, it is the physical
path. In either case, the URL does not begin with a slash.
"""
spp = self.getPhysicalPath()
try:
toVirt = aq_acquire(self, 'REQUEST').physicalPathToVirtualPath
except AttributeError:
return path2url(spp[1:])
return path2url(toVirt(spp))
# decorators did not work on variables
security.declarePrivate('getPhysicalRoot') # NOQA: D001
getPhysicalRoot = Acquired
@security.public
def getPhysicalPath(self):
# Get the physical path of the object.
#
# Returns a path (an immutable sequence of strings) that can be used to
# access this object again later, for example in a copy/paste
# operation. getPhysicalRoot() and getPhysicalPath() are designed to
# operate together.
# This implementation is optimized to avoid excessive amounts of
# function calls while walking up from an object on a deep level.
try:
id = self.id or self.getId()
except AttributeError:
id = self.getId()
path = (id, )
p = aq_parent(aq_inner(self))
if p is None:
return path
func = self.getPhysicalPath.__func__
while p is not None:
if func is p.getPhysicalPath.__func__:
try:
pid = p.id or p.getId()
except AttributeError:
pid = p.getId()
path = (pid, ) + path
p = aq_parent(aq_inner(p))
else:
if IApplication.providedBy(p):
path = ('', ) + path
else:
path = p.getPhysicalPath() + path
break
return path
@security.private
def unrestrictedTraverse(self, path, default=_marker, restricted=False):
"""Lookup an object by path.
path -- The path to the object. May be a sequence of strings or a slash
separated string. If the path begins with an empty path element
(i.e., an empty string or a slash) then the lookup is performed
from the application root. Otherwise, the lookup is relative to
self. Two dots (..) as a path element indicates an upward traversal
to the acquisition parent.
default -- If provided, this is the value returned if the path cannot
be traversed for any reason (i.e., no object exists at that path or
the object is inaccessible).
restricted -- If false (default) then no security checking is
performed. If true, then all of the objects along the path are
validated with the security machinery. Usually invoked using
restrictedTraverse().
"""
if not path:
return self
if isinstance(path, str):
path = path.split('/')
else:
path = list(path)
for part in path:
if not isinstance(part, str):
raise TypeError(
"path should be a string or an iterable of strings"
)
REQUEST = {'TraversalRequestNameStack': path}
path.reverse()
path_pop = path.pop
if len(path) > 1 and not path[0]:
# Remove trailing slash
path_pop(0)
if restricted:
validate = getSecurityManager().validate
if not path[-1]:
# If the path starts with an empty string, go to the root first.
path_pop()
obj = self.getPhysicalRoot()
if restricted:
validate(None, None, None, obj) # may raise Unauthorized
else:
obj = self
# import time ordering problem
from webdav.NullResource import NullResource
resource = _marker
try:
while path:
name = path_pop()
__traceback_info__ = path, name
if name[0] == '_':
# Never allowed in a URL.
raise NotFound(name)
if name == '..':
next = aq_parent(obj)
if next is not None:
if restricted and not validate(obj, obj, name, next):
raise Unauthorized(name)
obj = next
continue
bobo_traverse = getattr(obj, '__bobo_traverse__', None)
try:
if (
name
and name[:1] in '@+'
and name != '+'
and nsParse(name)[1]
):
# Process URI segment parameters.
ns, nm = nsParse(name)
try:
next = namespaceLookup(
ns, nm, obj, aq_acquire(self, 'REQUEST'))
if IAcquirer.providedBy(next):
next = next.__of__(obj)
if restricted and not validate(
obj, obj, name, next):
raise Unauthorized(name)
except LocationError:
raise AttributeError(name)
else:
next = UseTraversalDefault # indicator
try:
if bobo_traverse is not None:
next = bobo_traverse(REQUEST, name)
if restricted:
if aq_base(next) is not next:
# The object is wrapped, so the
# acquisition context is the container.
container = aq_parent(aq_inner(next))
elif getattr(next, '__self__',
None) is not None:
# Bound method, the bound instance
# is the container
container = next.__self__
elif getattr(
aq_base(obj),
name, _marker) is next:
# Unwrapped direct attribute of the
# object so object is the container
container = obj
else:
# Can't determine container
container = None
# If next is a simple unwrapped property,
# its parentage is indeterminate, but it
# may have been acquired safely. In this
# case validate will raise an error, and
# we can explicitly check that our value
# was acquired safely.
try:
ok = validate(
obj, container, name, next)
except Unauthorized:
ok = False
if not ok:
if (
container is not None
or guarded_getattr(obj, name, _marker) is not next # NOQA: E501
):
raise Unauthorized(name)
except UseTraversalDefault:
# behave as if there had been no
# '__bobo_traverse__'
bobo_traverse = None
if next is UseTraversalDefault:
if getattr(
aq_base(obj),
name, _marker) is not _marker:
if restricted:
next = guarded_getattr(obj, name)
else:
next = getattr(obj, name)
else:
try:
next = obj[name]
# The item lookup may return a
# NullResource, if this is the case we
# save it and return it if all other
# lookups fail.
if isinstance(next, NullResource):
resource = next
raise KeyError(name)
except (AttributeError, TypeError):
# Raise NotFound for easier debugging
# instead of AttributeError: __getitem__
# or TypeError: not subscriptable
raise NotFound(name)
if restricted and not validate(
obj, obj, None, next):
raise Unauthorized(name)
except (AttributeError, NotFound, KeyError) as e:
# Try to look for a view
next = queryMultiAdapter(
(obj, aq_acquire(self, 'REQUEST')),
Interface, name)
if next is not None:
if IAcquirer.providedBy(next):
next = next.__of__(obj)
if restricted and not validate(obj, obj, name, next):
raise Unauthorized(name)
elif bobo_traverse is not None:
# Attribute lookup should not be done after
# __bobo_traverse__:
raise e
else:
# No view, try acquired attributes
try:
if restricted:
next = guarded_getattr(obj, name, _marker)
else:
next = getattr(obj, name, _marker)
except AttributeError:
raise e
if next is _marker:
# If we have a NullResource from earlier use it.
next = resource
if next is _marker:
# Nothing found re-raise error
raise e
obj = next
return obj
except ConflictError:
raise
except Exception:
if default is not _marker:
return default
else:
raise
@security.public
def restrictedTraverse(self, path, default=_marker):
# Trusted code traversal code, always enforces securitys
return self.unrestrictedTraverse(path, default, restricted=True)
InitializeClass(Traversable)
def path2url(path):
return '/'.join(map(quote, path))