From 97e350341cab3bcbc28a10016331fcfefc11ef0d Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 30 Apr 2026 22:32:58 +0200 Subject: [PATCH 1/5] sharing: fix get request on single items with bday conversion --- radicale/app/get.py | 14 ++++++++++---- radicale/sharing/__init__.py | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/radicale/app/get.py b/radicale/app/get.py index b1e46540..ac5309f6 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,12 @@ 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: + answer = item.convert_vcf_to_ics().serialize() 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 From 39ece55490ae5c4afe2f81fcada8d2be442016be Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 30 Apr 2026 22:33:38 +0200 Subject: [PATCH 2/5] test for sharing: fix get request on single items with bday conversion --- radicale/tests/test_sharing.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/radicale/tests/test_sharing.py b/radicale/tests/test_sharing.py index c89b651c..e5dc2f83 100644 --- a/radicale/tests/test_sharing.py +++ b/radicale/tests/test_sharing.py @@ -4838,6 +4838,23 @@ 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 + # timerange filter elements as user logging.info("\n*** REPORT collection entries with timerange user -> ok") _, responses = self.report(path_shared_r, """\ From c05bd1e55c0c6d979bb88e18f524a339ac1456a2 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 30 Apr 2026 22:34:26 +0200 Subject: [PATCH 3/5] changelog for 97e350341cab3bcbc28a10016331fcfefc11ef0d --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 809663579e5acccfbee0247b1b5023463280fdc0 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 30 Apr 2026 22:50:33 +0200 Subject: [PATCH 4/5] sharing: fix get request on single item with not successful bday conversion --- radicale/app/get.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/radicale/app/get.py b/radicale/app/get.py index ac5309f6..a0139560 100644 --- a/radicale/app/get.py +++ b/radicale/app/get.py @@ -138,7 +138,11 @@ class ApplicationPartGet(ApplicationBase): # convert VCF to ICS answer = item.serialize(vcf_to_ics=True) else: - answer = item.convert_vcf_to_ics().serialize() + 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 From d496f9ee13d25609bc0e0cff49a82f4b9f197756 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 30 Apr 2026 22:51:04 +0200 Subject: [PATCH 5/5] test: sharing: fix get request on single item with not successful bday conversion --- radicale/tests/test_sharing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/radicale/tests/test_sharing.py b/radicale/tests/test_sharing.py index e5dc2f83..9ec5b566 100644 --- a/radicale/tests/test_sharing.py +++ b/radicale/tests/test_sharing.py @@ -4855,6 +4855,10 @@ permissions: RrWw""") # 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, """\