def runSession(self, request): ctr = 5 while ctr: ctr -= 1 self.doSession(request) if request and request.isRedirect(): location = request.getResponseHeader(headers.Location) if location: u = URL(location) if not u.scheme or u.scheme in ("http", "https",): # Get new server and base RURL different_server = (self.server != u.server) if u.server else False # Use new host in this session if different_server: self.setServer(u.server) # Reset the request with new info request.setURL(u.relativeURL()) request.clearResponse() # Write to log file if self.loghttp and self.log: self.log.write("\n <-------- HTTP REDIRECT -------->\n") self.log.write("Location: %s\n" % (location,)) # Recyle through loop continue # Exit when redirect does not occur break
def getPropertiesOnHierarchy(self, rurl, props): assert (isinstance(rurl, URL)) results = {} # Create WebDAV propfind request = PropFind(self, rurl.relativeURL(), headers.Depth1, props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) propresults = {} results[name.relativeURL()] = propresults for prop in props: if str(prop) in item.getTextProperties(): propresults[prop] = item.getTextProperties().get( str(prop)) elif str(prop) in item.getNodeProperties(): propresults[prop] = item.getNodeProperties()[str(prop)] else: self.handleHTTPError(request) return results
def runSession(self, request): ctr = 5 while ctr: ctr -= 1 self.doSession(request) if request and request.isRedirect(): location = request.getResponseHeader(headers.Location) if location: u = URL(location) if not u.scheme or u.scheme in ( "http", "https", ): # Get new server and base RURL different_server = ( self.server != u.server) if u.server else False # Use new host in this session if different_server and not self.noHostRedirect: self.setServer(u.server) # Reset the request with new info request.setURL(u.relativeURL()) request.clearResponse() # Write to log file if self.loghttp and self.log: self.log.write( "\n <-------- HTTP REDIRECT -------->\n" ) self.log.write("Location: %s\n" % (location, )) # Recyle through loop continue # Exit when redirect does not occur break
def getPropertiesOnHierarchy(self, rurl, props): assert(isinstance(rurl, URL)) results = {} # Create WebDAV propfind request = PropFind(self, rurl.relativeURL(), headers.Depth1, props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) propresults = {} results[name.relativeURL()] = propresults for prop in props: if str(prop) in item.getTextProperties(): propresults[prop] = item.getTextProperties().get(str(prop)) elif str(prop) in item.getNodeProperties(): propresults[prop] = item.getNodeProperties()[str(prop)] else: self.handleHTTPError(request) return results
class CalDAVSession(Session): class logger(object): def write(self, data): print data.replace("\r\n", "\n"), def __init__(self, server, port=None, ssl=False, user="", pswd="", principal=None, root=None, logging=False): super(CalDAVSession, self).__init__(server, port, ssl, log=CalDAVSession.logger()) self.loghttp = logging self.user = user self.pswd = pswd # Initialize state self.connect = None # Paths self.rootPath = URL(url=root) self.principalPath = URL(url=principal) if principal else None self._initCalDAVState() def _initCalDAVState(self): # We need to cache the server capabilities and properties if not self.principalPath: self._discoverPrincipal() def _discoverPrincipal(self): current = self.getCurrentPrincipalResource(self.rootPath) if current: self.principalPath = current if self.log: self.log.write("Found current principal path: %s\n" % (self.principalPath.absoluteURL(),)) return hrefs = self.getHrefListProperty(self.rootPath, davxml.principal_collection_set) if not hrefs: return # For each principal collection find current principal for href in hrefs: current = self.getCurrentPrincipalResource(href) if current: self.principalPath = current if self.log: self.log.write("Found current principal path: %s\n" % (self.principalPath.absoluteURL(),)) return def setUserPswd(self, user, pswd): self.user = user self.pswd = pswd self.authorization = None self._discoverPrincipal() def testResource(self, rurl): assert(isinstance(rurl, URL)) request = PropFind(self, rurl.relativeURL(), headers.Depth0, (davxml.resourcetype,)) # Process it self.runSession(request) return request.getStatusCode() == statuscodes.MultiStatus def getPropertyNames(self, rurl): assert(isinstance(rurl, URL)) results = () # Create WebDAV propfind request = PropNames(self, rurl.relativeURL(), headers.Depth0) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match rurl if name.equalRelative(rurl): results = tuple([name for name in item.getNodeProperties().iterkeys()]) else: self.handleHTTPError(request) return results def getProperties(self, rurl, props, xmldata=False): assert(isinstance(rurl, URL)) results = {} bad = None # Create WebDAV propfind if props: request = PropFind(self, rurl.relativeURL(), headers.Depth0, props) else: request = PropAll(self, rurl.relativeURL(), headers.Depth0) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match rurl if name.equalRelative(rurl): for name, value in item.getTextProperties().iteritems(): results[name] = value for name, value in item.getHrefProperties().iteritems(): if name not in results: results[name] = value for name, value in item.getNodeProperties().iteritems(): if name not in results: results[name] = tostring(value) if xmldata else value bad = item.getBadProperties() else: self.handleHTTPError(request) return results, bad def getPropertiesOnHierarchy(self, rurl, props): assert(isinstance(rurl, URL)) results = {} # Create WebDAV propfind request = PropFind(self, rurl.relativeURL(), headers.Depth1, props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) propresults = {} results[name.relativeURL()] = propresults for prop in props: if str(prop) in item.getTextProperties(): propresults[prop] = item.getTextProperties().get(str(prop)) elif str(prop) in item.getNodeProperties(): propresults[prop] = item.getNodeProperties()[str(prop)] else: self.handleHTTPError(request) return results def getHrefListProperty(self, rurl, propname): assert(isinstance(rurl, URL)) results = () # Create WebDAV propfind request = PropFind(self, rurl.relativeURL(), headers.Depth0, (propname,)) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result and extract any Hrefs for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match rurl if name.equalRelative(rurl): if str(propname) in item.getNodeProperties(): propnode = item.getNodeProperties()[str(propname)] results += tuple([URL(url=href.text, decode=True) for href in propnode.findall(str(davxml.href)) if href.text]) else: self.handleHTTPError(request) return results # Do principal-match report with self on the passed in url def getSelfProperties(self, rurl, props): assert(isinstance(rurl, URL)) results = {} # Create WebDAV principal-match request = PrincipalMatch(self, rurl.relativeURL(), props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each principal-match result and return first one that is appropriate for item in parser.getResults().itervalues(): for prop in props: if str(prop) in item.getNodeProperties(): href = item.getNodeProperties()[str(prop)].find(str(davxml.href)) if href is not None: results[prop] = URL(url=href.text, decode=True) # We'll take the first one, whatever that is break else: self.handleHTTPError(request) return None return results # Do principal-match report with self on the passed in url def getSelfHrefs(self, rurl): assert(isinstance(rurl, URL)) results = () # Create WebDAV principal-match request = PrincipalMatch(self, rurl.relativeURL(), (davxml.principal_URL,)) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result and extract any Hrefs for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) results += (name,) else: self.handleHTTPError(request) return None return results # Do principal-match report with self on the passed in url def getSelfPrincipalResource(self, rurl): assert(isinstance(rurl, URL)) hrefs = self.getHrefListProperty(rurl, davxml.principal_collection_set) if not hrefs: return None # For each principal collection find one that matches self for href in hrefs: results = self.getSelfHrefs(href) if results: return results[0] return None # Do current-user-principal property on the passed in url def getCurrentPrincipalResource(self, rurl): assert(isinstance(rurl, URL)) hrefs = self.getHrefListProperty(rurl, davxml.current_user_principal) return hrefs[0] if hrefs else None def setProperties(self, rurl, props): assert(isinstance(rurl, URL)) results = () # Convert property data into something sensible converted = [] for name, value in props: node = None if isinstance(value, types.StringType): node = Element(name) node.text = value elif isinstance(value, URL): node = Element(davxml.href) node.text = value.absoluteURL() elif isinstance(value, types.ListType) or isinstance(value, types.TupleType): hrefs = [] for item in value: if isinstance(item, URL): href = Element(davxml.href) href.text = item.relativeURL() hrefs.append(href) else: break else: node = Element(name) map(node.append, hrefs) if node is not None: converted.append(node) # Create WebDAV propfind request = PropPatch(self, rurl.relativeURL(), converted) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match rurl if name.equalRelative(rurl): for prop in item.getNodeProperties(): results += (prop,) else: self.handleHTTPError(request) return results def setACL(self, rurl, aces): assert(isinstance(rurl, URL)) # Create WebDAV ACL request = ACL(self, rurl.relativeURL(), aces) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def makeCollection(self, rurl): assert(isinstance(rurl, URL)) # Create WebDAV MKCOL request = MakeCollection(self, rurl.relativeURL()) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def makeCalendar(self, rurl, displayname=None, description=None): assert(isinstance(rurl, URL)) # Create WebDAV MKCALENDAR request = MakeCalendar(self, rurl.relativeURL(), displayname, description) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def makeAddressBook(self, rurl, displayname=None, description=None): assert(isinstance(rurl, URL)) # Create WebDAV extended MKCOL request = MakeAddressBook(self, rurl.relativeURL(), displayname, description) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def syncCollection(self, rurl, synctoken, props=()): assert(isinstance(rurl, URL)) newsynctoken = "" changed = set() removed = set() other = set() # Create WebDAV sync REPORT request = SyncCollection(self, rurl.relativeURL(), davxml.sync_level_1, synctoken, props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) if item.status == 404: removed.add(name) elif item.status / 100 != 2: other.add(name) else: changed.add(name) # Get the new token for node in parser.getOthers(): if node.tag == davxml.sync_token: newsynctoken = node.text break else: self.handleHTTPError(request) return (newsynctoken, changed, removed, other,) def queryCollection(self, rurl, timerange, start, end, expand, props=()): assert(isinstance(rurl, URL)) hrefs = set() # Create CalDAV query REPORT if timerange: request = QueryVEVENTTimeRange(self, rurl.relativeURL(), start, end, expand, props=props) else: raise NotImplementedError result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) hrefs.add(name) else: self.handleHTTPError(request) return hrefs def deleteResource(self, rurl): assert(isinstance(rurl, URL)) # Create WebDAV DELETE request = Delete(self, rurl.relativeURL()) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.NoContent): self.handleHTTPError(request) def moveResource(self, rurlFrom, rurlTo): assert(isinstance(rurlFrom, URL)) assert(isinstance(rurlTo, URL)) # Create WebDAV MOVE request = Move(self, rurlFrom.relativeURL(), rurlTo.absoluteURL()) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def readData(self, rurl): assert(isinstance(rurl, URL)) # Create WebDAV GET request = Get(self, rurl.relativeURL()) dout = ResponseDataString() request.setData(dout) # Process it self.runSession(request) # Check response status if request.getStatusCode() != statuscodes.OK: self.handleHTTPError(request) return None # Look for ETag if request.getNewETag() is not None: etag = request.getNewETag() # Handle server bug: ETag value MUST be quoted per HTTP/1.1 S3.11 if not etag.startswith('"'): etag = "\"%s\"" % (etag,) else: etag = None # Return data as a string and etag return dout.getData(), etag def writeData(self, rurl, data, contentType): assert(isinstance(rurl, URL)) # Create WebDAV PUT request = Put(self, rurl.relativeURL()) dout = RequestDataString(data, contentType) request.setData(dout, None) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent,): self.handleHTTPError(request) def importData(self, rurl, data, contentType): assert(isinstance(rurl, URL)) # Create WebDAV POST request = Post(self, rurl.relativeURL()) dout = RequestDataString(data, contentType) request.setData(dout, None) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in (statuscodes.OK, statuscodes.MultiStatus, statuscodes.NoContent,): self.handleHTTPError(request) def addAttachment(self, rurl, filename, data, contentType, return_representation): assert(isinstance(rurl, URL)) # Create WebDAV POST rurl.extended = "?action=attachment-add" request = Post(self, rurl.relativeURL()) dout = RequestDataString(data, contentType) request.setRequestHeader("Content-Disposition", "attachment;filename=%s" % (filename,)) if return_representation: request.setRequestHeader("Prefer", "return-representation") request.setData(dout, None) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent,): self.handleHTTPError(request) def updateAttachment(self, rurl, managed_id, filename, data, contentType, return_representation): assert(isinstance(rurl, URL)) # Create WebDAV POST rurl.extended = "?action=attachment-update&managed-id=%s" % (managed_id,) request = Post(self, rurl.relativeURL()) request.setRequestHeader("Content-Disposition", "attachment;filename=%s" % (filename,)) if return_representation: request.setRequestHeader("Prefer", "return-representation") dout = RequestDataString(data, contentType) request.setData(dout, None) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent,): self.handleHTTPError(request) def removeAttachment(self, rurl, managed_id): assert(isinstance(rurl, URL)) # Create WebDAV POST rurl.extended = "?action=attachment-remove&managed-id=%s" % (managed_id,) request = Post(self, rurl.relativeURL()) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in (statuscodes.OK, statuscodes.NoContent,): self.handleHTTPError(request) def displayHTTPError(self, request): print request.status_code def openSession(self): # Create connection self.connect = SmartHTTPConnection(self.server, self.port, self.ssl) # Write to log file if self.loghttp and self.log: self.log.write("\n <-------- BEGIN HTTP CONNECTION -------->\n") self.log.write("Server: %s\n" % (self.server,)) def closeSession(self): if self.connect: self.connect.close() self.connect = None # Write to log file if self.loghttp and self.log: self.log.write("\n <-------- END HTTP CONNECTION -------->\n") def runSession(self, request): ctr = 5 while ctr: ctr -= 1 self.doSession(request) if request and request.isRedirect(): location = request.getResponseHeader(headers.Location) if location: u = URL(location) if not u.scheme or u.scheme in ("http", "https",): # Get new server and base RURL different_server = (self.server != u.server) if u.server else False # Use new host in this session if different_server: self.setServer(u.server) # Reset the request with new info request.setURL(u.relativeURL()) request.clearResponse() # Write to log file if self.loghttp and self.log: self.log.write("\n <-------- HTTP REDIRECT -------->\n") self.log.write("Location: %s\n" % (location,)) # Recyle through loop continue # Exit when redirect does not occur break def doSession(self, request): # Do initialisation if not already done if not self.initialised: if not self.initialise(self.server, self.rootPath.relativeURL()): # Break connection with server self.closeConnection() return # Do the request if present if request: # Handle delayed authorization first_time = True while True: # Run the request actions - this will make any connection that is needed self.sendRequest(request) # Check for auth failure if none before if request.getStatusCode() == statuscodes.Unauthorized: # If we had authorization before, then chances are auth details are wrong - so delete and try again with new auth if self.hasAuthorization(): self.authorization = None # Display error so user knows why the prompt occurs again - but not the first time # as we might have a digest re-auth. if not first_time: self.displayHTTPError(request) # Get authorization object (prompt the user) and redo the request self.authorization, cancelled = self.getAuthorizor(first_time, request.getResponseHeaders(headers.WWWAuthenticate)) # Check for auth cancellation if cancelled: self.authorization = None else: first_time = False request.clearResponse() # Repeat the request loop with new authorization continue # If we get here we are complete with auth loop break # Now close it - eventually we will do keep-alive support # Break connection with server self.closeConnection() def doRequest(self, request): # Write request headers self.connect.putrequest(request.method, request.url, skip_host=True, skip_accept_encoding=True) hdrs = request.getRequestHeaders() for header, value in hdrs: self.connect.putheader(header, value) self.connect.endheaders() # Write to log file if self.loghttp and self.log: self.log.write("\n <-------- BEGIN HTTP REQUEST -------->\n") self.log.write("%s\n" % (request.getRequestStartLine(),)) for header, value in hdrs: self.log.write("%s: %s\n" % (header, value)) self.log.write("\n") # Write the data self.writeRequestData(request) # Blank line in log between if self.loghttp and self.log: self.log.write("\n <-------- BEGIN HTTP RESPONSE -------->\n") # Get response response = self.connect.getresponse() # Get response headers request.setResponseStatus(response.version, response.status, response.reason) request.setResponseHeaders(response.msg.headers) if self.loghttp and self.log: self.log.write("HTTP/%s %s %s\r\n" % ( {11: "1.1", 10: "1.0", 9: "0.9"}.get(response.version, "?"), response.status, response.reason )) for hdr in response.msg.headers: self.log.write(hdr) self.log.write("\n") # Now get the data self.readResponseData(request, response) # Trailer in log if self.loghttp and self.log: self.log.write("\n <-------- END HTTP RESPONSE -------->\n") def handleHTTPError(self, request): print "Ignoring error: %d" % (request.getStatusCode(),) def getAuthorizor(self, first_time, wwwhdrs): for witem in wwwhdrs: for item in witem.split(","): item = item.strip() if item.lower().startswith("basic"): return Basic(self.user, self.pswd), False elif item.lower().startswith("digest"): return Digest(self.user, self.pswd, wwwhdrs), False elif item.lower().startswith("negotiate") and Kerberos is not None: return Kerberos(self.user), False else: return None, True def writeRequestData(self, request): # Write the data if any present if request.hasRequestData(): stream = request.getRequestData() if stream: # Tell data we are using it stream.start() # Buffered write from stream more = True while more: data, more = stream.read() if data: self.connect.send(data) if self.loghttp and self.log: self.log.write(data) # Tell data we are done using it stream.stop() def readResponseData(self, request, response): # Check for data and write it data = response.read() if request.hasResponseData(): stream = request.getResponseData() stream.start() stream.write(data) stream.stop() else: response.read() if self.loghttp and self.log: self.log.write(data) def setServerType(self, type): self.type = type def setServerDescriptor(self, txt): self.descriptor = txt def setServerCapability(self, txt): self.capability = txt
class CalDAVSession(Session): class logger(object): def write(self, data): print data.replace("\r\n", "\n"), def __init__(self, server, port=None, ssl=False, afunix=None, user="", pswd="", principal=None, root=None, logging=False, noHostRedirect=False): super(CalDAVSession, self).__init__(server, port, ssl, afunix, log=CalDAVSession.logger()) self.loghttp = logging self.user = user self.pswd = pswd self.noHostRedirect = noHostRedirect # Initialize state self.connect = None # Paths self.rootPath = URL(url=root) self.principalPath = URL(url=principal) if principal else None self._initCalDAVState() def _initCalDAVState(self): # We need to cache the server capabilities and properties if not self.principalPath: self._discoverPrincipal() def _discoverPrincipal(self): current = self.getCurrentPrincipalResource(self.rootPath) if current: self.principalPath = current return current = self.getCurrentPrincipalResource( URL(url="/.well-known/caldav")) if current: self.principalPath = current return hrefs = self.getHrefListProperty(self.rootPath, davxml.principal_collection_set) if not hrefs: return hrefs = self.getHrefListProperty(URL(url="/.well-known/caldav"), davxml.principal_collection_set) if not hrefs: return # For each principal collection find current principal for href in hrefs: current = self.getCurrentPrincipalResource(href) if current: self.principalPath = current return def setUserPswd(self, user, pswd): self.user = user self.pswd = pswd self.authorization = None self._discoverPrincipal() def testResource(self, rurl): assert (isinstance(rurl, URL)) request = PropFind(self, rurl.relativeURL(), headers.Depth0, (davxml.resourcetype, )) # Process it self.runSession(request) return request.getStatusCode() == statuscodes.MultiStatus def getPropertyNames(self, rurl): assert (isinstance(rurl, URL)) results = () # Create WebDAV propfind request = PropNames(self, rurl.relativeURL(), headers.Depth0) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match rurl if name.equalRelative(rurl): results = tuple( [name for name in item.getNodeProperties().iterkeys()]) else: self.handleHTTPError(request) return results def getProperties(self, rurl, props, xmldata=False): assert (isinstance(rurl, URL)) results = {} bad = None # Create WebDAV propfind if props: request = PropFind(self, rurl.relativeURL(), headers.Depth0, props) else: request = PropAll(self, rurl.relativeURL(), headers.Depth0) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match rurl if name.equalRelative(rurl): for name, value in item.getTextProperties().iteritems(): results[name] = value for name, value in item.getHrefProperties().iteritems(): if name not in results: results[name] = value for name, value in item.getNodeProperties().iteritems(): if name not in results: results[name] = tostring( value) if xmldata else value bad = item.getBadProperties() else: self.handleHTTPError(request) return results, bad def getPropertiesOnHierarchy(self, rurl, props): assert (isinstance(rurl, URL)) results = {} # Create WebDAV propfind request = PropFind(self, rurl.relativeURL(), headers.Depth1, props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) propresults = {} results[name.relativeURL()] = propresults for prop in props: if str(prop) in item.getTextProperties(): propresults[prop] = item.getTextProperties().get( str(prop)) elif str(prop) in item.getNodeProperties(): propresults[prop] = item.getNodeProperties()[str(prop)] else: self.handleHTTPError(request) return results def getHrefListProperty(self, rurl, propname): assert (isinstance(rurl, URL)) results = () # Create WebDAV propfind request = PropFind(self, rurl.relativeURL(), headers.Depth0, (propname, )) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result and extract any Hrefs for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match the URL the request was actually targeted at if name.equalRelative(URL(url=request.url)): if str(propname) in item.getNodeProperties(): propnode = item.getNodeProperties()[str(propname)] results += tuple([ URL(url=href.text, decode=True) for href in propnode.findall(str(davxml.href)) if href.text ]) else: self.handleHTTPError(request) return results # Do principal-match report with self on the passed in url def getSelfProperties(self, rurl, props): assert (isinstance(rurl, URL)) results = {} # Create WebDAV principal-match request = PrincipalMatch(self, rurl.relativeURL(), props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each principal-match result and return first one that is appropriate for item in parser.getResults().itervalues(): for prop in props: if str(prop) in item.getNodeProperties(): href = item.getNodeProperties()[str(prop)].find( str(davxml.href)) if href is not None: results[prop] = URL(url=href.text, decode=True) # We'll take the first one, whatever that is break else: self.handleHTTPError(request) return None return results # Do principal-match report with self on the passed in url def getSelfHrefs(self, rurl): assert (isinstance(rurl, URL)) results = () # Create WebDAV principal-match request = PrincipalMatch(self, rurl.relativeURL(), (davxml.principal_URL, )) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result and extract any Hrefs for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) results += (name, ) else: self.handleHTTPError(request) return None return results # Do principal-match report with self on the passed in url def getSelfPrincipalResource(self, rurl): assert (isinstance(rurl, URL)) hrefs = self.getHrefListProperty(rurl, davxml.principal_collection_set) if not hrefs: return None # For each principal collection find one that matches self for href in hrefs: results = self.getSelfHrefs(href) if results: return results[0] return None # Do current-user-principal property on the passed in url def getCurrentPrincipalResource(self, rurl): assert (isinstance(rurl, URL)) hrefs = self.getHrefListProperty(rurl, davxml.current_user_principal) return hrefs[0] if hrefs else None def setProperties(self, rurl, props): assert (isinstance(rurl, URL)) results = () # Convert property data into something sensible converted = [] for name, value in props: node = None if isinstance(value, types.StringType): node = Element(name) node.text = value elif isinstance(value, URL): node = Element(davxml.href) node.text = value.absoluteURL() elif isinstance(value, types.ListType) or isinstance( value, types.TupleType): hrefs = [] for item in value: if isinstance(item, URL): href = Element(davxml.href) href.text = item.relativeURL() hrefs.append(href) else: break else: node = Element(name) map(node.append, hrefs) if node is not None: converted.append(node) # Create WebDAV propfind request = PropPatch(self, rurl.relativeURL(), converted) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) # Must match rurl if name.equalRelative(rurl): for prop in item.getNodeProperties(): results += (prop, ) else: self.handleHTTPError(request) return results def setACL(self, rurl, aces): assert (isinstance(rurl, URL)) # Create WebDAV ACL request = ACL(self, rurl.relativeURL(), aces) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def makeCollection(self, rurl): assert (isinstance(rurl, URL)) # Create WebDAV MKCOL request = MakeCollection(self, rurl.relativeURL()) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def makeCalendar(self, rurl, displayname=None, description=None): assert (isinstance(rurl, URL)) # Create WebDAV MKCALENDAR request = MakeCalendar(self, rurl.relativeURL(), displayname, description) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def makeAddressBook(self, rurl, displayname=None, description=None): assert (isinstance(rurl, URL)) # Create WebDAV extended MKCOL request = MakeAddressBook(self, rurl.relativeURL(), displayname, description) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def calendarMultiGet(self, rurl, hrefs, props): """ Fetches the specified props for the specified hrefs using a single multiget call. The return value is a dictionary where the keys are the hrefs and the values are PropFindResult objects containing results for the requested props. """ assert (isinstance(rurl, URL)) request = CalMultiget(self, rurl.relativeURL(), hrefs=hrefs, props=props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If it's a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) return parser.getResults() else: self.handleHTTPError(request) return None def addressbookMultiGet(self, rurl, hrefs, props): """ Fetches the specified props for the specified hrefs using a single multiget call. The return value is a dictionary where the keys are the hrefs and the values are PropFindResult objects containing results for the requested props. """ assert (isinstance(rurl, URL)) request = AdbkMultiget(self, rurl.relativeURL(), hrefs=hrefs, props=props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If it's a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) return parser.getResults() else: self.handleHTTPError(request) return None def syncCollection(self, rurl, synctoken, props=(), infinite=False): assert (isinstance(rurl, URL)) newsynctoken = "" changed = set() removed = set() other = set() # Create WebDAV sync REPORT request = SyncCollection( self, rurl.relativeURL(), davxml.sync_level_infinite if infinite else davxml.sync_level_1, synctoken, props) result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) if int(item.status) == 404: removed.add(name) elif int(item.status) / 100 != 2: other.add(name) else: changed.add(name) # Get the new token for node in parser.getOthers(): if node.tag == davxml.sync_token: newsynctoken = node.text break else: self.handleHTTPError(request) return ( newsynctoken, changed, removed, other, ) def queryCollection(self, rurl, timerange, start, end, expand, props=()): assert (isinstance(rurl, URL)) hrefs = set() # Create CalDAV query REPORT if timerange: request = QueryVEVENTTimeRange(self, rurl.relativeURL(), start, end, expand, props=props) else: raise NotImplementedError result = ResponseDataString() request.setOutput(result) # Process it self.runSession(request) # If its a 207 we want to parse the XML if request.getStatusCode() == statuscodes.MultiStatus: parser = PropFindParser() parser.parseData(result.getData()) # Look at each propfind result for item in parser.getResults().itervalues(): # Get child element name (decode URL) name = URL(url=item.getResource(), decode=True) hrefs.add(name) else: self.handleHTTPError(request) return hrefs def deleteResource(self, rurl): assert (isinstance(rurl, URL)) # Create WebDAV DELETE request = Delete(self, rurl.relativeURL()) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.NoContent): self.handleHTTPError(request) def moveResource(self, rurlFrom, rurlTo): assert (isinstance(rurlFrom, URL)) assert (isinstance(rurlTo, URL)) # Create WebDAV MOVE request = Move(self, rurlFrom.relativeURL(), rurlTo.absoluteURL()) # Process it self.runSession(request) if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): self.handleHTTPError(request) def readData(self, rurl): assert (isinstance(rurl, URL)) # Create WebDAV GET request = Get(self, rurl.relativeURL()) dout = ResponseDataString() request.setData(dout) # Process it self.runSession(request) # Check response status if request.getStatusCode() != statuscodes.OK: self.handleHTTPError(request) return None # Look for ETag if request.getNewETag() is not None: etag = request.getNewETag() # Handle server bug: ETag value MUST be quoted per HTTP/1.1 S3.11 if not etag.startswith('"'): etag = "\"%s\"" % (etag, ) else: etag = None # Return data as a string and etag return dout.getData(), etag def writeData(self, rurl, data, contentType, etag=None, method="PUT"): assert (isinstance(rurl, URL)) # Create WebDAV PUT if method == "PUT": request = Put(self, rurl.relativeURL()) elif method == "POST": request = Post(self, rurl.relativeURL()) elif method == "OPTIONS": request = Options(self, rurl.relativeURL()) dout = ResponseDataString() if data is not None: din = RequestDataString(data, contentType) if method == "PUT": request.setData(din, dout, etag=etag) elif method == "POST": request.setData(din, dout) else: request.setData(None, dout) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in ( statuscodes.OK, statuscodes.Created, statuscodes.NoContent, ): self.handleHTTPError(request) # Return data as a string return dout.getData() def importData(self, rurl, data, contentType): assert (isinstance(rurl, URL)) # Create WebDAV POST request = Post(self, rurl.relativeURL()) dout = RequestDataString(data, contentType) request.setData(dout, None) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in ( statuscodes.OK, statuscodes.MultiStatus, statuscodes.NoContent, ): self.handleHTTPError(request) def getInvites(self, rurl): """ Get the invitation details for the specified resource by reading and parsing the CS:invite WebDAV property. @param rurl: the resource whose property is to be read @type rurl: L{URL} """ assert (isinstance(rurl, URL)) results, bad = self.getProperties(rurl, (csxml.invite, )) if csxml.invite in bad: return Invites() else: return Invites().parseFromInvite(results.get(csxml.invite)) def addInvitees(self, rurl, user_uids, read_write, summary=None): """ Add a sharing invite for the specified resource. @param rurl: the resource to share @type rurl: L{URL} @param user_uids: short name or full principal path of user to share with @type user_uids: C{str} @param read_write: whether to share read-only C{False} or read-write C{True} @type read_write: C{bool} @param summary: summary description for the share @type summary: C{str} """ assert (isinstance(rurl, URL)) # Add invitation POST request = AddInvitees(self, rurl.relativeURL(), user_uids, read_write, summary) # Process it self.runSession(request) if request.getStatusCode() not in ( statuscodes.OK, statuscodes.NoContent, ): self.handleHTTPError(request) def removeInvitee(self, rurl, invitee): """ Remove an invite from a shared resource. @param rurl: the resource currently being shared @type rurl: L{URL} @param invitee: invite DAV:href for the user being removed @type invitee: C{str} """ assert (isinstance(rurl, URL)) # Remove invitation POST request = RemoveInvitee(self, rurl.relativeURL(), invitee) # Process it self.runSession(request) if request.getStatusCode() not in ( statuscodes.OK, statuscodes.NoContent, ): self.handleHTTPError(request) def getNotifications(self, rurl): """ Get a list of L{Notification} objects for the specified notification collection. @param rurl: a user's notification collection URL @type rurl: L{URL} """ assert (isinstance(rurl, URL)) # List all children of the notification collection results = self.getPropertiesOnHierarchy(rurl, (davxml.getcontenttype, )) items = results.keys() items.sort() notifications = [] for path in items: path = urllib.unquote(path) nurl = URL(url=path) if rurl == nurl: continue props = results[path] if props.get(davxml.getcontenttype, "none").split(";")[0] in ("text/xml", "application/xml"): data, _ignore_etag = self.readData(URL(url=path)) node = XML(data) if node.tag == str(csxml.notification): for child in node.getchildren(): if child.tag == str(csxml.invite_notification): notifications.append( InviteNotification().parseFromNotification( nurl, child)) elif child.tag == str(csxml.invite_reply): notifications.append( InviteReply().parseFromNotification( nurl, child)) return notifications def processNotification(self, principal, notification, accept): """ Accept or decline a sharing invite in the specified notification. @param principal: the principal acting on the notification @type principal: L{CalDAVPrincipal} @param notification: the notification @type notification: L{InviteNotification} @param accept: whether to accept C{True} or decline C{False} the invite @type accept: C{bool} """ assert (isinstance(notification.url, URL)) # POST goes to home and we need to figure that out from the notification rurl = principal.homeset[ 0] if notification.shared_type == "calendar" else principal.adbkhomeset[ 0] # Add invitation POST request = ProcessNotification(self, rurl.relativeURL(), notification, accept) # Process it self.runSession(request) if request.getStatusCode() not in ( statuscodes.OK, statuscodes.NoContent, ): self.handleHTTPError(request) return False else: return True def addAttachment(self, rurl, filename, data, contentType, return_representation): assert (isinstance(rurl, URL)) # Create WebDAV POST rurl.extended = "?action=attachment-add" request = Post(self, rurl.relativeURL()) dout = RequestDataString(data, contentType) request.setRequestHeader("Content-Disposition", "attachment;filename=%s" % (filename, )) if return_representation: request.setRequestHeader("Prefer", "return-representation") request.setData(dout, None) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in ( statuscodes.OK, statuscodes.Created, statuscodes.NoContent, ): self.handleHTTPError(request) def updateAttachment(self, rurl, managed_id, filename, data, contentType, return_representation): assert (isinstance(rurl, URL)) # Create WebDAV POST rurl.extended = "?action=attachment-update&managed-id=%s" % ( managed_id, ) request = Post(self, rurl.relativeURL()) request.setRequestHeader("Content-Disposition", "attachment;filename=%s" % (filename, )) if return_representation: request.setRequestHeader("Prefer", "return-representation") dout = RequestDataString(data, contentType) request.setData(dout, None) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in ( statuscodes.OK, statuscodes.Created, statuscodes.NoContent, ): self.handleHTTPError(request) def removeAttachment(self, rurl, managed_id): assert (isinstance(rurl, URL)) # Create WebDAV POST rurl.extended = "?action=attachment-remove&managed-id=%s" % ( managed_id, ) request = Post(self, rurl.relativeURL()) # Process it self.runSession(request) # Check response status if request.getStatusCode() not in ( statuscodes.OK, statuscodes.NoContent, ): self.handleHTTPError(request) def displayHTTPError(self, request): print request.status_code def openSession(self): # Create connection self.connect = SmartHTTPConnection(self.server, self.port, self.ssl, self.afunix) # Write to log file if self.loghttp and self.log: self.log.write( "\n <-------- BEGIN HTTP CONNECTION -------->\n") self.log.write("Server: %s\n" % (self.server, )) def closeSession(self): if self.connect: self.connect.close() self.connect = None # Write to log file if self.loghttp and self.log: self.log.write( "\n <-------- END HTTP CONNECTION -------->\n") def runSession(self, request): ctr = 5 while ctr: ctr -= 1 self.doSession(request) if request and request.isRedirect(): location = request.getResponseHeader(headers.Location) if location: u = URL(location) if not u.scheme or u.scheme in ( "http", "https", ): # Get new server and base RURL different_server = ( self.server != u.server) if u.server else False # Use new host in this session if different_server and not self.noHostRedirect: self.setServer(u.server) # Reset the request with new info request.setURL(u.relativeURL()) request.clearResponse() # Write to log file if self.loghttp and self.log: self.log.write( "\n <-------- HTTP REDIRECT -------->\n" ) self.log.write("Location: %s\n" % (location, )) # Recyle through loop continue # Exit when redirect does not occur break def doSession(self, request): # Do initialisation if not already done if not self.initialised: if not self.initialise(self.server, self.rootPath.relativeURL()): # Break connection with server self.closeConnection() return # Do the request if present if request: # Handle delayed authorization first_time = True while True: # Run the request actions - this will make any connection that is needed self.sendRequest(request) # Check for auth failure if none before if request.getStatusCode() == statuscodes.Unauthorized: # If we had authorization before, then chances are auth details are wrong - so delete and try again with new auth if self.hasAuthorization(): self.authorization = None # Display error so user knows why the prompt occurs again - but not the first time # as we might have a digest re-auth. if not first_time: self.displayHTTPError(request) # Get authorization object (prompt the user) and redo the request self.authorization, cancelled = self.getAuthorizor( first_time, request.getResponseHeaders(headers.WWWAuthenticate)) # Check for auth cancellation if cancelled: self.authorization = None else: first_time = False request.clearResponse() # Repeat the request loop with new authorization continue # If we get here we are complete with auth loop break # Now close it - eventually we will do keep-alive support # Break connection with server self.closeConnection() def doRequest(self, request): # Write request headers self.connect.putrequest(request.method, request.url, skip_host=True, skip_accept_encoding=True) hdrs = request.getRequestHeaders() for header, value in hdrs: self.connect.putheader(header, value) self.connect.endheaders() # Write to log file if self.loghttp and self.log: self.log.write( "\n <-------- BEGIN HTTP REQUEST -------->\n") self.log.write("%s\n" % (request.getRequestStartLine(), )) for header, value in hdrs: self.log.write("%s: %s\n" % (header, value)) self.log.write("\n") # Write the data self.writeRequestData(request) # Blank line in log between if self.loghttp and self.log: self.log.write( "\n <-------- BEGIN HTTP RESPONSE -------->\n") # Get response response = self.connect.getresponse() # Get response headers request.setResponseStatus(response.version, response.status, response.reason) request.setResponseHeaders(response.msg.headers) if self.loghttp and self.log: self.log.write("HTTP/%s %s %s\r\n" % ({ 11: "1.1", 10: "1.0", 9: "0.9" }.get(response.version, "?"), response.status, response.reason)) for hdr in response.msg.headers: self.log.write(hdr) self.log.write("\n") # Now get the data self.readResponseData(request, response) # Trailer in log if self.loghttp and self.log: self.log.write("\n <-------- END HTTP RESPONSE -------->\n") def handleHTTPError(self, request): print "Ignoring error: %d" % (request.getStatusCode(), ) def getAuthorizor(self, first_time, wwwhdrs): for witem in wwwhdrs: for item in witem.split(","): item = item.strip() if item.lower().startswith("basic"): return Basic(self.user, self.pswd), False elif item.lower().startswith("digest"): return Digest(self.user, self.pswd, wwwhdrs), False elif item.lower().startswith( "negotiate") and Kerberos is not None: return Kerberos(self.user), False else: return None, True def writeRequestData(self, request): # Write the data if any present if request.hasRequestData(): stream = request.getRequestData() if stream: # Tell data we are using it stream.start() # Buffered write from stream more = True while more: data, more = stream.read() if data: self.connect.send(data) if self.loghttp and self.log: self.log.write(data) # Tell data we are done using it stream.stop() def readResponseData(self, request, response): # Check for data and write it data = response.read() if request.hasResponseData(): stream = request.getResponseData() stream.start() stream.write(data) stream.stop() else: response.read() if self.loghttp and self.log: self.log.write(data) def setServerType(self, type): self.type = type def setServerDescriptor(self, txt): self.descriptor = txt def setServerCapability(self, txt): self.capability = txt