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 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 _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 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 testURL(self): """Exercising the URL class""" long_url = "http://*****:*****@www.example.com:8080/caldav.php/?foo=bar" # 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(long_url) 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), long_url) assert_equal(str(url3), "/bar") assert_equal(str(url4), long_url) assert_equal(str(url5), "/bar") ## 3b) repr should also be exercised. Returns URL(/bar) now. assert ("/bar" in repr(url5)) assert ("URL" in repr(url5)) assert (len(repr(url5)) < 12) # 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') # 8) strip_trailing_slash assert_equal( URL('http://www.example.com:8080/bar/').strip_trailing_slash(), URL('http://www.example.com:8080/bar')) assert_equal( URL('http://www.example.com:8080/bar/').strip_trailing_slash(), URL('http://www.example.com:8080/bar').strip_trailing_slash())