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