fix(contacts): parse Apple/iCloud item-grouped vCard EMAIL/TEL properties (#1438)

_parse_vcards matched property names with a bare line.startswith("EMAIL") /
"TEL" / "FN:" / "UID:". RFC 6350 property groups — emitted by default by Apple
Contacts / iCloud and many CardDAV servers — prefix the name with a group token,
e.g. item1.EMAIL;type=pref:jane@example.com. Those lines never matched, so emails
and phone numbers from any Apple-synced or Apple-exported address book were
silently dropped (breaking contact search by email, composer autocomplete, and
vCard/CSV export round-trips).

Strip an optional leading group token before matching and value extraction;
no-op for non-grouped lines.

Adds tests/test_contacts_vcard_parse.py (grouped + plain) — the grouped case
fails before this change and passes after.

Co-authored-by: NubsCarson <nubs@nubs.site>
This commit is contained in:
Shaw
2026-06-03 01:24:04 -04:00
committed by GitHub
parent 3eed73e11e
commit 43ed3f7148
2 changed files with 55 additions and 10 deletions

View File

@@ -0,0 +1,38 @@
"""Regression: _parse_vcards must read Apple/iCloud item-grouped properties.
RFC 6350 property groups (the default emitted by Apple Contacts.app / iCloud and
many CardDAV servers) prefix the property name with a group token, e.g.
`item1.EMAIL;type=pref:jane@example.com`. The parser matched property names with
a bare `line.startswith("EMAIL")` / `"TEL"` / `"FN:"`, so grouped lines never
matched and the email / phone were silently dropped — breaking contact search by
email, the email-composer autocomplete, and vCard/CSV export round-trips for any
address book synced from Apple.
"""
from routes.contacts_routes import _parse_vcards
def test_apple_item_grouped_properties_parsed():
vcf = (
"BEGIN:VCARD\nVERSION:3.0\nFN:Jane Doe\n"
"item1.EMAIL;type=INTERNET;type=pref:jane@example.com\n"
"item2.TEL;type=CELL;type=pref:+15550100\n"
"UID:abc-123\nEND:VCARD\n"
)
c = _parse_vcards(vcf)[0]
assert c["emails"] == ["jane@example.com"]
assert c["phones"] == ["+15550100"]
assert c["uid"] == "abc-123"
def test_plain_ungrouped_properties_still_parsed():
vcf = (
"BEGIN:VCARD\nVERSION:3.0\nFN:John Smith\n"
"EMAIL;TYPE=INTERNET:john@example.com\n"
"TEL;TYPE=CELL:+15550199\n"
"UID:xyz\nEND:VCARD\n"
)
c = _parse_vcards(vcf)[0]
assert c["name"] == "John Smith"
assert c["emails"] == ["john@example.com"]
assert c["phones"] == ["+15550199"]
assert c["uid"] == "xyz"