mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-28 15:45:22 -04:00
fix: vCard parser drops folded continuation lines, corrupting emails (#1870)
This commit is contained in:
@@ -150,6 +150,14 @@ def _vunesc(value: str) -> str:
|
|||||||
|
|
||||||
def _parse_vcards(text: str) -> List[Dict]:
|
def _parse_vcards(text: str) -> List[Dict]:
|
||||||
"""Parse a stream of vCards into dicts with name, email, phone."""
|
"""Parse a stream of vCards into dicts with name, email, phone."""
|
||||||
|
# Unfold RFC 6350 3.2 line folding first: a CRLF/LF followed by a single
|
||||||
|
# space or tab is a continuation of the previous logical line. Real
|
||||||
|
# CardDAV servers (Radicale, iCloud, Apple/Google) fold long EMAIL / FN /
|
||||||
|
# PHOTO lines, and splitting on raw newlines without unfolding dropped the
|
||||||
|
# continuation (e.g. "...@example\n .com" lost the ".com"), truncating the
|
||||||
|
# email/name.
|
||||||
|
text = re.sub(r"\r\n[ \t]", "", text or "")
|
||||||
|
text = re.sub(r"\n[ \t]", "", text)
|
||||||
contacts = []
|
contacts = []
|
||||||
for block in re.split(r"BEGIN:VCARD", text):
|
for block in re.split(r"BEGIN:VCARD", text):
|
||||||
if not block.strip():
|
if not block.strip():
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"""vCard parsing must unfold RFC 6350 folded lines.
|
||||||
|
|
||||||
|
CardDAV servers fold logical lines longer than 75 octets onto continuation
|
||||||
|
lines that begin with a space/tab. _parse_vcards split on raw newlines
|
||||||
|
without unfolding, so a folded EMAIL/FN line lost its continuation (a long
|
||||||
|
address like ...@exampledomain<fold>.com was stored as ...@exampledomain),
|
||||||
|
silently corrupting the contact.
|
||||||
|
"""
|
||||||
|
from routes.contacts_routes import _parse_vcards
|
||||||
|
|
||||||
|
|
||||||
|
def test_folded_email_is_reassembled():
|
||||||
|
vcard = (
|
||||||
|
"BEGIN:VCARD\r\n"
|
||||||
|
"VERSION:3.0\r\n"
|
||||||
|
"FN:John Doe\r\n"
|
||||||
|
"EMAIL;TYPE=INTERNET:john.doe.with.a.very.long.local.part@exampledomain\r\n"
|
||||||
|
" .com\r\n"
|
||||||
|
"END:VCARD\r\n"
|
||||||
|
)
|
||||||
|
contacts = _parse_vcards(vcard)
|
||||||
|
assert len(contacts) == 1
|
||||||
|
assert contacts[0]["emails"] == [
|
||||||
|
"john.doe.with.a.very.long.local.part@exampledomain.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_folded_display_name_is_reassembled():
|
||||||
|
vcard = (
|
||||||
|
"BEGIN:VCARD\n"
|
||||||
|
"FN:A Very Long Display Name That The Server\n"
|
||||||
|
" Decided To Fold\n"
|
||||||
|
"EMAIL:x@y.com\n"
|
||||||
|
"END:VCARD\n"
|
||||||
|
)
|
||||||
|
c = _parse_vcards(vcard)[0]
|
||||||
|
assert c["name"] == "A Very Long Display Name That The Server Decided To Fold"
|
||||||
|
|
||||||
|
|
||||||
|
def test_unfolded_vcard_still_parses():
|
||||||
|
vcard = "BEGIN:VCARD\nFN:Jane\nEMAIL:jane@z.com\nTEL:+15550001\nEND:VCARD\n"
|
||||||
|
c = _parse_vcards(vcard)[0]
|
||||||
|
assert c["name"] == "Jane"
|
||||||
|
assert c["emails"] == ["jane@z.com"]
|
||||||
|
assert c["phones"] == ["+15550001"]
|
||||||
Reference in New Issue
Block a user