Skip to content

Commit

Permalink
some refactoring, cleanup and potentially even bugfixes in _create - …
Browse files Browse the repository at this point in the history
…plus potential bugfix in the copy method, the URL should change if the uuid changes. Fixes python-caldav#160 and more.
  • Loading branch information
tobixen committed Oct 23, 2022
1 parent 94a39c3 commit 81e7fcd
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 42 deletions.
92 changes: 52 additions & 40 deletions caldav/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1788,11 +1788,14 @@ def copy(self, keep_uid=False, new_parent=None):
Events, todos etc can be copied within the same calendar, to another
calendar or even to another caldav server
"""
return self.__class__(
obj = self.__class__(
parent=new_parent or self.parent,
data=self.data,
id=self.id if keep_uid else str(uuid.uuid1()),
)
if not keep_uid:
obj.url = obj.generate_url()
return obj

def load(self):
"""
Expand All @@ -1810,54 +1813,63 @@ def load(self):

## TODO: this method should be simplified and renamed, and probably
## some of the logic should be moved elsewhere
def _create(self, data, id=None, path=None):
def _find_id_path(self, id=None, path=None):
"""
With CalDAV, every object has an URL. With icalendar, every object
should have a UID. This UID may or may not be copied into self.id.
This method will:
0) if ID is given, assume that as the UID, and set it in the object
1) if UID is given in the object, assume that as the ID
2) if ID is not given, but the path is given, generate the ID from the
path
3) If neither ID nor path is given, use the uuid method to generate an
ID (TODO: recommendation is to concat some timestamp, serial or
random number and a domain)
4) if no path is given, generate the URL from the ID
"""
i = self.icalendar_instance.subcomponents[0]
if not id and getattr(self, 'id', None):
id = self.id
if not id:
id = i.pop('UID', None)
if not path and getattr(self, 'path', None):
path = self.path
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 id is None:
id = str(uuid.uuid1())
i.pop('UID', None)
i.add('UID', id)

self.id = id

if path is None:
## See https://github.com/python-caldav/caldav/issues/143 for the rationale behind double-quoting slashes
## TODO: should try to wrap my head around issues that arises when id contains weird characters. maybe it's
## better to generate a new uuid here, particularly if id is in some unexpected format.
path = quote(id.replace("/", "%2F")) + ".ics"
path = self.parent.url.join(path)
path = self.generate_url()
else:
path = self.parent.url.join(path)

self.url = URL.objectify(path)

## TODO: still some refactoring to be done here
def _create(self, data, id=None, path=None):
self._find_id_path(id=id, path=path)
## SECURITY TODO: we should probably have a check here to verify that no such object exists already
r = self.client.put(
path, data, {"Content-Type": 'text/calendar; charset="utf-8"'}
self.url, 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 generate_url(self):
## See https://github.com/python-caldav/caldav/issues/143 for the rationale behind double-quoting slashes
## TODO: should try to wrap my head around issues that arises when id contains weird characters. maybe it's
## better to generate a new uuid here, particularly if id is in some unexpected format.
return self.parent.url.join(quote(self.id.replace("/", "%2F")) + ".ics")


def change_attendee_status(self, attendee=None, **kwargs):
if not attendee:
Expand Down Expand Up @@ -1983,9 +1995,9 @@ def save(
## non-conforming icalendar data. We'll just throw in a
## try-send-data-except-wash-through-vobject-logic here.
try:
self._create(self.data, self.id, path)
self._create(data=self.data, id=self.id, path=path)
except error.PutError:
self._create(self.vobject_instance.serialize(), self.id, path)
self._create(data=self.vobject_instance.serialize(), id=self.id, path=path)
return self

def __str__(self):
Expand Down
4 changes: 3 additions & 1 deletion changelog-0.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ The "thisandfuture"-mode will establish the completed task as a separate recurre

## Github issues and pull requests

https://github.com/python-caldav/caldav/issues/16
https://github.com/python-caldav/caldav/issues/127
https://github.com/python-caldav/caldav/issues/145
https://github.com/python-caldav/caldav/pull/204
https://github.com/python-caldav/caldav/pull/208
https://github.com/python-caldav/caldav/pull/212
https://github.com/python-caldav/caldav/pull/216
https://github.com/python-caldav/caldav/issues/219
https://github.com/python-caldav/caldav/issues/16

## Commits

Expand Down
3 changes: 3 additions & 0 deletions tests/compatibility_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@

'rrule_takes_no_count':
"""Fastmail consistently yields a "502 bad gateway" when presented with a rrule containing COUNT""",

'no-current-user-principal':
"""when querying for the current user principal property, server doesn't report anything useful""",
}

xandikos = [
Expand Down
6 changes: 5 additions & 1 deletion tests/test_caldav.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,11 @@ def setup(self):
self.testcal_id2 = "pythoncaldav-test2"

self.caldav = client(**self.server_params)
self.principal = self.caldav.principal()

if False and self.check_compatibility_flag("no-current-user-principal"):
self.principal = Principal(client=self.caldav, url=self.server_params['principal_url'])
else:
self.principal = self.caldav.principal()

logging.debug(
"## going to tear down old test calendars, "
Expand Down

0 comments on commit 81e7fcd

Please sign in to comment.