| @@ -117,6 +117,108 @@ def _href(collection, href): | ||||
|         href.lstrip("/")) | ||||
|  | ||||
|  | ||||
| def _comp_match(item, filter_, scope="collection"): | ||||
|     """Check whether the ``item`` matches the comp ``filter_``. | ||||
|  | ||||
|     If ``scope`` is ``"collection"``, the filter is applied on the | ||||
|     item's collection. Otherwise, it's applied on the item. | ||||
|  | ||||
|     See rfc4791-9.7.1. | ||||
|  | ||||
|     """ | ||||
|     filter_length = len(filter_) | ||||
|     if scope == "collection": | ||||
|         tag = item.collection.get_meta("tag") | ||||
|     else: | ||||
|         for component in item.components(): | ||||
|             if component.name in ("VTODO", "VEVENT", "VJOURNAL"): | ||||
|                 tag = component.name | ||||
|     if filter_length == 0: | ||||
|         # Point #1 of rfc4791-9.7.1 | ||||
|         return filter_.get("name") == tag | ||||
|     else: | ||||
|         if filter_length == 1: | ||||
|             if filter_[0].tag == _tag("C", "is-not-defined"): | ||||
|                 # Point #2 of rfc4791-9.7.1 | ||||
|                 return filter_.get("name") != tag | ||||
|         if filter_[0].tag == _tag("C", "time-range"): | ||||
|             # Point #3 of rfc4791-9.7.1 | ||||
|             if not _time_range_match(item, filter_[0]): | ||||
|                 return False | ||||
|             filter_.remove(filter_[0]) | ||||
|         # Point #4 of rfc4791-9.7.1 | ||||
|         return all( | ||||
|             _prop_match(item, child) if child.tag == _tag("C", "prop-filter") | ||||
|             else _comp_match(item, child, scope="component") | ||||
|             for child in filter_) | ||||
|  | ||||
|  | ||||
| def _prop_match(item, filter_): | ||||
|     """Check whether the ``item`` matches the prop ``filter_``. | ||||
|  | ||||
|     See rfc4791-9.7.2 and rfc6352-10.5.1. | ||||
|  | ||||
|     """ | ||||
|     filter_length = len(filter_) | ||||
|     if item.collection.get_meta("tag") == "VCALENDAR": | ||||
|         for component in item.components(): | ||||
|             if component.name in ("VTODO", "VEVENT", "VJOURNAL"): | ||||
|                 vobject_item = component | ||||
|     else: | ||||
|         vobject_item = item.item | ||||
|     if filter_length == 0: | ||||
|         # Point #1 of rfc4791-9.7.2 | ||||
|         return filter_.get("name").lower() in vobject_item.contents | ||||
|     else: | ||||
|         if filter_length == 1: | ||||
|             if filter_[0].tag == _tag("C", "is-not-defined"): | ||||
|                 # Point #2 of rfc4791-9.7.2 | ||||
|                 return filter_.get("name").lower() not in vobject_item.contents | ||||
|         if filter_[0].tag == _tag("C", "time-range"): | ||||
|             # Point #3 of rfc4791-9.7.2 | ||||
|             if not _time_range_match(item, filter_[0]): | ||||
|                 return False | ||||
|             filter_.remove(filter_[0]) | ||||
|         elif filter_[0].tag == _tag("C", "text-match"): | ||||
|             # Point #4 of rfc4791-9.7.2 | ||||
|             if not _text_match(item, filter_[0]): | ||||
|                 return False | ||||
|             filter_.remove(filter_[0]) | ||||
|         return all( | ||||
|             _param_filter_match(item, param_filter) | ||||
|             for param_filter in filter_) | ||||
|  | ||||
|  | ||||
| def _time_range_match(item, _filter): | ||||
|     """Check whether the ``item`` matches the time-range ``filter_``. | ||||
|  | ||||
|     See rfc4791-9.9. | ||||
|  | ||||
|     """ | ||||
|     # TODO: implement this | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def _text_match(item, _filter): | ||||
|     """Check whether the ``item`` matches the text-match ``filter_``. | ||||
|  | ||||
|     See rfc4791-9.7.3. | ||||
|  | ||||
|     """ | ||||
|     # TODO: implement this | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def _param_filter_match(item, _filter): | ||||
|     """Check whether the ``item`` matches the param-filter ``filter_``. | ||||
|  | ||||
|     See rfc4791-9.7.3. | ||||
|  | ||||
|     """ | ||||
|     # TODO: implement this | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def name_from_path(path, collection): | ||||
|     """Return Radicale item name from ``path``.""" | ||||
|     collection_parts = collection.path.strip("/").split("/") | ||||
| @@ -491,16 +593,11 @@ def report(path, xml_request, collection): | ||||
|                     hreferences.add(href_path[len(base_prefix) - 1:]) | ||||
|         else: | ||||
|             hreferences = (path,) | ||||
|         # TODO: handle other filters | ||||
|         # TODO: handle the nested comp-filters correctly | ||||
|         # Read rfc4791-9.7.1 for info | ||||
|         tag_filters = set( | ||||
|             element.get("name").upper() for element | ||||
|             in root.findall(".//%s" % _tag("C", "comp-filter"))) | ||||
|         tag_filters.discard("VCALENDAR") | ||||
|         filters = ( | ||||
|             root.findall(".//%s" % _tag("C", "filter")) + | ||||
|             root.findall(".//%s" % _tag("CR", "filter"))) | ||||
|     else: | ||||
|         hreferences = () | ||||
|         tag_filters = set() | ||||
|         hreferences = filters = () | ||||
|  | ||||
|     # Writing answer | ||||
|     multistatus = ET.Element(_tag("D", "multistatus")) | ||||
| @@ -526,10 +623,12 @@ def report(path, xml_request, collection): | ||||
|             items = [collection.get(href) for href, etag in collection.list()] | ||||
|  | ||||
|         for item in items: | ||||
|             if (tag_filters and | ||||
|                     item.name not in tag_filters and | ||||
|                     not {tag.upper() for tag in item.contents} & tag_filters): | ||||
|                 continue | ||||
|             if filters: | ||||
|                 match = ( | ||||
|                     _comp_match if collection.get_meta("tag") == "VCALENDAR" | ||||
|                     else _prop_match) | ||||
|                 if not all(match(item, filter_[0]) for filter_ in filters): | ||||
|                     continue | ||||
|  | ||||
|             found_props = [] | ||||
|             not_found_props = [] | ||||
|   | ||||
| @@ -93,6 +93,129 @@ class BaseRequests: | ||||
|         status, headers, answer = self.request("GET", "/calendar.ics/") | ||||
|         assert "VEVENT" not in answer | ||||
|  | ||||
|     def _test_filter(self, filters): | ||||
|         filters_text = "".join( | ||||
|             "<C:filter>%s</C:filter>" % filter_ for filter_ in filters) | ||||
|         self.request( | ||||
|             "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR") | ||||
|         event = get_file_content("event.ics") | ||||
|         self.request("PUT", "/calendar.ics/event.ics", event) | ||||
|         status, headers, answer = self.request( | ||||
|             "REPORT", "/calendar.ics", | ||||
|             """<?xml version="1.0" encoding="utf-8" ?> | ||||
|                <C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav"> | ||||
|                  <D:prop xmlns:D="DAV:"> | ||||
|                    <D:getetag/> | ||||
|                  </D:prop> | ||||
|                  %s | ||||
|                </C:calendar-query>""" % filters_text) | ||||
|         return answer | ||||
|  | ||||
|     def test_calendar_tag_filter(self): | ||||
|         """Report request with tag-based filter on calendar.""" | ||||
|         assert "href>/calendar.ics/event.ics</" in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"></C:comp-filter>"""]) | ||||
|  | ||||
|     def test_item_tag_filter(self): | ||||
|         """Report request with tag-based filter on an item.""" | ||||
|         assert "href>/calendar.ics/event.ics</" in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"></C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|         assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VTODO"></C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|  | ||||
|     def test_item_not_tag_filter(self): | ||||
|         """Report request with tag-based is-not filter on an item.""" | ||||
|         assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:is-not-defined /> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|         assert "href>/calendar.ics/event.ics</" in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VTODO"> | ||||
|                 <C:is-not-defined /> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|  | ||||
|     def test_item_prop_filter(self): | ||||
|         """Report request with prop-based filter on an item.""" | ||||
|         assert "href>/calendar.ics/event.ics</" in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="SUMMARY"></C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|         assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="UNKNOWN"></C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|  | ||||
|     def test_item_not_prop_filter(self): | ||||
|         """Report request with prop-based is-not filter on an item.""" | ||||
|         assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="SUMMARY"> | ||||
|                   <C:is-not-defined /> | ||||
|                 </C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|         assert "href>/calendar.ics/event.ics</" in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="UNKNOWN"> | ||||
|                   <C:is-not-defined /> | ||||
|                 </C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|  | ||||
|     def test_mutiple_filters(self): | ||||
|         """Report request with multiple filters on an item.""" | ||||
|         assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="SUMMARY"> | ||||
|                   <C:is-not-defined /> | ||||
|                 </C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>""", """ | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="UNKNOWN"> | ||||
|                   <C:is-not-defined /> | ||||
|                 </C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|         assert "href>/calendar.ics/event.ics</" in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="SUMMARY"></C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>""", """ | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="UNKNOWN"> | ||||
|                   <C:is-not-defined /> | ||||
|                 </C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|         assert "href>/calendar.ics/event.ics</" in self._test_filter([""" | ||||
|             <C:comp-filter name="VCALENDAR"> | ||||
|               <C:comp-filter name="VEVENT"> | ||||
|                 <C:prop-filter name="SUMMARY"></C:prop-filter> | ||||
|                 <C:prop-filter name="UNKNOWN"> | ||||
|                   <C:is-not-defined /> | ||||
|                 </C:prop-filter> | ||||
|               </C:comp-filter> | ||||
|             </C:comp-filter>"""]) | ||||
|  | ||||
|  | ||||
| class TestMultiFileSystem(BaseRequests, BaseTest): | ||||
|     """Base class for filesystem tests.""" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Guillaume Ayoub
					Guillaume Ayoub