############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved. # # 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. $Id: Traversable.py 70153 2006-09-13 16:16:02Z tseaver $ """ from urllib import quote from Globals import InitializeClass from AccessControl import ClassSecurityInfo from AccessControl import getSecurityManager from AccessControl import Unauthorized from AccessControl.ZopeGuards import guarded_getattr from Acquisition import Acquired, aq_inner, aq_parent, aq_base from zExceptions import NotFound from ZODB.POSException import ConflictError from OFS.interfaces import ITraversable import webdav from zope.interface import implements, Interface from zope.component import queryMultiAdapter from zope.traversing.interfaces import TraversalError from zope.traversing.namespace import nsParse, namespaceLookup _marker = object() class Traversable: implements(ITraversable) security = ClassSecurityInfo() security.declarePublic('absolute_url') 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 = self.REQUEST.physicalPathToURL except AttributeError: return path2url(spp[1:]) return toUrl(spp) security.declarePublic('absolute_url_path') 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 = self.REQUEST.physicalPathToURL except AttributeError: return path2url(spp) or '/' return toUrl(spp, relative=1) or '/' security.declarePublic('virtual_url_path') 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 = self.REQUEST.physicalPathToVirtualPath except AttributeError: return path2url(spp[1:]) return path2url(toVirt(spp)) security.declarePrivate('getPhysicalRoot') getPhysicalRoot=Acquired security.declarePublic('getPhysicalPath') 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. """ path = (self.getId(),) p = aq_parent(aq_inner(self)) if p is not None: path = p.getPhysicalPath() + path return path security.declarePrivate('unrestrictedTraverse') 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): # Unicode paths are not allowed path = path.split('/') else: path = list(path) 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 and not validate(None, None, None, obj): raise Unauthorized(name) else: obj = self 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 != '+': # Process URI segment parameters. ns, nm = nsParse(name) if ns: try: next = namespaceLookup( ns, nm, obj, self.REQUEST).__of__(obj) if restricted and not validate( obj, obj, name, next): raise Unauthorized(name) except TraversalError: raise AttributeError(name) elif 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, 'im_self', None) is not None: # Bound method, the bound instance # is the container container = next.im_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): raise Unauthorized(name) else: 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, webdav.NullResource.NullResource): resource = next raise KeyError(name) except AttributeError: # Raise NotFound for easier debugging # instead of AttributeError: __getitem__ raise NotFound(name) if restricted and not validate( obj, obj, None, next): raise Unauthorized(name) except (AttributeError, NotFound, KeyError), e: # Try to look for a view next = queryMultiAdapter((obj, self.REQUEST), Interface, name) if next is not None: 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: if default is not _marker: return default else: raise security.declarePublic('restrictedTraverse') 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))