diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f1b244..11b581b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 3.7.3.dev * Extension: expose RADICALE:version for authenticated users via PROPFIND +* Fix: sharing: GET request on single item with bday conversion ## 3.7.2 * Fix: broken storage/mtime granularity detection on vfat diff --git a/radicale/app/get.py b/radicale/app/get.py index b1e46540..a0139560 100644 --- a/radicale/app/get.py +++ b/radicale/app/get.py @@ -121,7 +121,10 @@ class ApplicationPartGet(ApplicationBase): elif limited_access: return httputils.NOT_ALLOWED else: - content_type = xmlutils.OBJECT_MIMETYPES[item.name] + if share and share['Conversion'] == "bday": + content_type = xmlutils.MIMETYPES["VCALENDAR"] + else: + content_type = xmlutils.OBJECT_MIMETYPES[item.name] content_disposition = "" assert item.last_modified headers = { @@ -130,9 +133,16 @@ class ApplicationPartGet(ApplicationBase): "ETag": item.etag} if content_disposition: headers["Content-Disposition"] = content_disposition - if isinstance(item, storage.BaseCollection) and share and share['Conversion'] == "bday": - # convert VCF to ICS - answer = item.serialize(vcf_to_ics=True) + if share and share['Conversion'] == "bday": + if isinstance(item, storage.BaseCollection): + # convert VCF to ICS + answer = item.serialize(vcf_to_ics=True) + else: + item_converted = item.convert_vcf_to_ics() + if item_converted is not None: + answer = item_converted.serialize() + else: + return httputils.NOT_FOUND else: answer = item.serialize() return client.OK, headers, answer, None diff --git a/radicale/sharing/__init__.py b/radicale/sharing/__init__.py index ba2c5f6b..109b292d 100644 --- a/radicale/sharing/__init__.py +++ b/radicale/sharing/__init__.py @@ -497,6 +497,9 @@ class BaseSharing: logger.info("sharing/%s: resolved path %r->%r, user %r->%r not enabled by user", "map", path, result['PathMapped'], user, result['Owner']) return {'error': 'map-not-enabled'} + if result['Conversion'] == "bday" and result['PathMapped'].endswith(".ics"): + result['PathMapped'] = result['PathMapped'].removesuffix(".ics") + ".vcf" + logger.info("sharing/%s: resolved path %r->%r, user %r->%r, Permissions=%r Conversion=%r", "map", path, result['PathMapped'], user, result['Owner'], result['Permissions'], result['Conversion']) return result diff --git a/radicale/tests/test_sharing.py b/radicale/tests/test_sharing.py index c89b651c..9ec5b566 100644 --- a/radicale/tests/test_sharing.py +++ b/radicale/tests/test_sharing.py @@ -4838,6 +4838,27 @@ permissions: RrWw""") assert path_shared_r + "contact3-with-bday.ics" in responses assert path_shared_r + "contact1.ics" not in responses + # verify content as user + logging.info("\n*** GET item as user -> ok") + _, headers, answer = self.request("GET", path_shared_r + "contact2-with-bday.ics", login="user:userpw") + logging.debug("resonse: %r", answer) + assert "BEGIN:VCARD" not in answer + assert "BEGIN:VCALENDAR" in answer + assert "RRULE:FREQ=YEARLY" in answer + assert "DTSTART;VALUE=DATE:19700101" in answer + assert "DTEND;VALUE=DATE:19700102" in answer + assert "TRANSP:TRANSPARENT" in answer + assert "DESCRIPTION:BDAY=1970-01-01" in answer + # content type must be adjusted + assert 'Content-Type' in headers + assert 'text/calendar' in headers['Content-Type'] + # title from Properties + assert 'Content-Disposition' not in headers + + # get a single item which is not exsting on conversion + logging.info("\n*** GET item as user -> ok") + _, headers, answer = self.request("GET", path_shared_r + "contact1.ics", login="user:userpw", check=404) + # timerange filter elements as user logging.info("\n*** REPORT collection entries with timerange user -> ok") _, responses = self.report(path_shared_r, """\