Guillaume Ayoub 9 лет назад
Родитель
Сommit
fef9f0abc6
3 измененных файлов с 88 добавлено и 18 удалено
  1. 39 17
      radicale/xmlutils.py
  2. 3 0
      tests/static/event.ics
  3. 46 1
      tests/test_base.py

+ 39 - 17
radicale/xmlutils.py

@@ -173,10 +173,11 @@ def _prop_match(item, filter_):
         # Point #1 of rfc4791-9.7.2
         return filter_.get("name").lower() in vobject_item.contents
     else:
+        name = filter_.get("name").lower()
         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
+                return name 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]):
@@ -184,22 +185,11 @@ def _prop_match(item, filter_):
             filter_.remove(filter_[0])
         elif filter_[0].tag == _tag("C", "text-match"):
             # Point #4 of rfc4791-9.7.2
-            # TODO: collations are not supported, but the default ones needed
-            # for DAV servers are actually pretty useless. Texts are lowered to
-            # be case-insensitive, almost as the "i;ascii-casemap" value.
-            match = next(filter_[0].itertext()).lower()
-            value = vobject_item.getChildValue(filter_.get("name").lower())
-            if value is None:
-                return False
-            value = value.lower()
-            if filter_[0].get("negate-condition") == "yes":
-                if match in value:
-                    return False
-            elif match not in value:
+            if not _text_match(vobject_item, filter_[0], name):
                 return False
             filter_.remove(filter_[0])
         return all(
-            _param_filter_match(item, param_filter)
+            _param_filter_match(vobject_item, param_filter, name)
             for param_filter in filter_)
 
 
@@ -213,14 +203,46 @@ def _time_range_match(item, filter_):
     return True
 
 
-def _param_filter_match(item, filter_):
+def _text_match(vobject_item, filter_, child_name, attrib_name=None):
+    """Check whether the ``item`` matches the text-match ``filter_``.
+
+    See rfc4791-9.7.5.
+
+    """
+    # TODO: collations are not supported, but the default ones needed
+    # for DAV servers are actually pretty useless. Texts are lowered to
+    # be case-insensitive, almost as the "i;ascii-casemap" value.
+    match = next(filter_.itertext()).lower()
+    children = getattr(vobject_item, "%s_list" % child_name, [])
+    if attrib_name:
+        condition = any(
+            match in attrib.lower() for child in children
+            for attrib in child.params.get(attrib_name, []))
+    else:
+        condition = any(match in child.value.lower() for child in children)
+    if filter_.get("negate-condition") == "yes":
+        return not condition
+    else:
+        return condition
+
+
+def _param_filter_match(vobject_item, filter_, parent_name):
     """Check whether the ``item`` matches the param-filter ``filter_``.
 
     See rfc4791-9.7.3.
 
     """
-    # TODO: implement this
-    return True
+    name = filter_.get("name")
+    children = getattr(vobject_item, "%s_list" % parent_name, [])
+    condition = any(name in child.params for child in children)
+    if len(filter_):
+        if filter_[0].tag == _tag("C", "text-match"):
+            return condition and _text_match(
+                vobject_item, filter_[0], parent_name, name)
+        elif filter_[0].tag == _tag("C", "is-not-defined"):
+            return not condition
+    else:
+        return condition
 
 
 def name_from_path(path, collection):

+ 3 - 0
tests/static/event.ics

@@ -25,6 +25,9 @@ LAST-MODIFIED:20130902T150158Z
 DTSTAMP:20130902T150158Z
 UID:event
 SUMMARY:Event
+ORGANIZER:mailto:unclesam@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Jane Doe:MAILTO:janedoe@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="MAILTO:bob@host.com";PARTSTAT=ACCEPTED;CN=John Doe:MAILTO:johndoe@example.com
 DTSTART;TZID=Europe/Paris:20130902T180000
 DTEND;TZID=Europe/Paris:20130902T190000
 END:VEVENT

+ 46 - 1
tests/test_base.py

@@ -217,7 +217,7 @@ class BaseRequests:
             </C:comp-filter>"""])
 
     def test_text_match_filter(self):
-        """Report request with tag-based filter on calendar."""
+        """Report request with text-match filter on calendar."""
         assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
             <C:comp-filter name="VCALENDAR">
               <C:comp-filter name="VEVENT">
@@ -251,6 +251,51 @@ class BaseRequests:
               </C:comp-filter>
             </C:comp-filter>"""])
 
+    def test_param_filter(self):
+        """Report request with param-filter on calendar."""
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="ATTENDEE">
+                  <C:param-filter name="PARTSTAT">
+                    <C:text-match collation="i;ascii-casemap"
+                    >ACCEPTED</C:text-match>
+                  </C:param-filter>
+                </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="ATTENDEE">
+                  <C:param-filter name="PARTSTAT">
+                    <C:text-match collation="i;ascii-casemap"
+                    >UNKNOWN</C:text-match>
+                  </C:param-filter>
+                </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="ATTENDEE">
+                  <C:param-filter name="PARTSTAT">
+                    <C:is-not-defined />
+                  </C:param-filter>
+                </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="ATTENDEE">
+                  <C:param-filter name="UNKNOWN">
+                    <C:is-not-defined />
+                  </C:param-filter>
+                </C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+
 
 class TestMultiFileSystem(BaseRequests, BaseTest):
     """Base class for filesystem tests."""