Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/webdav/WebdavClient.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/webdav/WebdavClient.py')
-rw-r--r--src/webdav/WebdavClient.py840
1 files changed, 840 insertions, 0 deletions
diff --git a/src/webdav/WebdavClient.py b/src/webdav/WebdavClient.py
new file mode 100644
index 0000000..ab5cec3
--- /dev/null
+++ b/src/webdav/WebdavClient.py
@@ -0,0 +1,840 @@
+# pylint: disable-msg=R0904,W0142,W0511,W0104,C0321,E1103,W0212
+#
+# Copyright 2008 German Aerospace Center (DLR)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+This module contains the classes ResourceStorer and CollectionStorer for accessing WebDAV resources.
+"""
+
+
+from davlib import XML_CONTENT_TYPE
+
+from urlparse import urlsplit
+import re
+import types
+import sys
+import os
+import shutil
+import socket
+
+from webdav import Constants
+from webdav.WebdavResponse import LiveProperties
+from webdav.WebdavRequests import createFindBody, createUpdateBody, createDeleteBody, createSearchBody
+from webdav.Condition import ConditionTerm
+from webdav.Connection import Connection, WebdavError, AuthorizationError
+from webdav.VersionHandler import VersionHandler
+
+from webdav.acp.Privilege import Privilege
+from webdav.acp.Acl import ACL
+from webdav.NameCheck import validateResourceName, WrongNameError
+
+
+__version__ = '$Revision$'[11:-2]
+
+SOCKET_DEFAULT_TIMEOUT = 10
+
+
+def switchUnicodeUrlOn(switch):
+ """
+ Configure whether to use unicode (UTF-8) encoded URLs (default) or
+ Latin-1 encoded URLs.
+
+ @param switch: 1 if unicode URLs shall be used
+ """
+
+ assert switch == 0 or switch == 1, "Pass boolean argument, please."
+ Constants.CONFIG_UNICODE_URL = switch
+
+
+def parseDigestAuthInfo(authInfo):
+ """
+ Parses the authentication information returned from a server and returns
+ a dictionary containing realm, qop, and nonce.
+
+ @see: L{AuthorizationError<webdav.Connection.AuthorizationError>}
+ or the main function of this module.
+ """
+
+ info = dict()
+ info["realm"] = re.search('realm="([^"]+)"', authInfo).group(1)
+ info["qop"] = re.search('qop="([^"]+)"', authInfo).group(1)
+ info["nonce"] = re.search('nonce="([^"]+)"', authInfo).group(1)
+ return info
+
+
+class ResourceStorer(object):
+ """
+ This class provides client access to a WebDAV resource
+ identified by an URI. It provides all WebDAV class 2 features which include
+ uploading data, getting and setting properties qualified by a XML name space,
+ locking and unlocking the resource.
+ This class does not cache resource data. This has to be performed by its clients.
+
+ @author: Roland Betz
+ """
+
+ # Instance properties
+ url = property(lambda self: str(self.connection) + self.path, None, None, "Resource's URL")
+
+ def __init__(self, url, connection=None, validateResourceNames=True):
+ """
+ Creates an instance for the given URL
+ User must invoke validate() after construction to check the resource on the server.
+
+ @param url: Unique resource location for this storer.
+ @type url: C{string}
+ @param connection: this optional parameter contains a Connection object
+ for the host part of the given URL. Passing a connection saves
+ memory by sharing this connection. (defaults to None)
+ @type connection: L{webdav.Connection}
+ @raise WebdavError: If validation of resource name path parts fails.
+ """
+
+ assert connection == None or isinstance(connection, Connection)
+ parts = urlsplit(url, allow_fragments=False)
+ self.path = parts[2]
+ self.validateResourceNames = validateResourceNames
+
+ # validate URL path
+ for part in self.path.split('/'):
+ if part != '' and not "ino:" in part: # explicitly allowing this character sequence as a part of a path (Tamino 4.4)
+ if self.validateResourceNames:
+ try:
+ validateResourceName(part)
+ except WrongNameError:
+ raise WebdavError("Found invalid resource name part.")
+ self.name = part
+ # was: filter(lambda part: part and validateResourceName(part), self.path.split('/'))
+ # but filter is deprecated
+
+ self.defaultNamespace = None # default XML name space of properties
+ if connection:
+ self.connection = connection
+ else:
+ conn = parts[1].split(":")
+ if len(conn) == 1:
+ self.connection = Connection(conn[0], protocol = parts[0]) # host and protocol
+ else:
+ self.connection = Connection(conn[0], int(conn[1]), protocol = parts[0]) # host and port and protocol
+ self.versionHandler = VersionHandler(self.connection, self.path)
+
+
+ def validate(self):
+ """
+ Check whether URL contains a WebDAV resource
+ Uses the WebDAV OPTIONS method.
+
+ @raise WebdavError: L{WebdavError} if URL does not contain a WebDAV resource
+ """
+ #davHeader = response.getheader(HTTP_HEADER_DAV)
+ davHeader = self.getSpecificOption(Constants.HTTP_HEADER_DAV)
+ self.connection.logger.debug("HEADER DAV: %s" % davHeader)
+ if not(davHeader) or davHeader.find("2") < 0: # DAV class 2 supported ?
+ raise WebdavError("URL does not support WebDAV", 0)
+
+ def options(self):
+ """
+ Send an OPTIONS request to server and return all HTTP headers.
+
+ @return: map of all HTTP headers returned by the OPTIONS method.
+ """
+ response = self.connection.options(self.path)
+ result = {}
+ result.update(response.msg)
+ self.connection.logger.debug("OPTION returns: " + str(result.keys()))
+ return result
+
+ def _getAclSupportAvailable(self):
+ """
+ Returns True if the current connection has got ACL support.
+
+ @return: ACL support (True / False)
+ @rtype: C{bool}
+ """
+ options = self.getSpecificOption(Constants.HTTP_HEADER_DAV)
+ if options.find(Constants.HTTP_HEADER_OPTION_ACL) >= 0:
+ return True
+ else:
+ return False
+
+ aclSupportAvailable = property(_getAclSupportAvailable)
+
+ def _getDaslBasicsearchSupportAvailable(self):
+ """
+ Returns True if the current connection supports DASL basic search.
+
+ @return: DASL basic search support (True / False)
+ @rtype: C{bool}
+ """
+ options = self.getSpecificOption(Constants.HTTP_HEADER_DASL)
+ if not options or \
+ not options.find(Constants.HTTP_HEADER_OPTION_DAV_BASIC_SEARCH) >= 0:
+ return False
+ else:
+ return True
+
+ daslBasicsearchSupportAvailable = property(_getDaslBasicsearchSupportAvailable)
+
+ def isConnectedToCatacombServer(self):
+ """
+ Returns True if connected to a Catacomb WebDav server.
+
+ @return: if connected to Catacomb Webdav server (True / False)
+ @rtype: C{bool}
+ """
+ if not self.connection.serverTypeChecked:
+ options = self.getSpecificOption(Constants.HTTP_HEADER_SERVER)
+ if options.find(Constants.HTTP_HEADER_SERVER_TAMINO) >= 0:
+ self.connection.isConnectedToCatacomb = False
+ else:
+ self.connection.isConnectedToCatacomb = True
+ self.connection.serverTypeChecked = True
+ return self.connection.isConnectedToCatacomb
+
+ def getSpecificOption(self, option):
+ """
+ Returns specified WebDav options.
+ @param option: name of the option
+
+ @return: String containing the value of the option.
+ @rtype: C{string}
+ """
+ options = ''
+ try:
+ options = self.options().get(option)
+ except KeyError:
+ return options
+ return options
+
+ ### delegate some method invocations
+ def __getattr__(self, name):
+ """
+ Build-in method:
+ Forwards unknow lookups (methods) to delegate object 'versionHandler'.
+
+ @param name: name of unknown attribute
+ """
+ # delegate Delta-V methods
+ return getattr(self.versionHandler, name)
+
+ def copy(self, toUrl, infinity=True):
+ """
+ Copies this resource.
+
+ @param toUrl: target URI path
+ @param infinity: Flag that indicates that the complete content of collection is copied. (default)
+ @type depth: C{boolean}
+ """
+ self.connection.logger.debug("Copy to " + repr(toUrl));
+ _checkUrl(toUrl)
+ if infinity:
+ response = self.connection.copy(self.path, toUrl)
+ else:
+ response = self.connection.copy(self.path, toUrl, 0)
+ if response.status == Constants.CODE_MULTISTATUS and response.msr.errorCount > 0:
+ raise WebdavError("Request failed: " + response.msr.reason, response.msr.code)
+
+ def delete(self, lockToken=None):
+ """
+ Deletes this resource.
+
+ @param lockToken: String returned by last lock operation or null.
+ @type lockToken: L{LockToken}
+ """
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ response = self.connection.delete(self.path, header)
+ if response.status == Constants.CODE_MULTISTATUS and response.msr.errorCount > 0:
+ raise WebdavError("Request failed: " + response.msr.reason, response.msr.code)
+
+ def move(self, toUrl):
+ """
+ Moves this resource to the given path or renames it.
+
+ @param toUrl: new (URI) path
+ """
+ self.connection.logger.debug("Move to " + repr(toUrl));
+ _checkUrl(toUrl)
+ response = self.connection.move(self.path, toUrl)
+ if response.status == Constants.CODE_MULTISTATUS and response.msr.errorCount > 0:
+ raise WebdavError("Request failed: " + response.msr.reason, response.msr.code)
+
+
+ def lock(self, owner):
+ """
+ Locks this resource for exclusive write access. This means that for succeeding
+ write operations the returned lock token has to be passed.
+ If the methode does not throw an exception the lock has been granted.
+
+ @param owner: describes the lock holder
+ @return: lock token string (automatically generated)
+ @rtype: L{LockToken}
+ """
+ response = self.connection.lock(self.path, owner)
+ if response.status == Constants.CODE_MULTISTATUS and response.msr.errorCount > 0:
+ raise WebdavError("Request failed: " + response.msr.reason, response.msr.code)
+ return LockToken(self.url, response.locktoken)
+
+ def unlock(self, lockToken):
+ """
+ Removes the lock from this resource.
+
+ @param lockToken: which has been return by the lock() methode
+ @type lockToken: L{LockToken}
+ """
+ self.connection.unlock(self.path, lockToken.token)
+
+
+ def deleteContent(self, lockToken=None):
+ """
+ Delete binary data at permanent storage.
+
+ @param lockToken: None or lock token from last lock request
+ @type lockToken: L{LockToken}
+ """
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ self.connection.put(self.path, "", extra_hdrs=header)
+
+ def uploadContent(self, content, lockToken=None):
+ """
+ Write binary data to permanent storage.
+
+ @param content: containing binary data
+ @param lockToken: None or lock token from last lock request
+ @type lockToken: L{LockToken}
+ """
+ assert not content or isinstance(content, types.UnicodeType) or\
+ isinstance(content, types.StringType), "Content is not a string: " + content.__class__.__name__
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ response = None
+ if not content is None:
+ header["Content-length"] = len(content)
+ else:
+ header["Content-length"] = 0
+
+ try:
+ response = self.connection.put(self.path, content, extra_hdrs=header)
+ finally:
+ if response:
+ self.connection.logger.debug(response.read())
+ response.close()
+
+ def uploadFile(self, newFile, lockToken=None):
+ """
+ Write binary data to permanent storage.
+
+ @param newFile: File containing binary data.
+ @param lockToken: None or lock token from last lock request
+ @type lockToken: L{LockToken}
+ """
+ assert isinstance(newFile, types.FileType), "Argument is no file: " + file.__class__.__name__
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ self.connection.putFile(self.path, newFile, header=header)
+
+ def downloadContent(self):
+ """
+ Read binary data from permanent storage.
+ """
+ response = self.connection.get(self.path)
+ # TODO: Other interface ? return self.connection.getfile()
+ return response
+
+ def downloadFile(self, localFileName):
+ """
+ Copy binary data from permanent storage to a local file.
+
+ @param localFileName: file to write binary data to
+ """
+ localFile = open(localFileName, 'wb')
+ remoteFile = self.downloadContent()
+ try:
+ socket.setdefaulttimeout(SOCKET_DEFAULT_TIMEOUT)
+ _blockCopyFile(remoteFile, localFile, Connection.blockSize)
+ except socket.error, e:
+ raise e
+ remoteFile.close()
+ localFile.close()
+
+ def readProperties(self, *names):
+ """
+ Reads the given properties.
+
+ @param names: a list of property names.
+ A property name is a (XmlNameSpace, propertyName) tuple.
+ @return: a map from property names to DOM Element or String values.
+ """
+ assert names, "Property names are missing."
+ body = createFindBody(names, self.defaultNamespace)
+ response = self.connection.propfind(self.path, body, depth=0)
+ properties = response.msr.values()[0]
+ if properties.errorCount > 0:
+ raise WebdavError("Property is missing on '%s': %s" % (self.path, properties.reason), properties.code)
+ return properties
+
+ def readProperty(self, nameSpace, name):
+ """
+ Reads the given property.
+
+ @param nameSpace: XML-namespace
+ @type nameSpace: string
+ @param name: A property name.
+ @type name: string
+
+ @return: a map from property names to DOM Element or String values.
+ """
+ results = self.readProperties((nameSpace, name))
+ if len(results) == 0:
+ raise WebdavError("Property is missing: " + results.reason)
+ return results.values()[0]
+
+ def readAllProperties(self):
+ """
+ Reads all properties of this resource.
+
+ @return: a map from property names to DOM Element or String values.
+ """
+ response = self.connection.allprops(self.path, depth=0)
+ return response.msr.values()[0]
+
+ def readAllPropertyNames(self):
+ """
+ Returns the names of all properties attached to this resource.
+
+ @return: List of property names
+ """
+ response = self.connection.propnames(self.path, depth=0)
+ return response.msr.values()[0]
+
+ def readStandardProperties(self):
+ """
+ Read all WebDAV live properties.
+
+ @return: A L{LiveProperties} instance which contains a getter method for each live property.
+ """
+ body = createFindBody(LiveProperties.NAMES, Constants.NS_DAV)
+ response = self.connection.propfind(self.path, body, depth=0)
+ properties = response.msr.values()[0]
+ return LiveProperties(properties)
+
+ def writeProperties(self, properties, lockToken=None):
+ """
+ Sets or updates the given properties.
+
+ @param lockToken: if the resource has been locked this is the lock token.
+ @type lockToken: L{LockToken}
+ @param properties: a map from property names to a String or
+ DOM element value for each property to add or update.
+ """
+ assert isinstance(properties, types.DictType)
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ body = createUpdateBody(properties, self.defaultNamespace)
+ response = self.connection.proppatch(self.path, body, header)
+ if response.msr.errorCount > 0:
+ raise WebdavError("Request failed: " + response.msr.reason, response.msr.code)
+
+ def deleteProperties(self, lockToken=None, *names):
+ """
+ Removes the given properties from this resource.
+
+ @param lockToken: if the resource has been locked this is the lock token.
+ @type lockToken: L{LockToken}
+ @param names: a collection of property names.
+ A property name is a (XmlNameSpace, propertyName) tuple.
+ """
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ body = createDeleteBody(names, self.defaultNamespace)
+ response = self.connection.proppatch(self.path, body, header)
+ if response.msr.errorCount > 0:
+ raise WebdavError("Request failed: " + response.msr.reason, response.msr.code)
+
+ # ACP extension
+ def setAcl(self, acl, lockToken=None):
+ """
+ Sets ACEs in the non-inherited and non-protected ACL or the resource.
+ This is the implementation of the ACL method of the WebDAV ACP.
+
+ @param acl: ACL to be set on resource as ACL object.
+ @param lockToken: If the resource has been locked this is the lock token (defaults to None).
+ @type lockToken: L{LockToken}
+ """
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ headers = {}
+ if lockToken:
+ headers = lockToken.toHeader()
+ headers['Content-Type'] = XML_CONTENT_TYPE
+ body = acl.toXML()
+ response = self.connection._request('ACL', self.path, body, headers)
+ return response
+ ## TODO: parse DAV:error response
+
+ def getAcl(self):
+ """
+ Returns this resource's ACL in an ACL instance.
+
+ @return: Access Control List.
+ @rtype: L{ACL<webdav.acp.Acl.ACL>}
+ """
+ xmlAcl = self.readProperty(Constants.NS_DAV, Constants.TAG_ACL)
+ return ACL(xmlAcl)
+
+ def getCurrentUserPrivileges(self):
+ """
+ Returns a tuple of the current user privileges.
+
+ @return: list of Privilege instances
+ @rtype: list of L{Privilege<webdav.acp.Privilege.Privilege>}
+ """
+ privileges = self.readProperty(Constants.NS_DAV, Constants.PROP_CURRENT_USER_PRIVILEGE_SET)
+ result = []
+ for child in privileges.children:
+ result.append(Privilege(domroot=child))
+ return result
+
+ def getPrincipalCollections(self):
+ """
+ Returns a list principal collection URLs.
+
+ @return: list of principal collection URLs
+ @rtype: C{list} of C{unicode} elements
+ """
+ webdavQueryResult = self.readProperty(Constants.NS_DAV, Constants.PROP_PRINCIPAL_COLLECTION_SET)
+ principalCollectionList = []
+ for child in webdavQueryResult.children:
+ principalCollectionList.append(child.first_cdata)
+ return principalCollectionList
+
+ def getOwnerUrl(self):
+ """ Explicitly retireve the Url of the owner. """
+
+ result = self.readProperty(Constants.NS_DAV, Constants.PROP_OWNER)
+ if result and len(result.children):
+ return result.children[0].textof()
+ return None
+
+class CollectionStorer(ResourceStorer):
+ """
+ This class provides client access to a WebDAV collection resource identified by an URI.
+ This class does not cache resource data. This has to be performed by its clients.
+
+ @author: Roland Betz
+ """
+
+ def __init__(self, url, connection=None, validateResourceNames=True):
+ """
+ Creates a CollectionStorer instance for a URL and an optional Connection object.
+ User must invoke validate() after constuction to check the resource on the server.
+
+ @see: L{webdav.WebdavClient.ResourceStorer.__init__}
+ @param url: unique resource location for this storer
+ @param connection: this optional parameter contains a Connection object for the host part
+ of the given URL. Passing a connection saves memory by sharing this connection.
+ """
+ if url[-1] != '/': # Collection URL must end with slash
+ url += '/'
+ ResourceStorer.__init__(self, url, connection, validateResourceNames)
+
+ def getResourceStorer(self, name):
+ """
+ Return a ResourceStorer instance for a child resource (member) of this Collection.
+
+ @param name: leaf name of child resource
+ @return: L{ResourceStorer} instance
+ """
+ assert isinstance(name, types.StringType) or isinstance(name, types.UnicodeType)
+ return ResourceStorer(self.url + name, self.connection, self.validateResourceNames)
+
+ def validate(self):
+ """
+ Check whether this URL contains a WebDAV collection.
+ Uses the WebDAV OPTION method.
+
+ @raise WebdavError: L{WebdavError} if URL does not contain a WebDAV collection resource.
+ """
+ super(CollectionStorer, self).validate()
+ isCollection = self.readProperty(Constants.NS_DAV, Constants.PROP_RESOURCE_TYPE)
+ if not (isCollection and isCollection.children):
+ raise WebdavError("Not a collection URL.", 0)
+
+ def addCollection(self, name, lockToken=None):
+ """
+ Make a new WebDAV collection resource within this collection.
+
+ @param name: of the new collection
+ @param lockToken: None or token returned by last lock operation
+ @type lockToken: L{LockToken}
+ """
+ assert isinstance(name, types.StringType) or isinstance(name, types.UnicodeType)
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ if self.validateResourceNames:
+ validateResourceName(name)
+ if name[-1] != '/': # Collection URL must end with slash
+ name += '/'
+ self.connection.mkcol(self.path + name, header)
+ return CollectionStorer(self.url + name, self.connection, self.validateResourceNames)
+
+ def addResource(self, name, content=None, properties=None, lockToken=None):
+ """
+ Create a new empty WebDAV resource contained in this collection with the given
+ properties.
+
+ @param name: leaf name of the new resource
+ @param content: None or initial binary content of resource
+ @param properties: name/value-map containing properties
+ @param lockToken: None or token returned by last lock operation
+ @type lockToken: L{LockToken}
+ """
+ assert isinstance(name, types.StringType) or isinstance(name, types.UnicodeType)
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ if self.validateResourceNames:
+ validateResourceName(name) # check for invalid characters
+ resource_ = ResourceStorer(self.url + name, self.connection, self.validateResourceNames)
+ resource_.uploadContent(content, lockToken)
+ if properties:
+ resource_.writeProperties(properties, lockToken)
+ return resource_
+
+ def deleteResource(self, name, lockToken=None):
+ """
+ Delete a collection which is contained within this collection
+
+ @param name: leaf name of a contained collection resource
+ @param lockToken: None or token returned by last lock operation
+ @type lockToken: L{LockToken}
+ """
+ assert isinstance(name, types.StringType) or isinstance(name, types.UnicodeType)
+ assert lockToken == None or isinstance(lockToken, LockToken), \
+ "Invalid lockToken argument %s" % type(lockToken)
+ header = {}
+ if lockToken:
+ header = lockToken.toHeader()
+ if self.validateResourceNames:
+ validateResourceName(name)
+ response = self.connection.delete(self.path + name, header)
+ if response.status == Constants.CODE_MULTISTATUS and response.msr.errorCount > 0:
+ raise WebdavError("Request failed: %s" % response.msr.reason, response.msr.code)
+
+ def lockAll(self, owner):
+ """
+ Locks this collection resource for exclusive write access. This means that for
+ succeeding write operations the returned lock token has to be passed.
+ The operation is applied recursively to all contained resources.
+ If the methode does not throw an exception then the lock has been granted.
+
+ @param owner: describes the lock holder
+ @return: Lock token string (automatically generated).
+ @rtype: L{LockToken}
+ """
+ assert isinstance(owner, types.StringType) or isinstance(owner, types.UnicodeType)
+ response = self.connection.lock(self.path, owner, depth=Constants.HTTP_HEADER_DEPTH_INFINITY)
+ return LockToken(self.url, response.locktoken)
+
+ def listResources(self):
+ """
+ Describe all members within this collection.
+
+ @return: map from URI to a L{LiveProperties} instance containing the WebDAV
+ live attributes of the contained resource
+ """
+ # *LiveProperties.NAMES denotes the list of all live properties as an
+ # argument to the method call.
+ response = self.connection.getprops(self.path,
+ depth=1,
+ ns=Constants.NS_DAV,
+ *LiveProperties.NAMES)
+ result = {}
+ for path, properties in response.msr.items():
+ if path == self.path: # omit this collection resource
+ continue
+ ## some servers do not append a trailing slash to collection paths
+ if self.path.endswith('/') and self.path[0:-1] == path:
+ continue
+ result[path] = LiveProperties(properties=properties)
+ return result
+
+ def getCollectionContents(self):
+ """
+ Return a list of the tuple (resources or collection) / properties)
+
+ @return: a list of the tuple (resources or collection) / properties)
+ @rtype: C{list}
+ """
+ self.validate()
+ collectionContents = []
+ result = self.listResources()
+ for url, properties_ in result.items():
+ if not self.path == url:
+ if properties_.getResourceType() == 'resource':
+ myWebDavStorer = ResourceStorer(url, self.connection, self.validateResourceNames)
+ else:
+ myWebDavStorer = CollectionStorer(url, self.connection, self.validateResourceNames)
+ collectionContents.append((myWebDavStorer, properties_))
+ return collectionContents
+
+ def findProperties(self, *names):
+ """
+ Retrieve given properties for this collection and all directly contained resources.
+
+ @param names: a list of property names
+ @return: a map from resource URI to a map from property name to value.
+ """
+ assert isinstance(names, types.ListType) or isinstance(names, types.TupleType), \
+ "Argument name has type %s" % str(type(names))
+ body = createFindBody(names, self.defaultNamespace)
+ response = self.connection.propfind(self.path, body, depth=1)
+ return response.msr
+
+ def deepFindProperties(self, *names):
+ """
+ Retrieve given properties for this collection and all contained (nested) resources.
+
+ Note:
+ =====
+ This operation can take a long time if used with recursive=true and is therefore
+ disabled on some WebDAV servers.
+
+ @param names: a list of property names
+ @return: a map from resource URI to a map from property name to value.
+ """
+ assert isinstance(names, types.ListType.__class__) or isinstance(names, types.TupleType), \
+ "Argument name has type %s" % str(type(names))
+ body = createFindBody(names, self.defaultNamespace)
+ response = self.connection.propfind(self.path, body, depth=Constants.HTTP_HEADER_DEPTH_INFINITY)
+ return response.msr
+
+ def findAllProperties(self):
+ """
+ Retrieve all properties for this collection and all directly contained resources.
+
+ @return: a map from resource URI to a map from property name to value.
+ """
+ response = self.connection.allprops(self.path, depth=1)
+ return response.msr
+
+
+ # DASL extension
+ def search(self, conditions, selects):
+ """
+ Search for contained resources which match the given search condition.
+
+ @param conditions: tree of ConditionTerm instances representing a logical search term
+ @param selects: list of property names to retrieve for the found resources
+ """
+ assert isinstance(conditions, ConditionTerm)
+ headers = { 'Content-Type' : XML_CONTENT_TYPE, "depth": Constants.HTTP_HEADER_DEPTH_INFINITY}
+ body = createSearchBody(selects, self.path, conditions)
+ response = self.connection._request('SEARCH', self.path, body, headers)
+ return response.msr
+
+
+class LockToken(object):
+ """
+ This class provides help on handling WebDAV lock tokens.
+
+ @author: Roland Betz
+ """
+ # restrict instance variables
+ __slots__ = ('url', 'token')
+
+ def __init__(self, url, token):
+ assert isinstance(url, types.StringType) or isinstance(url, types.UnicodeType), \
+ "Invalid url argument %s" % type(url)
+ assert isinstance(token, types.StringType) or isinstance(token, types.UnicodeType), \
+ "Invalid lockToken argument %s" % type(token)
+ self.url = url
+ self.token = token
+
+ def value(self):
+ """
+ Descriptive string containing the lock token's URL and the token itself.
+
+ @return: Descriptive lock token with URL.
+ @rtype: C{string}
+ """
+ return "<" + self.url + "> (<" + self.token + ">)"
+
+ def toHeader(self):
+ """
+ Header fragment for WebDAV request.
+
+ @return: Dictionary containing an entry for the lock token query.
+ @rtype: C{dictionary}
+ """
+ return {Constants.HTTP_HEADER_IF: self.value()}
+
+ def __str__(self):
+ return self.value()
+
+
+def _blockCopyFile(source, dest, blockSize):
+ """
+ Copies a file in chunks of C{blockSize}.
+
+ @param source: Source file.
+ @type source: FileIO buffer.
+ @param dest: Destination file.
+ @type dest: FileIO buffer.
+ @param blockSize: Size of block in bytes.
+ @type blockSize: C{int}
+ """
+ transferedBytes = 0
+ block = source.read(blockSize)
+ while len(block):
+ dest.write(block)
+ transferedBytes += len(block);
+ block = source.read(blockSize)
+
+def _checkUrl(url):
+ """
+ Checks the given URL for validity.
+
+ @param url: URL to check.
+ @type url: C{string}
+
+ @raise ValueError: If the URL does not contain valid/usable content.
+ """
+
+ parts = urlsplit(url, allow_fragments=False)
+ if len(parts[0]) == 0 or len(parts[1]) == 0 or len(parts[2]) == 0:
+ raise ValueError("Invalid URL: " + repr(url))