Fix: bday conversion clobbers NICKNAME when FN is empty
The share-by-map BDAY-to-ICS conversion builds a placeholder mapping for
the SUMMARY/DESCRIPTION/alarm templates. Each block sets the fallback
marker for its own placeholder (e.g. {nickname} -> '!nickname!', {n:f} ->
'!n:f!'), but the {fn} block wrote the empty-FN fallback to {nickname}
instead of {fn}.
As a result, for a VCARD whose FN is present but empty, a genuine
NICKNAME was overwritten with '!fn!' in the generated event, and the
{fn} placeholder never received its '!fn!' marker, so a '[{fn}|...]'
fallback in a template failed to resolve.
Assign the fallback to {fn}, matching the surrounding blocks.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
* Fix: time-range filter treated a VEVENT with a whole-day DURATION (e.g. P1D, P2D) as zero-length (timedelta.seconds instead of total_seconds), so such events were missing from calendar-query REPORT results
|
||||
* Fix: calendar-data expand (REPORT) left recurrence properties (e.g. RDATE) on the expanded single-occurrence VEVENTs; a single try/except around the sequential delattr() calls stopped at the first absent property (e.g. missing EXDATE), so later ones were never removed
|
||||
* Fix: text-match filter on a structured property (e.g. vCard N or ADR) crashed with HTTP 500 (AttributeError: 'Name'/'Address' object has no attribute 'lower') because vobject parses these into non-string objects; their text representation is now used
|
||||
* Fix: sharing bday-to-ICS conversion assigned the empty-FN fallback marker to the {nickname} placeholder instead of {fn}, so a VCARD with an empty FN got its NICKNAME overwritten with "!fn!" in the generated SUMMARY/DESCRIPTION and the {fn} fallback never resolved
|
||||
|
||||
## 3.7.6
|
||||
* Extension: item verification on commandline
|
||||
|
||||
@@ -717,7 +717,7 @@ class Item:
|
||||
if hasattr(self.vobject_item, "fn") and self.vobject_item.fn.value != "":
|
||||
placeholder_mapping['{fn}'] = self.vobject_item.fn.value
|
||||
else:
|
||||
placeholder_mapping['{nickname}'] = '!fn!'
|
||||
placeholder_mapping['{fn}'] = '!fn!'
|
||||
|
||||
# rfc6350#6.2 FamilyName;GivenName;AdditionalNames;HonorificPrefixes;HonorificSuffixes
|
||||
if hasattr(self.vobject_item, "n") and self.vobject_item.n.value.family != "":
|
||||
|
||||
@@ -28,11 +28,13 @@ import re
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib
|
||||
from typing import Dict, Sequence, Tuple, Union
|
||||
from typing import Dict, Sequence, Tuple, Union, cast
|
||||
|
||||
import pytest
|
||||
import vobject
|
||||
|
||||
from radicale import pathutils, sharing, xmlutils
|
||||
from radicale import pathutils, sharing, storage, xmlutils
|
||||
from radicale.item import Item
|
||||
from radicale.tests import BaseTest
|
||||
from radicale.tests.helpers import get_file_content
|
||||
|
||||
@@ -5328,6 +5330,39 @@ permissions: RrWw""")
|
||||
assert "Test-FN-C3 (1990/5)" in answer
|
||||
assert "Test-FN-C3 (1990/6)" in answer
|
||||
|
||||
def test_sharing_bday_conversion_empty_fn(self) -> None:
|
||||
"""BDAY-to-ICS conversion of a VCARD with an empty FN property.
|
||||
|
||||
vCard mandates FN, but some clients emit it empty. The fallback for
|
||||
an empty ``{fn}`` must be assigned to the ``{fn}`` placeholder, not to
|
||||
``{nickname}``: otherwise a present NICKNAME gets clobbered with the
|
||||
``!fn!`` marker and the ``{fn}`` fallback never triggers.
|
||||
"""
|
||||
vcard = vobject.readOne(
|
||||
"BEGIN:VCARD\r\n"
|
||||
"VERSION:3.0\r\n"
|
||||
"UID:contact-empty-fn\r\n"
|
||||
"N:FamilyTest;GivenTest;;;\r\n"
|
||||
"FN:\r\n"
|
||||
"NICKNAME:Test-NICKNAME\r\n"
|
||||
"BDAY:1990-05-06\r\n"
|
||||
"END:VCARD\r\n")
|
||||
|
||||
class _StubCollection:
|
||||
path = "test"
|
||||
|
||||
item = Item(collection=cast(storage.BaseCollection, _StubCollection()),
|
||||
vobject_item=vcard, href="contact-empty-fn.vcf")
|
||||
converted = item.convert_vcf_to_ics(ShareActions={"config": {
|
||||
"conversion_bday_summary_template": "{nickname} (BDAY)",
|
||||
"conversion_bday_description_template": "[{fn}|no-fn]"}})
|
||||
assert converted is not None
|
||||
serialized = converted.serialize()
|
||||
# NICKNAME must survive, not be overwritten by the '!fn!' marker
|
||||
assert "SUMMARY:Test-NICKNAME (BDAY)" in serialized
|
||||
# empty {fn} must expose the '!fn!' marker so the fallback resolves
|
||||
assert "DESCRIPTION:no-fn" in serialized
|
||||
|
||||
def test_sharing_api_map_vcf_bday_age_template(self) -> None:
|
||||
"""share-by-map with conversion=bday template tests with age."""
|
||||
self.configure({"auth": {"type": "htpasswd",
|
||||
|
||||
Reference in New Issue
Block a user