python-caldav / caldav

Apache License 2.0
327 stars 98 forks source link

Todo objects with duplicate STATUS can be generated #355

Closed FrnchFrgg closed 10 months ago

FrnchFrgg commented 11 months ago

Even though when generating the data all property names are uppercased, this is not done before iterating the properties which means that all possible cases will generate duplicates. In particular using any other case in the save_todo call will fail since that will be considered a duplicate of the STATUS property inserted as a default by caldav itself.

Note that the error is probably hidden when using more forgiving CALDAV servers than mine: reading the traceback below it seems that the error is indeed raised the caldav library but that it only validates the event on PUT failure.

I use radicale which AFAICT refuses the VTODO because it also validates it with vobject (since I get the exact same error in the radicale logs)

>>> import caldav
>>> client = caldav.DAVClient("xxx", username="foo", password="bar")
>>> cal = client.principal().calendars()[0]
>>> x = cal.save_todo(summary="works")
>>> y = cal.save_todo(summary="workstoo", STATUS="COMPLETED")
>>> z = cal.save_todo(summary="chokes", status="COMPLETED")
ValidateError                             Traceback (most recent call last)
File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:768, in Calendar.save_todo(self, ical, no_overwrite, no_create, **ical_data)
    757 """
    758 Add a new task to the calendar, with the given ical.
    759 
    760 Parameters:
    761  * ical - ical object (text)
    762 """
    763 t = Todo(
    764     self.client,
    765     data=self._use_or_create_ics(ical, objtype="VTODO", **ical_data),
    766     parent=self,
    767 )
--> 768 t.save(no_overwrite=no_overwrite, no_create=no_create, obj_type="todo")
    769 self._handle_relations(t.id, ical_data)
    770 return t

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2283, in CalendarObjectResource.save(self, no_overwrite, no_create, obj_type, increase_seqno, if_schedule_tag_match)
   2280     if seqno is not None:
   2281         self.icalendar_component.add("SEQUENCE", seqno + 1)
-> 2283 self._create(id=self.id, path=path)
   2284 return self

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2155, in CalendarObjectResource._create(self, id, path, retry_on_failure)
   2150 def _create(self, id=None, path=None, retry_on_failure=True):
   2151     ## We're efficiently running the icalendar code through the icalendar
   2152     ## library.  This may cause data modifications and may "unfix"
   2153     ## https://github.com/python-caldav/caldav/issues/43
   2154     self._find_id_path(id=id, path=path)
-> 2155     self._put()

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2146, in CalendarObjectResource._put(self, retry_on_failure)
   2142 if retry_on_failure:
   2143     ## This looks like a noop, but the object may be "cleaned".
   2144     ## See https://github.com/python-caldav/caldav/issues/43
   2145     self.vobject_instance
-> 2146     return self._put(False)
   2147 else:
   2148     raise error.PutError(errmsg(r))

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2137, in CalendarObjectResource._put(self, retry_on_failure)
   2134 def _put(self, retry_on_failure=True):
   2135     ## SECURITY TODO: we should probably have a check here to verify that no such object exists already
   2136     r = self.client.put(
-> 2137         self.url, self.data, {"Content-Type": 'text/calendar; charset="utf-8"'}
   2138     )
   2139     if r.status == 302:
   2140         path = [x[1] for x in r.headers if x[0] == "location"][0]

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2321, in CalendarObjectResource._get_data(self)
   2319     return to_normal_str(self._data)
   2320 elif self._vobject_instance:
-> 2321     return to_normal_str(self._vobject_instance.serialize())
   2322 elif self._icalendar_instance:
   2323     return to_normal_str(self._icalendar_instance.to_ical())

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/base.py:254, in VBase.serialize(self, buf, lineLength, validate, behavior)
    252     if DEBUG:
    253         logger.debug("serializing {0!s} with behavior {1!s}".format(self.name, behavior))
--> 254     return behavior.serialize(self, buf, lineLength, validate)
    255 else:
    256     if DEBUG:

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/icalendar.py:1001, in VCalendar2_0.serialize(cls, obj, buf, lineLength, validate)
    999 cls.generateImplicitParameters(obj)
   1000 if validate:
-> 1001     cls.validate(obj, raiseException=True)
   1002 if obj.isNative:
   1003     transformed = obj.transformFromNative()

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/behavior.py:85, in Behavior.validate(cls, obj, raiseException, complainUnrecognized)
     83 count = {}
     84 for child in obj.getChildren():
---> 85     if not child.validate(raiseException, complainUnrecognized):
     86         return False
     87     name = child.name.upper()

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/base.py:124, in VBase.validate(self, *args, **kwds)
    120 """
    121 Call the behavior's validate method, or return True.
    122 """
    123 if self.behavior:
--> 124     return self.behavior.validate(self, *args, **kwds)
    125 return True

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/icalendar.py:1228, in VTodo.validate(cls, obj, raiseException, *args)
   1226     return False
   1227 else:
-> 1228     return super(VTodo, cls).validate(obj, raiseException, *args)

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/behavior.py:98, in Behavior.validate(cls, obj, raiseException, complainUnrecognized)
     96         if raiseException:
     97             m = "{0} components cannot contain more than {1} {2}"
---> 98             raise base.ValidateError(m.format(cls.name, val[1], key))
     99         return False
    100 return True

ValidateError: 'VTODO components cannot contain more than 1 STATUS'
tobixen commented 11 months ago

I think this bug was fixed in master branch yesterday. Please check.

tobixen commented 11 months ago

I found the same problem when writing up tests in #354, so the fix was piggybacked there.

tobixen commented 11 months ago

Right, HomeAssistant ... dependent on how you are running it, it may be really difficult to check how it works with the master branch. I will make a new minor release Real Soon Now. Possibly tomorrow.

tobixen commented 10 months ago

I think this issue has been fixed and will close the issue. Please correct me if I'm wrong.

FrnchFrgg commented 10 months ago

Yes I can confirm this issue is fixed in latest caldav.