def calendar_home_set(self, url): if isinstance(url, CalendarSet): self._calendar_home_set = url else: if not is_url(url): self._calendar_home_set = CalendarSet(self.client, self.client.url.join(URL.objectify(url))) else: self.client.url = URL.objectify(url) self.client.handle = httplib.HTTPSConnection(self.client.url.hostname, self.client.url.port) self._calendar_home_set = CalendarSet(self.client, URL.objectify(url))
def __init__(self, client=None, url=None): """ url input is for backward compatibility and should normally be avoided. If url is not given, deduct principal path as well as calendar home set path from doing propfinds. """ self.client = client self._calendar_home_set = None ## backwards compatibility. if url is not None: self.url = client.url.join(URL.objectify(url)) else: self.url = self.client.url cup = self.get_properties([dav.CurrentUserPrincipal()]) self.url = self.client.url.join(URL.objectify(cup['{DAV:}current-user-principal']))
def __init__(self, client=None, url=None, parent=None, name=None, id=None, **extra): """ Default constructor. Parameters: * client: A DAVClient instance * url: The url for this object. May be a full URL or a relative URL. * parent: The parent object - used when creating objects * name: A displayname * id: The resource id (UID for an Event) """ if client is None and parent is not None: client = parent.client self.client = client self.parent = parent self.name = name self.id = id self.extra_init_options = extra # url may be a path relative to the caldav root if client and url: self.url = client.url.join(url) else: self.url = URL.objectify(url)
def __init__(self, client=None, url=None, parent=None, name=None, id=None, etag=None, **extra): """ Default constructor. Parameters: * client: A DAVClient instance * url: The url for this object. May be a full URL or a relative URL. * parent: The parent object - used when creating objects * name: A displayname * id: The resource id (UID for an Event) """ if client is None and parent is not None: client = parent.client self.client = client self.parent = parent self.name = name self.id = id self.etag = etag self.extra_init_options = extra ## url may be a path relative to the caldav root if client and url: self.url = client.url.join(url) else: self.url = URL.objectify(url)
def testAbsoluteURL(self): """Version 0.7.0 does not handle responses with absolute URLs very well, ref https://github.com/python-caldav/caldav/pull/103""" ## none of this should initiate any communication client = DAVClient(url='http://cal.example.com/') principal = Principal(client=client, url='http://cal.example.com/home/bernard/') ## now, ask for the calendar_home_set, but first we need to mock up client.propfind mocked_response = mock.MagicMock() mocked_response.status_code = 207 mocked_response.reason = 'multistatus' mocked_response.headers = {} mocked_response.content = """ <xml> <d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"> <d:response> <d:href>http://cal.example.com/home/bernard/</d:href> <d:propstat> <d:prop> <c:calendar-home-set> <d:href>http://cal.example.com/home/bernard/calendars/</d:href> </c:calendar-home-set> </d:prop> <d:status>HTTP/1.1 200 OK</d:status> </d:propstat> </d:response> </d:multistatus> </xml>""" mocked_davresponse = DAVResponse(mocked_response) client.propfind = mock.MagicMock(return_value=mocked_davresponse) bernards_calendars = principal.calendar_home_set assert_equal(bernards_calendars.url, URL('http://cal.example.com/home/bernard/calendars/'))
def search(self, xml, comp_class=None): """ Takes some input XML, does a report query on a calendar object and returns the resource objects found. Partly solves https://github.com/python-caldav/caldav/issues/16 TODO: this code is duplicated many places, we ought to do more code refactoring """ matches = [] response = self._query(xml, 1, 'report') results = self._handle_prop_response(response=response, props=[cdav.CalendarData()]) for r in results: data = results[r][cdav.CalendarData.tag] if comp_class is None: comp_class = self._calendar_comp_class_by_data(data) if comp_class is None: ## Ouch, we really shouldn't get here. This probably ## means we got some bad data from the server. I've ## observed receiving a VCALENDAR from Baikal that did ## not contain anything. Let's assume the data is ## void and should not be counted. continue url = URL(r) if url.hostname is None: # Quote when result is not a full URL r = quote(r) matches.append( comp_class(self.client, url=self.url.join(r), data=data, parent=self)) return matches
def children(self, type=None): """ List children, using a propfind (resourcetype) on the parent object, at depth = 1. """ c = [] depth = 1 properties = {} props = [dav.ResourceType(), ] response = self._query_properties(props, depth) for r in response.tree.findall(dav.Response.tag): # We use canonicalized urls to index children href = str(self.url.join(URL.objectify(r.find(dav.Href.tag).text)).canonical()) assert(href) properties[href] = {} for p in props: t = r.find(".//" + p.tag) if len(list(t)) > 0: if type is not None: val = t.find(".//" + type) else: val = t.find(".//*") if val is not None: val = val.tag else: val = None else: val = t.text properties[href][p.tag] = val for path in properties.keys(): resource_type = properties[path][dav.ResourceType.tag] if resource_type == type or type is None: path = URL.objectify(path) ## TODO: investigate the RFCs thoroughly - why does a "get ## members of this collection"-request also return the collection URL itself? ## And why is the strip_trailing_slash-method needed? The collection URL ## should always end with a slash according to RFC 2518, section 5.2. if self.url.strip_trailing_slash() != path.strip_trailing_slash(): c.append((path, resource_type)) return c
def request(self, url, method="GET", body="", headers={}): """ Actually sends the request """ # objectify the url url = URL.objectify(url) proxies = None if self.proxy is not None: proxies = {url.scheme: self.proxy} log.debug("using proxy - %s" % (proxies)) # ensure that url is a unicode string url = str(url) combined_headers = dict(self.headers) combined_headers.update(headers) if body is None or body == "" and "Content-Type" in combined_headers: del combined_headers["Content-Type"] log.debug( "sending request - method={0}, url={1}, headers={2}\nbody:\n{3}" .format(method, url, combined_headers, body)) auth = None if self.auth is None and self.username is not None: auth = requests.auth.HTTPDigestAuth(self.username, self.password) else: auth = self.auth r = requests.request(method, url, data=to_wire(body), headers=combined_headers, proxies=proxies, auth=auth, verify=self.ssl_verify_cert) response = DAVResponse(r) # If server supports BasicAuth and not DigestAuth, let's try again: if response.status == 401 and self.auth is None and auth is not None: auth = requests.auth.HTTPBasicAuth(self.username, self.password) r = requests.request(method, url, data=to_wire(body), headers=combined_headers, proxies=proxies, auth=auth, verify=self.ssl_verify_cert) response = DAVResponse(r) # this is an error condition the application wants to know if response.status == requests.codes.forbidden or \ response.status == requests.codes.unauthorized: ex = error.AuthorizationError() ex.url = url ex.reason = response.reason raise ex # let's save the auth object and remove the user/pass information if not self.auth and auth: self.auth = auth del self.username del self.password return response
def calendar_home_set(self, url): if isinstance(url, CalendarSet): self._calendar_home_set = url return sanitized_url = URL.objectify(url) if sanitized_url.hostname and sanitized_url.hostname != self.client.url.hostname: ## icloud (and others?) having a load balanced system, where each principal resides on one named host self.client.url = sanitized_url self._calendar_home_set = CalendarSet(self.client, self.client.url.join(sanitized_url))
def __init__(self, url, proxy=None, username=None, password=None, auth=None, ssl_verify_cert=True, timeout=None): """ Sets up a HTTPConnection object towards the server in the url. Parameters: * url: A fully qualified url: `scheme://user:pass@hostname:port` * proxy: A string defining a proxy server: `hostname:port` * username and password should be passed as arguments or in the URL * auth and ssl_verify_cert is passed to requests.request. ** ssl_verify_cert can be the path of a CA-bundle or False. """ self.session = requests.Session() log.debug("url: " + str(url)) self.url = URL.objectify(url) self.timeout = timeout # Prepare proxy info if proxy is not None: self.proxy = proxy # requests library expects the proxy url to have a scheme if re.match('^.*://', proxy) is None: self.proxy = self.url.scheme + '://' + proxy # add a port is one is not specified # TODO: this will break if using basic auth and embedding # username:password in the proxy URL p = self.proxy.split(":") if len(p) == 2: self.proxy += ':8080' log.debug("init - proxy: %s" % (self.proxy)) # Build global headers self.headers = { "User-Agent": "Mozilla/5.0", "Content-Type": "text/xml", "Accept": "text/xml, text/calendar" } if self.url.username is not None: username = unquote(self.url.username) password = unquote(self.url.password) self.username = username self.password = password self.auth = auth # TODO: it's possible to force through a specific auth method here, # but no test code for this. self.ssl_verify_cert = ssl_verify_cert self.url = self.url.unauth() log.debug("self.url: " + str(url))
def calendar_home_set(self, url): if isinstance(url, CalendarSet): self._calendar_home_set = url return sanitized_url = URL.objectify(url) if sanitized_url.hostname and sanitized_url.hostname != self.client.url.hostname: ## icloud (and others?) having a load balanced system, where each principal resides on one named host self.client.url = sanitized_url self._calendar_home_set = CalendarSet( self.client, self.client.url.join(sanitized_url))
def __init__(self, client=None, url=None): """ Returns a Principal. Parameters: * client: a DAVClient() oject * url: Deprecated - for backwards compatibility purposes only. If url is not given, deduct principal path as well as calendar home set path from doing propfinds. """ self.client = client self._calendar_home_set = None ## backwards compatibility. if url is not None: self.url = client.url.join(URL.objectify(url)) else: self.url = self.client.url cup = self.get_properties([dav.CurrentUserPrincipal()]) self.url = self.client.url.join(URL.objectify(cup['{DAV:}current-user-principal']))
def __init__(self, client=None, url=None): """ Returns a Principal. Parameters: * client: a DAVClient() oject * url: Deprecated - for backwards compatibility purposes only. If url is not given, deduct principal path as well as calendar home set path from doing propfinds. """ self.client = client self._calendar_home_set = None ## backwards compatibility. if url is not None: self.url = client.url.join(URL.objectify(url)) else: self.url = self.client.url cup = self.get_properties([dav.CurrentUserPrincipal()]) self.url = self.client.url.join( URL.objectify(cup['{DAV:}current-user-principal']))
def children(self, type=None): """ List children, using a propfind (resourcetype) on the parent object, at depth = 1. """ c = [] depth = 1 properties = {} props = [dav.ResourceType(), ] response = self._query_properties(props, depth) for r in response.tree.findall(dav.Response.tag): # We use canonicalized urls to index children href = str(self.url.join(URL.objectify(r.find(dav.Href.tag).text)).canonical()) assert(href) properties[href] = {} for p in props: t = r.find(".//" + p.tag) if len(list(t)) > 0: if type is not None: val = t.find(".//" + type) else: val = t.find(".//*") if val is not None: val = val.tag else: val = None else: val = t.text properties[href][p.tag] = val for path in properties.keys(): resource_type = properties[path][dav.ResourceType.tag] if resource_type == type or type is None: if self.url != path: c.append((URL.objectify(path), resource_type)) return c
def save(self): """ The save method for a calendar is only used to create it, for now. We know we have to create it when we don't have a url. Returns: * self """ if self.url is None: self._create(self.name, self.id) if not self.url.endswith('/'): self.url = URL.objectify(str(self.url) + '/') return self
def save(self): """ The save method for a calendar is only used to create it, for now. We know we have to create it when we don't have a url. Returns: * self """ if self.url is None: self._create(name=self.name, id=self.id, **self.extra_init_options) if not self.url.endswith('/'): self.url = URL.objectify(str(self.url) + '/') return self
class DAVClientMock: """Mock class for DAVClient instance.""" url = URL.objectify("http://localhost") def mkcalendar(self, url): return True def delete(self, url): return True def proppatch(self, url, body, dummy=None): return Response(200) def request(self, url, method="GET", body="", headers={}): return Response(200)
def calendar_home_set(self, url): if isinstance(url, CalendarSet): self._calendar_home_set = url return sanitized_url = URL.objectify(url) ## TODO: sanitized_url should never be None, this needs more ## research. added here as it solves real-world issues, ref ## https://github.com/python-caldav/caldav/pull/56 if sanitized_url is not None: if (sanitized_url.hostname and sanitized_url.hostname != self.client.url.hostname): # icloud (and others?) having a load balanced system, # where each principal resides on one named host self.client.url = sanitized_url self._calendar_home_set = CalendarSet( self.client, self.client.url.join(sanitized_url))
def __init__(self, url, proxy=None, username=None, password=None, headers=None): """ Sets up a HTTPConnection object towards the server in the url. Parameters: * url: A fully qualified url: `scheme://user:pass@hostname:port` * proxy: A string defining a proxy server: `hostname:port` """ self.url = URL.objectify(url) # Prepare proxy info if proxy is not None: # TODO: this will break if using basic auth and embedding # username:password in the proxy URL self.proxy = proxy.split(":") if len(self.proxy) == 1: self.proxy.append(8080) else: self.proxy[1] = int(self.proxy[1]) # Build global headers self.headers = {"User-Agent": "Mozilla/5.0", "Content-Type": "text/xml", "Accept": "text/xml"} if headers: self.headers.update(headers) if self.url.username is not None: username = urllib.unquote(self.url.username) password = urllib.unquote(self.url.password) if username is not None: hash = (("%s:%s" % (username, password)) .encode('base64')[:-1]) self.headers['authorization'] = "Basic %s" % hash # Connection proxy if self.proxy is not None: self.handle = httplib.HTTPConnection(*self.proxy) # direct, https # TODO: we shouldn't use SSL on http://weird.server.example.com:443/ elif self.url.port == 443 or self.url.scheme == 'https': self.handle = httplib.HTTPSConnection(self.url.hostname, self.url.port) # direct, http else: self.handle = httplib.HTTPConnection(self.url.hostname, self.url.port) self.url = self.url.unauth()
def object_by_uid(self, uid, comp_filter=None): """ Get one event from the calendar. Parameters: * uid: the event uid Returns: * Event() or None """ data = cdav.CalendarData() prop = dav.Prop() + data query = cdav.TextMatch(uid) query = cdav.PropFilter("UID") + query if comp_filter: query = comp_filter + query vcalendar = cdav.CompFilter("VCALENDAR") + query filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] response = self._query(root, 1, 'report') if response.status == 404: raise error.NotFoundError("%s not found on server" % uid) elif response.status == 400: raise error.ReportError(errmsg(response)) items_found = response.tree.findall(".//" + dav.Response.tag) for r in items_found: href = unquote(r.find(".//" + dav.Href.tag).text) data = unquote(r.find(".//" + cdav.CalendarData.tag).text) # Ref Lucas Verney, we've actually done a substring search, if the # uid given in the query is short (i.e. just "0") we're likely to # get false positives back from the server. # # Long uids are folded, so splice the lines together here before # attempting a match. item_uid = re.search(r'\nUID:((.|\n[ \t])*)\n', data) if (not item_uid or re.sub(r'\n[ \t]', '', item_uid.group(1)) != uid): continue return self._calendar_comp_class_by_data(data)( self.client, url=URL.objectify(href), data=data, parent=self) raise error.NotFoundError("%s not found on server" % uid)
def _create(self, data, id=None, path=None): if id is None and path is not None and str(path).endswith('.ics'): id = re.search('(/|^)([^/]*).ics',str(path)).group(2) elif id is None: id = self.instance.vevent.uid.value if path is None: path = id + ".ics" path = self.parent.url.join(path) r = self.client.put(path, data, {"Content-Type": 'text/calendar; charset="utf-8"'}) if r.status == 302: path = [x[1] for x in r.headers if x[0]=='location'][0] elif not (r.status in (204, 201)): raise error.PutError(r.raw) self.url = URL.objectify(path) self.id = id
def object_by_uid(self, uid, comp_filter=None): """ Get one event from the calendar. Parameters: * uid: the event uid Returns: * Event() or None """ data = cdav.CalendarData() prop = dav.Prop() + data query = cdav.TextMatch(uid) query = cdav.PropFilter("UID") + query if comp_filter: query = comp_filter + query vcalendar = cdav.CompFilter("VCALENDAR") + query filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] response = self._query(root, 1, 'report') if response.status == 404: raise error.NotFoundError(errmsg(response)) elif response.status == 400: raise error.ReportError(errmsg(response)) items_found = response.tree.findall(".//" + dav.Response.tag) for r in items_found: href = unquote(r.find(".//" + dav.Href.tag).text) data = unquote(r.find(".//" + cdav.CalendarData.tag).text) # Ref Lucas Verney, we've actually done a substring search, if the # uid given in the query is short (i.e. just "0") we're likely to # get false positives back from the server. if not "\nUID:%s\n" % uid in data: # TODO: optimistic assumption, uid line is not folded. We # need to unfold the content to be 100% sure that we won't # filter away true positives here. continue return self._calendar_comp_class_by_data(data)( self.client, url=URL.objectify(href), data=data, parent=self) raise error.NotFoundError(errmsg(response))
def _query(self, root=None, depth=0, query_method='propfind', url=None, expected_return_value=None): """ This is an internal method for doing a query. It's a result of code-refactoring work, attempting to consolidate similar-looking code into a common method. """ # ref https://bitbucket.org/cyrilrbt/caldav/issues/46 - # COMPATIBILITY ISSUE. The lines below seems to solve real # world problems, though I believe it's the wrong place to # inject the missing slash. # TODO: find out why the slash is missing and fix # it properly. # Background: Collection URLs ends with a slash, # non-collection URLs does not end with a slash. If the # slash is missing, Servers MAY pretend it's present (RFC # 4918, section 5.2, collection resources), hence only some # few servers break when the slash is missing. RFC 4918 # specifies that collection URLs end with a slash while # non-collection URLs should not end with a slash. if url is None: url = self.url if not url.endswith('/'): url = URL(str(url) + '/') body = "" if root: if hasattr(root, 'xmlelement'): body = etree.tostring(root.xmlelement(), encoding="utf-8", xml_declaration=True) else: body = root ret = getattr(self.client, query_method)(url, body, depth) if ret.status == 404: raise error.NotFoundError(errmsg(ret)) if ((expected_return_value is not None and ret.status != expected_return_value) or ret.status >= 400): raise error.exception_by_method[query_method](errmsg(ret)) return ret
def __init__(self, client=None, url=None, parent=None, name=None, id=None, etag=None): """ Default constructor. Parameters: * client: A DAVClient instance * url: The url for this object * parent: The parent object - used when creating objects * name: A displayname * id: The resource id (UID for an Event) """ if client is None and parent is not None: client = parent.client self.client = client self.parent = parent self.name = name self.id = id self.url = URL.objectify(url)
def _create(self, data, id=None, path=None): if id is None and path is not None and str(path).endswith('.ics'): id = re.search('(/|^)([^/]*).ics', str(path)).group(2) elif id is None: for obj_type in ('vevent', 'vtodo', 'vjournal', 'vfreebusy'): obj = None if hasattr(self.vobject_instance, obj_type): obj = getattr(self.vobject_instance, obj_type) elif self.vobject_instance.name.lower() == obj_type: obj = self.vobject_instance if obj is not None: try: id = obj.uid.value except AttributeError: id = str(uuid.uuid1()) obj.add('uid') obj.uid.value = id break else: for obj_type in ('vevent', 'vtodo', 'vjournal', 'vfreebusy'): obj = None if hasattr(self.vobject_instance, obj_type): obj = getattr(self.vobject_instance, obj_type) elif self.vobject_instance.name.lower() == obj_type: obj = self.vobject_instance if obj is not None: if not hasattr(obj, 'uid'): obj.add('uid') obj.uid.value = id break if path is None: path = quote(id) + ".ics" path = self.parent.url.join(path) r = self.client.put(path, data, {"Content-Type": 'text/calendar; charset="utf-8"'}) if r.status == 302: path = [x[1] for x in r.headers if x[0] == 'location'][0] elif not (r.status in (204, 201)): raise error.PutError(errmsg(r)) self.url = URL.objectify(path) self.id = id
def event_by_uid(self, uid): """ Get one event from the calendar. Parameters: * uid: the event uid Returns: * Event() or None """ e = None data = cdav.CalendarData() etag = dav.ETag() prop = dav.Prop() + etag + data match = cdav.TextMatch(uid) propf = cdav.PropFilter("UID") + match vevent = cdav.CompFilter("VEVENT") + propf vcalendar = cdav.CompFilter("VCALENDAR") + vevent filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] q = etree.tostring(root.xmlelement(), encoding="utf-8", xml_declaration=True) response = self.client.report(self.url.path, q, 1) if response.status == 404: raise error.NotFoundError(response.raw) elif response.status == 400: raise error.ReportError(response.raw) r = response.tree.find(".//" + dav.Response.tag) if r is not None: href = URL.objectify(r.find(".//" + dav.Href.tag).text) data = r.find(".//" + cdav.CalendarData.tag).text etag = r.find(".//" + dav.ETag.tag).text e = Event(self.client, url=href, data=data, etag=etag, parent=self) else: raise error.NotFoundError(response.raw) return e
def __init__(self, url, proxy=None, username=None, password=None, auth=None, ssl_verify_cert=True): """ Sets up a HTTPConnection object towards the server in the url. Parameters: * url: A fully qualified url: `scheme://user:pass@hostname:port` * proxy: A string defining a proxy server: `hostname:port` * username and password should be passed as arguments or in the URL * auth and ssl_verify_cert is passed to requests.request. ** ssl_verify_cert can be the path of a CA-bundle or False. """ log.debug("url: " + str(url)) self.url = URL.objectify(url) # Prepare proxy info if proxy is not None: self.proxy = proxy if re.match('^.*://', proxy) is None: # requests library expects the proxy url to have a scheme self.proxy = self.url.scheme + '://' + proxy # add a port is one is not specified # TODO: this will break if using basic auth and embedding # username:password in the proxy URL p = self.proxy.split(":") if len(p) == 2: self.proxy += ':8080' log.debug("init - proxy: %s" % (self.proxy)) # Build global headers self.headers = {"User-Agent": "Mac+OS+X/10.11.2 (15C50) accountsd/113", "Content-Type": "text/xml", "Accept": "text/xml"} if self.url.username is not None: username = unquote(self.url.username) password = unquote(self.url.password) self.username = username self.password = password self.auth = auth ## TODO: it's possible to force through a specific auth method here, but no test code for this. self.ssl_verify_cert = ssl_verify_cert self.url = self.url.unauth() log.debug("self.url: " + str(url))
def date_search(self, start, end=None): """ Search events by date in the calendar. Recurring events are expanded if they have an occurence during the specified time frame. Parameters: * start = datetime.today(). * end = same as above. Returns: * [Event(), ...] """ matches = [] # build the request expand = cdav.Expand(start, end) data = cdav.CalendarData() + expand prop = dav.Prop() + data range = cdav.TimeRange(start, end) vevent = cdav.CompFilter("VEVENT") + range vcalendar = cdav.CompFilter("VCALENDAR") + vevent filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] q = etree.tostring(root.xmlelement(), encoding="utf-8", xml_declaration=True) response = self.client.report(self.url, q, 1) for r in response.tree.findall(".//" + dav.Response.tag): status = r.find(".//" + dav.Status.tag) if status.text.endswith("200 OK"): href = URL.objectify(r.find(dav.Href.tag).text) href = self.url.join(href) data = r.find(".//" + cdav.CalendarData.tag).text e = Event(self.client, url=href, data=data, parent=self) matches.append(e) else: raise error.ReportError(response.raw) return matches
def event_by_uid(self, uid): """ Get one event from the calendar. Parameters: * uid: the event uid Returns: * Event() or None """ e = None data = cdav.CalendarData() prop = dav.Prop() + data match = cdav.TextMatch(uid) propf = cdav.PropFilter("UID") + match vevent = cdav.CompFilter("VEVENT") + propf vcalendar = cdav.CompFilter("VCALENDAR") + vevent filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] q = etree.tostring(root.xmlelement(), encoding="utf-8", xml_declaration=True) response = self.client.report(self.url, q, 1) if response.status == 404: raise error.NotFoundError(response.raw) elif response.status == 400: raise error.ReportError(response.raw) r = response.tree.find(".//" + dav.Response.tag) if r is not None: href = URL.objectify(r.find(".//" + dav.Href.tag).text) data = r.find(".//" + cdav.CalendarData.tag).text e = Event(self.client, url=href, data=data, parent=self) else: raise error.NotFoundError(response.raw) return e
def request(self, url, method="GET", body="", headers={}): """ Actually sends the request """ url = URL.objectify(url) if self.proxy is not None: url = "%s://%s:%s%s" % (self.url.scheme, self.url.hostname, self.url.port, url.path) combined_headers = self.headers combined_headers.update(headers) if body is None or body == "" and "Content-Type" in combined_headers: del combined_headers["Content-Type"] if isinstance(body, unicode): body = body.encode('utf-8') try: logging.debug("########## SENDING REQUEST ###########") logging.debug("\nMETHOD=%s, \nURL=%s, \nHEADERS=%s\nBODY:\n%s" % (method, url, combined_headers, body)) self.handle.request(method, url, body, combined_headers) response = DAVResponse(self.handle.getresponse()) except httplib.BadStatusLine: # Try to reconnect self.handle.close() self.handle.connect() ## TODO: we're missing test code on this. (will need to ## mock up a server to test this) self.handle.request(method, url, body, combined_headers) response = DAVResponse(self.handle.getresponse(n)) # this is an error condition the application wants to know if response.status == httplib.FORBIDDEN or \ response.status == httplib.UNAUTHORIZED: ex = error.AuthorizationError() ex.url = url ex.reason = response.reason raise ex return response
def date_search(self, start, end=None): """ Search events by date in the calendar. Recurring events are expanded if they have an occurence during the specified time frame. Parameters: * start = datetime.today(). * end = same as above. Returns: * [Event(), ...] """ matches = [] # build the request expand = cdav.Expand(start, end) data = cdav.CalendarData() + expand prop = dav.Prop() + data range = cdav.TimeRange(start, end) vevent = cdav.CompFilter("VEVENT") + range vcalendar = cdav.CompFilter("VCALENDAR") + vevent filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] q = etree.tostring(root.xmlelement(), encoding="utf-8", xml_declaration=True) response = self.client.report(self.url.path, q, 1) for r in response.tree.findall(".//" + dav.Response.tag): status = r.find(".//" + dav.Status.tag) if status.text.endswith("200 OK"): href = URL.objectify(r.find(dav.Href.tag).text) href = self.url.join(href) data = r.find(".//" + cdav.CalendarData.tag).text e = Event(self.client, url=href, data=data, parent=self) matches.append(e) else: raise error.ReportError(response.raw) return matches
def _create(self, data, id=None, path=None): if id is None and path is not None and str(path).endswith('.ics'): id = re.search('(/|^)([^/]*).ics', str(path)).group(2) elif id is None: for obj_type in ('vevent', 'vtodo', 'vjournal', 'vfreebusy'): obj = None for subcomp in self.instance.subcomponents: if subcomp.name.lower() == obj_type: obj = subcomp break if obj is None and self.instance.name.lower == obj_type: obj = self.instance if obj is not None: id = obj["uid"] break else: for obj_type in ('vevent', 'vtodo', 'vjournal', 'vfreebusy'): obj = None for subcomp in self.instance.subcomponents: if subcomp.name.lower() == obj_type: obj = subcomp break if obj is None and self.instance.name.lower == obj_type: obj = self.instance if obj is not None: obj.add("uid", id) break if path is None: path = id + ".ics" path = self.parent.url.join(path) r = self.client.put(path, data, {"Content-Type": 'text/calendar; charset="utf-8"'}) if r.status == 302: path = [x[1] for x in r.headers if x[0] == 'location'][0] elif not (r.status in (204, 201)): raise error.PutError(errmsg(r)) self.url = URL.objectify(path) self.id = id
def object_by_uid(self, uid, comp_filter=None): """ Get one event from the calendar. Parameters: * uid: the event uid Returns: * Event() or None """ data = cdav.CalendarData() prop = dav.Prop() + data query = cdav.TextMatch(uid) query = cdav.PropFilter("UID") + query if comp_filter: query = comp_filter + query else: raise Exception("Need a comp_filter") vcalendar = cdav.CompFilter("VCALENDAR") + query filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] response = self._query(root, 1, 'report') if response.status == 404: raise error.NotFoundError(response.raw) elif response.status == 400: raise error.ReportError(response.raw) r = response.tree.find(".//" + dav.Response.tag) if r is not None: href = r.find(".//" + dav.Href.tag).text data = r.find(".//" + cdav.CalendarData.tag).text return self._calendar_comp_class_by_data(data)( self.client, url=URL.objectify(href), data=data, parent=self) else: raise error.NotFoundError(response.raw)
def children(self, type=None): """ List children, using a propfind (resourcetype) on the parent object, at depth = 1. """ c = [] depth = 1 properties = {} props = [dav.ResourceType(), dav.DisplayName()] response = self._query_properties(props, depth) properties = self._handle_prop_response(response=response, props=props, type=type, what='tag') for path in list(properties.keys()): resource_type = properties[path][dav.ResourceType.tag] resource_name = properties[path][dav.DisplayName.tag] if resource_type == type or type is None: url = URL(path) if url.hostname is None: # Quote when path is not a full URL path = quote(path) # TODO: investigate the RFCs thoroughly - why does a "get # members of this collection"-request also return the # collection URL itself? # And why is the strip_trailing_slash-method needed? # The collection URL should always end with a slash according # to RFC 2518, section 5.2. if (self.url.strip_trailing_slash() != self.url.join(path).strip_trailing_slash()): c.append( (self.url.join(path), resource_type, resource_name)) return c
def children(self, type=None): """ List children, using a propfind (resourcetype) on the parent object, at depth = 1. """ c = [] depth = 1 properties = {} props = [dav.ResourceType(), ] if type: props.append(dav.DisplayName()) response = self._query_properties(props, depth) for r in response.tree.findall(dav.Response.tag): # We use canonicalized urls to index children try: href = str(self.url.join(URL.objectify(r.find(dav.Href.tag).text)).canonical()) assert(href) properties[href] = {} for p in props: t = r.find(".//" + p.tag) if len(list(t)) > 0: if type is not None: val = t.find(".//" + type) else: val = t.find(".//*") if val is not None: val = val.tag else: val = None else: val = t.text properties[href][p.tag] = val except Exception , e: logging.info("error %s" % e)
def object_by_uid(self, uid, comp_filter=None): """ Get one event from the calendar. Parameters: * uid: the event uid Returns: * Event() or None """ data = cdav.CalendarData() prop = dav.Prop() + data query = cdav.TextMatch(uid) query = cdav.PropFilter("UID") + query if comp_filter: query = comp_filter + query vcalendar = cdav.CompFilter("VCALENDAR") + query filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] response = self._query(root, 1, 'report') if response.status == 404: raise error.NotFoundError(response.raw) elif response.status == 400: raise error.ReportError(response.raw) r = response.tree.find(".//" + dav.Response.tag) if r is not None: href = r.find(".//" + dav.Href.tag).text data = r.find(".//" + cdav.CalendarData.tag).text return self._calendar_comp_class_by_data(data)(self.client, url=URL.objectify(href), data=data, parent=self) else: raise error.NotFoundError(response.raw)
def request(self, url, method="GET", body="", headers={}): """ Actually sends the request """ # objectify the url url = URL.objectify(url) proxies = None if self.proxy is not None: proxies = {url.scheme: self.proxy} log.debug("using proxy - %s" % (proxies)) # ensure that url is a unicode string url = str(url) combined_headers = self.headers combined_headers.update(headers) if body is None or body == "" and "Content-Type" in combined_headers: del combined_headers["Content-Type"] log.debug("sending request - method={0}, url={1}, headers={2}\nbody:\n{3}".format(method, url, combined_headers, body)) auth = None if self.auth is None and self.username is not None: auth = requests.auth.HTTPDigestAuth(self.username, self.password) else: auth = self.auth r = requests.request(method, url, data=to_wire(body), headers=combined_headers, proxies=proxies, auth=auth, verify=self.ssl_verify_cert) response = DAVResponse(r) ## If server supports BasicAuth and not DigestAuth, let's try again: if response.status == 401 and self.auth is None and auth is not None: # if there were redirects, we need to continue with them if r.history: url = r.url auth = requests.auth.HTTPBasicAuth(self.username, self.password) r = requests.request(method, url, data=to_wire(body), headers=combined_headers, proxies=proxies, auth=auth, verify=self.ssl_verify_cert) response = DAVResponse(r) if r.history: # requests do not redirect with body r = requests.request(method, r.url, data=to_wire(body), headers=combined_headers, proxies=proxies, auth=auth, verify=self.ssl_verify_cert) response = DAVResponse(r) self._last_response = r # this is an error condition the application wants to know if response.status == requests.codes.forbidden or \ response.status == requests.codes.unauthorized: ex = error.AuthorizationError() ex.url = url ex.reason = response.reason raise ex ## let's save the auth object and remove the user/pass information if not self.auth and auth: self.auth = auth del self.username del self.password return response
def calendar_home_set(self, url): if isinstance(url, CalendarSet): self._calendar_home_set = url else: self._calendar_home_set = CalendarSet(self.client, self.client.url.join(URL.objectify(url)))
def testURL(self): """Excersising the URL class""" ## 1) URL.objectify should return a valid URL object almost no matter what's thrown in url0 = URL.objectify(None) url0b= URL.objectify("") url1 = URL.objectify("http://*****:*****@www.example.com:8080/caldav.php/?foo=bar") url2 = URL.objectify(url1) url3 = URL.objectify("/bar") url4 = URL.objectify(urlparse.urlparse(str(url1))) url5 = URL.objectify(urlparse.urlparse("/bar")) ## 2) __eq__ works well assert_equal(url1, url2) assert_equal(url1, url4) assert_equal(url3, url5) ## 3) str will always return the URL assert_equal(str(url1), "http://*****:*****@www.example.com:8080/caldav.php/?foo=bar") assert_equal(str(url3), "/bar") assert_equal(str(url4), "http://*****:*****@www.example.com:8080/caldav.php/?foo=bar") assert_equal(str(url5), "/bar") ## 4) join method url6 = url1.join(url2) url7 = url1.join(url3) url8 = url1.join(url4) url9 = url1.join(url5) urlA = url1.join("someuser/calendar") urlB = url5.join(url1) assert_equal(url6, url1) assert_equal(url7, "http://*****:*****@www.example.com:8080/bar") assert_equal(url8, url1) assert_equal(url9, url7) assert_equal(urlA, "http://*****:*****@www.example.com:8080/caldav.php/someuser/calendar") assert_equal(urlB, url1) assert_raises(ValueError, url1.join, "http://www.google.com") ## 4b) join method, with URL as input parameter url6 = url1.join(URL.objectify(url2)) url7 = url1.join(URL.objectify(url3)) url8 = url1.join(URL.objectify(url4)) url9 = url1.join(URL.objectify(url5)) urlA = url1.join(URL.objectify("someuser/calendar")) urlB = url5.join(URL.objectify(url1)) url6b= url6.join(url0) url6c= url6.join(url0b) url6d= url6.join(None) for url6alt in (url6b, url6c, url6d): assert_equal(url6, url6alt) assert_equal(url6, url1) assert_equal(url7, "http://*****:*****@www.example.com:8080/bar") assert_equal(url8, url1) assert_equal(url9, url7) assert_equal(urlA, "http://*****:*****@www.example.com:8080/caldav.php/someuser/calendar") assert_equal(urlB, url1) assert_raises(ValueError, url1.join, "http://www.google.com") ## 5) all urlparse methods will work. always. assert_equal(url1.scheme, 'http') assert_equal(url2.path, '/caldav.php/') assert_equal(url7.username, 'foo') assert_equal(url5.path, '/bar') urlC = URL.objectify("https://www.example.com:443/foo") assert_equal(urlC.port, 443) ## 6) is_auth returns True if the URL contains a username. assert_equal(urlC.is_auth(), False) assert_equal(url7.is_auth(), True) ## 7) unauth() strips username/password assert_equal(url7.unauth(), 'http://www.example.com:8080/bar')
def testURL(self): """Excersising the URL class""" ## 1) URL.objectify should return a valid URL object almost no matter what's thrown in url0 = URL.objectify(None) url0b = URL.objectify("") url1 = URL.objectify( "http://*****:*****@www.example.com:8080/caldav.php/?foo=bar") url2 = URL.objectify(url1) url3 = URL.objectify("/bar") url4 = URL.objectify(urlparse(str(url1))) url5 = URL.objectify(urlparse("/bar")) ## 2) __eq__ works well assert_equal(url1, url2) assert_equal(url1, url4) assert_equal(url3, url5) ## 3) str will always return the URL assert_equal( str(url1), "http://*****:*****@www.example.com:8080/caldav.php/?foo=bar") assert_equal(str(url3), "/bar") assert_equal( str(url4), "http://*****:*****@www.example.com:8080/caldav.php/?foo=bar") assert_equal(str(url5), "/bar") ## 4) join method url6 = url1.join(url2) url7 = url1.join(url3) url8 = url1.join(url4) url9 = url1.join(url5) urlA = url1.join("someuser/calendar") urlB = url5.join(url1) assert_equal(url6, url1) assert_equal(url7, "http://*****:*****@www.example.com:8080/bar") assert_equal(url8, url1) assert_equal(url9, url7) assert_equal( urlA, "http://*****:*****@www.example.com:8080/caldav.php/someuser/calendar") assert_equal(urlB, url1) assert_raises(ValueError, url1.join, "http://www.google.com") ## 4b) join method, with URL as input parameter url6 = url1.join(URL.objectify(url2)) url7 = url1.join(URL.objectify(url3)) url8 = url1.join(URL.objectify(url4)) url9 = url1.join(URL.objectify(url5)) urlA = url1.join(URL.objectify("someuser/calendar")) urlB = url5.join(URL.objectify(url1)) url6b = url6.join(url0) url6c = url6.join(url0b) url6d = url6.join(None) for url6alt in (url6b, url6c, url6d): assert_equal(url6, url6alt) assert_equal(url6, url1) assert_equal(url7, "http://*****:*****@www.example.com:8080/bar") assert_equal(url8, url1) assert_equal(url9, url7) assert_equal( urlA, "http://*****:*****@www.example.com:8080/caldav.php/someuser/calendar") assert_equal(urlB, url1) assert_raises(ValueError, url1.join, "http://www.google.com") ## 5) all urlparse methods will work. always. assert_equal(url1.scheme, 'http') assert_equal(url2.path, '/caldav.php/') assert_equal(url7.username, 'foo') assert_equal(url5.path, '/bar') urlC = URL.objectify("https://www.example.com:443/foo") assert_equal(urlC.port, 443) ## 6) is_auth returns True if the URL contains a username. assert_equal(urlC.is_auth(), False) assert_equal(url7.is_auth(), True) ## 7) unauth() strips username/password assert_equal(url7.unauth(), 'http://www.example.com:8080/bar')