vCard writer: a lone carriage return in a name or note, or a line break in the email, adds properties to the card #27
Labels
No labels
bug
documentation
enhancement
performance
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-dav#27
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-dav at
97abc9779, comparing the plugin's vCard writer with the core's iCalendar writer and rendering cards with control characters in them.The plugin's
escapehandles\r\nand\nbut leaves a lone\rin the content line, andEMAILandURLare written with no treatment at all. A vCard is a list of lines, and the plugin's ownunfold, like other readers, takes a lone\ras a line break, so whatever follows it is read as a property of its own. This is content-line injection, the weakness postulo/postulo#218 (closed) fixed in the core's writer (applications/ical.py:63-71:\rfolded first, then every control character removed). The plugin keeps its own copy of the rule, and the copy never received the fix.src/postulo_dav/text.py:48-55(escape),src/postulo_dav/text.py:165(EMAIL;TYPE=work:{card.email}) and:169(URL:{card.url}) incard_to_text,src/postulo_dav/text.py:22-32(unfold, which splits on\r).first, a carriage return, thenTITLE:Injectedwas rendered and read back withtext_to_card: the note wasfirstand the titleInjected. The same with a carriage return in the name gave a card with anORGnobody typed, and with a plain\nin the email aTITLE. The pushed card carries the extra property into the address book. When that card is next pulled,apply_cardwrites the result back: the notes lose everything after the break and the role, company or email take the injected value.name,role,notesandemailthrough as sent (api/schemas.py:131-139;emailis a plainstr), so a token holder or a tool acting through one can. A captured company name keeps interior control characters (JobPostingDatastrips only the ends,plugins/base.py:42) and is written asORG. Text pasted from a source that uses bare\rdoes it by accident. The target is the account holder's own address book and, from there, every device that syncs it.escapedo what the core's does:text.replace("\r\n", "\n").replace("\r", "\n")before escaping, and drop every remaining character below U+0020 and U+007F. Strip the same control characters fromEMAILandURL(they are not text values, so no backslash escaping).ical.escapeis deliberately not on the plugin surface (#4), so the copy stays here; the test below is what keeps it from drifting again.Done when
tests/test_dav.pyhas a test that renders a card with\r,\r\n,\nand other control characters in the name, company, role and note, and a line break in the email and the URL, and asserts thattext_to_cardreturns no property that was not set and that the rendered text has no\routside the\r\nline endings.href#32Fixed by pull request #35, merged as
fb8bbd7ae.