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 _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 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 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 ensureSharees(self, session, calendarhref, n): """ Make sure the required number of sharees are present in the calendar. @param n: number of sharees @type n: C{int} """ users = [] uids = [] for i in range(n - self.currentCount): index = self.currentCount + i + 2 users.append("user%02d" % (index, )) uids.append("urn:x-uid:10000000-0000-0000-0000-000000000%03d" % (index, )) session.addInvitees(URL(path=calendarhref), uids, True) # Now accept each one for user in users: acceptor = SQLUsageSession(self.server, self.port, user=user, pswd=user, root="/", calendar="shared") notifications = acceptor.getNotifications( URL(path=acceptor.notificationHref)) principal = principalCache.getPrincipal(acceptor, acceptor.principalPath) acceptor.processNotification(principal, notifications[0], True) self.currentCount = n
def _warmUp(): # Warm-up server by doing calendar home and child collection propfinds. # Do this twice because the very first time might provision DB objects and # blow any DB cache - the second time will warm the DB cache. props = (davxml.resourcetype,) for _ignore in range(2): for session in sessions: session.getPropertiesOnHierarchy(URL(path=session.homeHref), props) session.getPropertiesOnHierarchy(URL(path=session.calendarHref), props) session.getPropertiesOnHierarchy(URL(path=session.inboxHref), props) session.getPropertiesOnHierarchy(URL(path=session.notificationHref), props)
def runLoop(self, sharee_counts): # Make the sessions sessions = [ SQLUsageSession(self.server, self.port, user=user, pswd=pswd, root="/", calendar="shared") for user, pswd in itertools.izip(self.users, self.pswds) ] sessions = sessions[0:1] # Create the calendar first sessions[0].makeCalendar(URL(path=sessions[0].calendarHref)) # Set of requests to execute requests = [ MultigetTest("mget-1" if self.compact else "multiget-1", sessions, self.logFilePath, "share", 1), MultigetTest("mget-50" if self.compact else "multiget-50", sessions, self.logFilePath, "share", 50), PropfindInviteTest("propfind", sessions, self.logFilePath, "share", 1), SyncTest("s-full" if self.compact else "sync-full", sessions, self.logFilePath, "share", True, 0), SyncTest("s-1" if self.compact else "sync-1", sessions, self.logFilePath, "share", False, 1), QueryTest("q-1" if self.compact else "query-1", sessions, self.logFilePath, "share", 1), QueryTest("q-10" if self.compact else "query-10", sessions, self.logFilePath, "share", 10), PutTest("put", sessions, self.logFilePath, "share"), ] self.requestLabels = [request.label for request in requests] # Warm-up server by doing shared calendar propfinds props = (davxml.resourcetype, ) for session in sessions: session.getPropertiesOnHierarchy(URL(path=session.calendarHref), props) # Now loop over sets of events for count in sharee_counts: print("Testing count = %d" % (count, )) self.ensureSharees(sessions[0], sessions[0].calendarHref, count) result = {} for request in requests: print(" Test = %s" % (request.label, )) result[request.label] = request.execute(count) self.results[count] = result
def del_folder (self, itemid, store=None): """Get rid of the specified folder.""" sess = self.session() path = URL(url=itemid) logging.info('Deleting all the contained items. Will not remove folder') items = sess.getPropertiesOnHierarchy(path, (davxml.getetag,)) hrefs = [x for x in items.keys() if x != path.toString().strip()] for href in hrefs: sess.deleteResource(URL(url=href)) logging.info('Deleted file %s...', href)
def del_folder(self, itemid, store=None): """Get rid of the specified folder.""" sess = self.session() path = URL(url=itemid) logging.info("Deleting all the contained items. Will not remove folder") items = sess.getPropertiesOnHierarchy(path, (davxml.getetag,)) hrefs = [x for x in items.keys() if x != path.toString().strip()] for href in hrefs: sess.deleteResource(URL(url=href)) logging.info("Deleted file %s...", href)
def prepare(self): """ Do some setup prior to the real request. """ if not self.full: # Get current sync token results, _ignore_bad = self.sessions[0].getProperties(URL(path=self.sessions[0].calendarHref), (davxml.sync_token,)) self.synctoken = results[davxml.sync_token] # Add resources to create required number of changes now = DateTime.getNowUTC() for i in range(self.count): href = joinURL(self.sessions[0].calendarHref, "sync-collection-%d.ics" % (i + 1,)) self.sessions[0].writeData(URL(path=href), ICAL % (now.getYear() + 1, i + 1,), "text/calendar")
def fetch_folders (self): """Fetch and return the list of addressbooks from the server.""" logging.debug('CDPIMDB.set_folders(): Begin') sess = self.session() roots = self.get_contacts_folders_roots() props = (davxml.resourcetype, davxml.getlastmodified,) ret = [] for root in roots: path = root.path logging.debug('Processing Root path %s in Root.', path) results = sess.getPropertiesOnHierarchy(URL(url=path), props) items = results.keys() items.sort() for rurl in items: rurl = urllib.unquote(rurl) if rurl == path: continue props = results[rurl] rtype = props.get(davxml.resourcetype) if not isinstance(rtype, str): for child in rtype.getchildren(): if child.tag == carddavxml.addressbook: name = rurl[len(path):-1] logging.debug('Found Folder %-15s in URI "%s"', name, rurl) ret.append((rurl.strip(), path, name)) logging.debug('CDPIMDB.set_folders(): Done.') return ret
def execute(self, cmdname, options): opts, args = getopt.getopt(shlex.split(options), '') data = None content_type = None path = None for name, _ignore_value in opts: print "Unknown option: %s" % (name, ) print self.usage(cmdname) raise WrongOptions if len(args) > 1: print "Wrong number of arguments: %d" % (len(args), ) print self.usage(cmdname) raise WrongOptions elif args: path = args[0] if not path.startswith("/"): path = os.path.join(self.shell.wd, path) else: path = self.shell.wd resource = URL(url=path) self.shell.account.session.writeData(resource, data, content_type, method="OPTIONS") return True
def execute(self, cmdname, options): opts, args = getopt.getopt(shlex.split(options), '') if len(opts) != 0: print self.usage(cmdname) raise WrongOptions if len(args) > 1: print "Wrong number of arguments: %d" % (len(args), ) print self.usage(cmdname) raise WrongOptions pid = args[0] if args else None if pid and pid.find("/") == -1: pid = "/principals/__uids__/%s/" % pid ppath = URL(url=pid) if pid else None principal = self.shell.account.getPrincipal(ppath) if principal is None: print "No principal found for %s" % (ppath if ppath else "current principal") raise CommandError homeset = principal.adbkhomeset if not homeset: print "No address book home set found for %s" % ( principal.principalPath, ) raise CommandError newpath = homeset[0].path result = self.shell.setWD(newpath) if not result: print "%s: No such directory" % (newpath, ) return result
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 cleanup(self): """ Do some cleanup after the real request. """ # Remove created resources href = joinURL(self.sessions[0].calendarHref, "put.ics") self.sessions[0].deleteResource(URL(path=href))
def set_def_folders(self): """See the documentation in class PIMDB""" root = self.get_def_root_folder_path() props = (carddavxml.default_addressbook_url, ) res, bad = self.session().getProperties(URL(url=root), props) uris = res.values() if len(uris) > 0: def_uri = uris[0].toString().strip() logging.debug('Looking for default folder: "%s"', def_uri) def_f, t = self.find_folder(def_uri) else: ## In some instances the server does not respond properly to the ## def_adbk_url property request. In this case we will just pick ## the first folder. One hopes every addressbook server will have ## at least one server logging.debug('Coud not find default adbk Property.') fs = self.get_contacts_folders() assert (len(fs) > 0) def_f = fs[0] logging.debug('Setting first available folder as default: %s', def_f.get_name()) self.set_def_folder(Folder.CONTACT_t, def_f)
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
def printACE(ace, account): principalText = ace.principal if principalText == str(davxml.href): principal = account.getPrincipal(URL(url=ace.data)) principalText = "%s (%s)" % (principal.getSmartDisplayName(), principal.principalURL) elif principalText == str(davxml.all): principalText = "ALL" elif principalText == str(davxml.authenticated): principalText = "AUTHENTICATED" elif principalText == str(davxml.unauthenticated): principalText = "UNAUTHENTICATED" elif principalText == str(davxml.property): principalText = "PROPERTY: %s" % (ace.data, ) result = "Principal: %s\n" % (principalText, ) if ace.invert or ace.protected or ace.inherited: result += " Status:" if ace.invert: result += " INVERTED" if ace.protected: result += " PROTECTED" if ace.inherited: result += " INHERITED" result += "\n" result += " Grant: " if ace.grant else " Deny: " result += ", ".join(ace.privs) result += "\n" return result
def execute(self, cmdname, options): opts, args = getopt.getopt(shlex.split(options), 'n') doURLDecode = False for name, _ignore_value in opts: if name == "-n": doURLDecode = True else: print "Unknown option: %s" % (name, ) print self.usage(cmdname) raise WrongOptions if len(args) != 2: print "Wrong number of arguments: %d" % (len(args), ) print self.usage(cmdname) raise WrongOptions while True: result = raw_input("Really move resource '%s' to '%s' [y/n]: " % ( args[0], args[1], )) if readline.get_current_history_length(): readline.remove_history_item( readline.get_current_history_length() - 1) if not result: continue if result[0] == "n": return True elif result[0] == "y": break fromResource = args[0] if not fromResource.startswith("/"): fromResource = os.path.join(self.shell.wd, fromResource) toResource = args[1] if not toResource.startswith("/"): toResource = os.path.join(self.shell.wd, toResource) resourceFrom = URL(url=fromResource, decode=doURLDecode) resourceTo = URL(url=self.shell.server + toResource, decode=doURLDecode) self.shell.account.session.moveResource(resourceFrom, resourceTo) return True
def execute(self, cmdname, options): fname = None content_type = None path = None opts, args = getopt.getopt(shlex.split(options), 'acf:') for name, value in opts: if name == "-f": fname = value elif name == "-a": if content_type: raise WrongOptions content_type = "text/vcard" elif name == "-c": content_type = "text/calendar" if content_type: raise WrongOptions else: print "Unknown option: %s" % (name, ) print self.usage(cmdname) raise WrongOptions if not fname: print "File name must be provided" print self.usage(cmdname) raise WrongOptions elif len(args) > 1: print "Wrong number of arguments: %d" % (len(args), ) print self.usage(cmdname) raise WrongOptions elif args: path = args[0] if not path.startswith("/"): path = os.path.join(self.shell.wd, path) if not path.endswith("/"): print "Can only POST to a directory: %s" % (path, ) print self.usage(cmdname) raise WrongOptions else: print "Path to POST to must be provided" print self.usage(cmdname) raise WrongOptions # Read in data try: data = open(os.path.expanduser(fname), "r").read() except IOError: print "Unable to read data from file: %s" % (fname, ) print self.usage(cmdname) raise WrongOptions resource = URL(url=path) self.shell.account.session.importData(resource, data, content_type) return True
def doRequest(self): """ Execute the actual HTTP request. """ now = DateTime.getNowUTC() href = joinURL(self.sessions[0].calendarHref, "put.ics") self.sessions[0].writeData(URL(path=href), ICAL % (now.getYear() + 1,), "text/calendar")
def setWD(self, newwd): # Check that the new one exists resource = (newwd if newwd.endswith("/") else newwd + "/") if not self.account.session.testResource(URL(url=resource)): return False self.prefix = self.wd = newwd return True
def put_item(self, name, data, content_type, etag=None): path = URL(url=name) res = self.get_db().session().writeData(path, data, content_type, etag=etag) return name
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 cleanup(self): """ Do some cleanup after the real request. """ # Remove created resources for i in range(self.count): href = joinURL(self.sessions[0].calendarHref, "tr-query-%d.ics" % (i + 1,)) self.sessions[0].deleteResource(URL(path=href))
def execute(self, cmdname, options): names = False all_props = False xmllist = False prop = None path = None opts, args = getopt.getopt(shlex.split(options), 'alnp:') for name, value in opts: if name == "-a": all_props = True elif name == "-l": xmllist = True elif name == "-n": names = True elif name == "-p": prop = value else: print "Unknown option: %s" % (name, ) print self.usage(cmdname) raise WrongOptions if len(args) > 1: print "Wrong number of arguments: %d" % (len(args), ) print self.usage(cmdname) raise WrongOptions elif args: path = args[0] if not path.startswith("/"): path = os.path.join(self.shell.wd, path) else: path = self.shell.wd if not path.endswith("/"): path += "/" resource = URL(url=path) if names: results = self.shell.account.session.getPropertyNames(resource) print " Properties: %s" % (utils.printList(results), ) else: if all_props: props = None elif prop: props = (prop, ) else: props = self.shell.account.session.getPropertyNames(resource) results, bad = self.shell.account.session.getProperties( resource, props, xmllist) print "OK Properties:" utils.printProperties(results) if bad: print "Failed Properties:" utils.printProperties(bad) return True
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 getAllDetails(self): if self.details is None: resource = URL(url=self.path + "/") props = self.session.account.session.getPropertyNames(resource) results, _ignore_bad = self.session.account.session.getProperties(resource, props) sorted = results.keys() sorted.sort() self.details = [(key, results[key],) for key in sorted] return self.details
def del_itemids(self, itemids): sess = self.get_db().session() for itemid in itemids: try: sess.deleteResource(URL(url=self.item_path(itemid))) self.del_contact(itemid) logging.info('Deleted CardDAV server contact %s...', itemid) except HTTPError, e: logging.error('Could not delete itemid: %s (%s)', itemid, e)
def cleanup(self): """ Do some cleanup after the real request. """ # Remove created resources href = joinURL(self.sessions[1].calendarHref, "organizer.ics") self.sessions[1].deleteResource(URL(path=href)) # Remove the attendee event and inbox items props = (davxml.resourcetype,) results = self.sessions[0].getPropertiesOnHierarchy(URL(path=self.sessions[0].calendarHref), props) for href in results.keys(): if len(href.split("/")[-1]) > 10: self.sessions[0].deleteResource(URL(path=href)) results = self.sessions[0].getPropertiesOnHierarchy(URL(path=self.sessions[0].inboxHref), props) for href in results.keys(): if href != self.sessions[0].inboxHref: self.sessions[0].deleteResource(URL(path=href))
def getDetails(self): resource = URL(url=self.path + "/") props = (davxml.resourcetype,) props += (davxml.getcontentlength, davxml.getlastmodified,) props, _ignore_bad = self.session.account.session.getProperties(resource, props) size = props.get(davxml.getcontentlength, "-") if not size: size = "0" modtime = props.get(davxml.getlastmodified, "-") return ["Size: %s" % (size,), "Modtime: %s" % (modtime,)]
def parsePropElementHref(self, prop, result, is_list): # Grab the element data hrefs = tuple([URL(url=href.text, decode=True) for href in prop.findall(str(davxml.href))]) if not is_list: if len(hrefs) == 1: hrefs = hrefs[0] else: hrefs = "" result.addHrefProperty(QName(prop.tag), hrefs) result.addNodeProperty(QName(prop.tag), prop)
def _getProperties(self): assert (self.session is not None) results, _ignore_bad = self.session.getProperties( URL(url=self.path), ( davxml.displayname, carddavxml.addressbook_description, )) self.displayname = results.get(davxml.displayname, "") self.description = results.get(carddavxml.addressbook_description, "")
def execute(self, cmdname, options): fname = None content_type = "text/plain" path = None opts, args = getopt.getopt(shlex.split(options), 'f:t:') for name, value in opts: if name == "-f": fname = value elif name == "-t": content_type = value else: print "Unknown option: %s" % (name, ) print self.usage(cmdname) raise WrongOptions if not fname: print "File name must be provided" print self.usage(cmdname) raise WrongOptions elif len(args) > 1: print "Wrong number of arguments: %d" % (len(args), ) print self.usage(cmdname) raise WrongOptions elif args: path = args[0] if not path.startswith("/"): path = os.path.join(self.shell.wd, path) if path.endswith("/"): print "Cannot PUT to a directory: %s" % (path, ) print self.usage(cmdname) raise WrongOptions else: print "Path to PUT to must be provided" print self.usage(cmdname) raise WrongOptions # Read in data fname = os.path.expanduser(fname) try: with open(fname, "r") as f: data = f.read() except IOError: print "Unable to read data from file: %s" % (fname, ) print self.usage(cmdname) raise WrongOptions resource = URL(url=path) self.shell.account.session.writeData(resource, data, content_type) return True
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
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 _refresh_contacts (self): logging.debug('Refreshing Contacts for folder %s...', self.get_name()) self.reset_contacts() ## Now fetch from server sess = self.get_db().session() path = URL(url=self.get_itemid()) props = (davxml.getetag,) items = sess.getPropertiesOnHierarchy(path, props) hrefs = [x for x in items.keys() if x != path.toString().strip()] etags = [items[x].get(davxml.getetag, "-") for x in items.keys()] cons = self.find_items(hrefs) for con in cons: self.add_contact(con) logging.debug('Successfully fetched and added contact: %s', con.get_disp_name()) logging.debug('Refreshing Contacts for folder %s..done.', self.get_name())
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
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
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