[Zope-Checkins] CVS: Zope2 - davcmds.py:1.8.98.2

Andreas Jung andreas@dhcp165.digicool.com
Mon, 9 Apr 2001 12:49:18 -0400


Update of /cvs-repository/Zope2/lib/python/webdav
In directory yetix:/work/zope/Zope2/lib/python/webdav

Modified Files:
      Tag: zope-2_3-branch
	davcmds.py 
Log Message:
merged into 2.3 branch



--- Updated File davcmds.py in package Zope2 --
--- davcmds.py	2001/03/08 16:19:08	1.8.98.1
+++ davcmds.py	2001/04/09 16:49:17	1.8.98.2
@@ -90,9 +90,13 @@
 import sys, os, string, regex
 from common import absattr, aq_base, urlfix, urlbase
 from OFS.PropertySheets import DAVProperties
+from LockItem import LockItem
+from WriteLockInterface import WriteLockInterface
+from Acquisition import aq_parent
 from xmltools import XmlParser
 from cStringIO import StringIO
 from urllib import quote
+from AccessControl import getSecurityManager
 
 def safe_quote(url, mark=r'%', find=string.find):
     if find(url, mark) > -1:
@@ -209,8 +213,12 @@
         result.write('</d:response>\n')        
         if depth in ('1', 'infinity') and iscol:
             for ob in obj.objectValues():
+                if ob.meta_type=='Broken Because Product is Gone': continue
                 dflag=hasattr(ob, '_p_changed') and (ob._p_changed == None)
-                if hasattr(ob, '__dav_resource__'):
+                if hasattr(ob, '__locknull_resource__'):
+                    # Do nothing, a null resource shouldn't show up to DAV
+                    if dflag: ob._p_deactivate()
+                elif hasattr(ob, '__dav_resource__'):
                     uri=os.path.join(url, absattr(ob.id))
                     depth=depth=='infinity' and depth or 0
                     self.apply(ob, uri, depth, result, top=0)
@@ -338,23 +346,220 @@
 class Lock:
     """Model a LOCK request."""
     def __init__(self, request):
-        self.request=request
-        data=request.get('BODY', '')
-        self.scope='exclusive'
-        self.type='write'
-        self.owner=''
+        self.request = request
+        data = request.get('BODY', '')
+        self.scope = 'exclusive'
+        self.type = 'write'
+        self.owner = ''
+        timeout = request.get_header('Timeout', 'Infinite')
+        self.timeout = string.strip(string.split(timeout,',')[-1])
         self.parse(data)
 
     def parse(self, data, dav='DAV:'):
-        root=XmlParser().parse(data)
-        info=root.elements('lockinfo', ns=dav)[0]
-        ls=info.elements('lockscope', ns=dav)[0]
-        self.scope=ls.elements()[0].name()
-        lt=info.elements('locktype', ns=dav)[0]
-        self.type=lt.elements()[0].name()
-        lo=info.elements('owner', ns=dav)
-        if lo: self.owner=lo[0].toxml()
+        root = XmlParser().parse(data)
+        info = root.elements('lockinfo', ns=dav)[0]
+        ls = info.elements('lockscope', ns=dav)[0]
+        self.scope = ls.elements()[0].name()
+        lt = info.elements('locktype', ns=dav)[0]
+        self.type = lt.elements()[0].name()
+
+        lockowner = info.elements('owner', ns=dav)
+        if lockowner:
+            # Since the Owner element may contain children in different
+            # namespaces (or none at all), we have to find them for potential
+            # remapping.  Note that Cadaver doesn't use namespaces in the
+            # XML it sends.
+            lockowner = lockowner[0]
+            for el in lockowner.elements():
+                name, elns = el.name(), el.namespace()
+                if not elns:
+                    # There's no namespace, so we have to add one
+                    lockowner.remap({dav:'ot'})
+                    el.__nskey__ = 'ot'
+                    for subel in el.elements():
+                        if not subel.namespace():
+                            el.__nskey__ = 'ot'
+                else:
+                    el.remap({dav:'o'})
+            self.owner = lockowner.strval()
 
+    def apply(self, obj, creator=None, depth='infinity', token=None,
+              result=None, url=None, top=1):
+        """ Apply, built for recursion (so that we may lock subitems
+        of a collection if requested """
+        if result is None:
+            result = StringIO()
+            url = urlfix(self.request['URL'], 'LOCK')
+            url = urlbase(url)
+        iscol = hasattr(obj, '__dav_collection__')
+        if iscol and url[-1] != '/': url = url + '/'
+        errmsg = None
+        lock = None
+
+        try:
+            lock = LockItem(creator, self.owner, depth, self.timeout,
+                            self.type, self.scope, token)
+            if token is None: token = lock.getLockToken()
+        except ValueError, valerrors:
+            errmsg = "412 Precondition Failed"
+        except:
+            errmsg = "403 Forbidden"
+
+        try:
+            if not WriteLockInterface.isImplementedBy(obj):
+                if top:
+                    # This is the top level object in the apply, so we
+                    # do want an error
+                    errmsg = "405 Method Not Allowed"
+                else:
+                    # We're in an infinity request and a subobject does
+                    # not support locking, so we'll just pass
+                    pass
+            elif obj.wl_isLocked():
+                errmsg = "423 Locked"
+            else:
+                method = getattr(obj, 'wl_setLock')
+                vld = getSecurityManager().validate(None, obj, 'wl_setLock',
+                                                    method)
+                if vld and token and (lock is not None):
+                    obj.wl_setLock(token, lock)
+                else:
+                    errmsg = "403 Forbidden"
+        except:
+            errmsg = "403 Forbidden"
+            
+        if errmsg:
+            if top and ((depth in (0, '0')) or (not iscol)):
+                # We don't need to raise multistatus errors
+                raise errmsg[4:]
+            elif not result.getvalue():
+                # We haven't had any errors yet, so our result is empty
+                # and we need to set up the XML header
+                result.write('<?xml version="1.0" encoding="utf-8" ?>\n' \
+                             '<d:multistatus xmlns:d="DAV:">\n')
+            result.write('<d:response>\n <d:href>%s</d:href>\n' % url)
+            result.write(' <d:status>HTTP/1.1 %s</d:status>\n' % errmsg)
+            result.write('</d:response>\n')
+
+        if depth == 'infinity' and iscol:
+            for ob in obj.objectValues():
+                if hasattr(obj, '__dav_resource__'):
+                    uri = os.path.join(url, absattr(ob.id))
+                    self.apply(ob, creator, depth, token, result,
+                               uri, top=0)
+        if not top: return token, result
+        if result.getvalue():
+            # One or more subitems probably failed, so close the multistatus
+            # element and clear out all succesful locks
+            result.write('</d:multistatus>')
+            get_transaction().abort() # This *SHOULD* clear all succesful locks
+        return token, result.getvalue()
+    
 
+class Unlock:
+    """ Model an Unlock request """
 
+    def apply(self, obj, token, url=None, result=None, top=1):
+        if result is None:
+            result = StringIO()
+            url = urlfix(url, 'UNLOCK')
+            url = urlbase(url)
+        iscol = hasattr(obj, '__dav_collection__')
+        if iscol and url[-1] != '/': url = url + '/'
+        errmsg = None
+
+        islockable = WriteLockInterface.isImplementedBy(obj)
+
+        if islockable and obj.wl_hasLock(token):
+            method = getattr(obj, 'wl_delLock')
+            vld = getSecurityManager().validate(None,obj,'wl_delLock',method)
+            if vld: obj.wl_delLock(token)
+            else: errmsg = "403 Forbidden"
+        elif not islockable:
+            # Only set an error message if the command is being applied
+            # to a top level object.  Otherwise, we're descending a tree
+            # which may contain many objects that don't implement locking,
+            # so we just want to avoid them
+            if top: errmsg = "405 Method Not Allowed"
+
+        if errmsg:
+            if top and (not iscol):
+                # We don't need to raise multistatus errors
+                if errmsg[:3] == '403': raise "Forbidden"
+                else: raise "Precondition Failed"
+            elif not result.getvalue():
+                # We haven't had any errors yet, so our result is empty
+                # and we need to set up the XML header
+                result.write('<?xml version="1.0" encoding="utf-8" ?>\n' \
+                             '<d:multistatus xmlns:d="DAV:">\n')
+            result.write('<d:response>\n <d:href>%s</d:href>\n' % url)
+            result.write(' <d:status>HTTP/1.1 %s</d:status>\n' % errmsg)
+            result.write('</d:response>\n')
 
+        if iscol:
+            for ob in obj.objectValues():
+                if hasattr(ob, '__dav_resource__') and \
+                   WriteLockInterface.isImplementedBy(ob):
+                    uri = os.path.join(url, absattr(ob.id))
+                    self.apply(ob, token, uri, result, top=0)
+        if not top: return result
+        if result.getvalue():
+            # One or more subitems probably failed, so close the multistatus
+            # element and clear out all succesful unlocks
+            result.write('</d:multistatus>')
+            get_transaction().abort()
+        return result.getvalue()
+    
+
+class DeleteCollection:
+    """ With WriteLocks in the picture, deleting a collection involves
+    checking *all* descendents (deletes on collections are always of depth
+    infinite) for locks and if the locks match. """
+
+    def apply(self, obj, token, user, url=None, result=None, top=1):
+        if result is None:
+            result = StringIO()
+            url = urlfix(url, 'DELETE')
+            url = urlbase(url)
+        iscol = hasattr(obj, '__dav_collection__')
+        errmsg = None
+        parent = aq_parent(obj)
+
+        islockable = WriteLockInterface.isImplementedBy(obj)
+        if parent and (not user.has_permission('Delete objects', parent)):
+            # User doesn't have permission to delete this object
+            errmsg = "403 Forbidden"
+        elif islockable and obj.wl_isLocked():
+            if token and obj.wl_hasLock(token):
+                # Object is locked, and the token matches (no error)
+                errmsg = ""
+            else:
+                errmsg = "423 Locked"
+
+        if errmsg:
+            if top and (not iscol):
+                err = errmsg[4:]
+                raise err
+            elif not result.getvalue():
+                # We haven't had any errors yet, so our result is empty
+                # and we need to set up the XML header
+                result.write('<?xml version="1.0" encoding="utf-8" ?>\n' \
+                             '<d:multistatus xmlns:d="DAV:">\n')
+            result.write('<d:response>\n <d:href>%s</d:href>\n' % url)
+            result.write(' <d:status>HTTP/1.1 %s</d:status>\n' % errmsg)
+            result.write('</d:response>\n')
+
+        if iscol:
+            for ob in obj.objectValues():
+                dflag = hasattr(ob,'_p_changed') and (ob._p_changed == None)
+                if hasattr(ob, '__dav_resource__'):
+                    uri = os.path.join(url, absattr(ob.id))
+                    self.apply(ob, token, user, uri, result, top=0)
+                    if dflag: ob._p_deactivate()
+        if not top: return result
+        if result.getvalue():
+            # One or more subitems can't be delted, so close the multistatus
+            # element
+            result.write('</d:multistatus>\n')
+        return result.getvalue()
+