Safe vCard
Bounded RFC 6350 vCard 4.0 parser. Walks the content-line grammar (BEGIN:VCARD ... END:VCARD) into a JSON AST that the CardDAV stack stores per-tenant. Compatible with the RFC 2425 / 2426 shape that legacy CardDAV clients still emit when they negotiate VERSION:3.0; the parser admits both versions and exposes the declared VERSION field on the resulting card.
Substrate for the contacts storage protocol (b.mail.dav).
Defense posture mirrors b.safeIcal — the vCard grammar shares the line-folding + property-parameter shape with iCalendar but does not carry an RRULE-class amplifier; the equivalent amplifier here is the PHOTO / LOGO / SOUND / KEY inline-embedded-binary properties which a hostile vCard can stuff with megabytes of base64 to exhaust storage.
Caps:
- Total bytes (256 KiB strict / 1 MiB balanced / 4 MiB permissive) — refused before parsing begins. - PHOTO / LOGO / SOUND / KEY inline-embed bytes (1 MiB strict / 4 MiB balanced / 16 MiB permissive) — refused when the declared property value or data: URI body exceeds the cap. - Per-line bytes after unfolding (8 KiB strict / 32 KiB balanced / 128 KiB permissive). - Total cards in a stream (16 strict / 256 balanced / 4096 permissive). RFC 6350 §3.2 permits chained BEGIN:VCARD / END:VCARD pairs.
Header-injection / control-char defense: refuses NUL, C0 control bytes (other than TAB), and DEL (0x7F) inside property values.
Property allowlist: every property name must either appear in the RFC 6350 §6 property registry or carry the X- experimental prefix. Unknown bare names are refused.
Explicit non-goals (deferred — operator escape hatch noted):
- **vCard 4.0 to 3.0 conversion (RFC 6868)** — the parser exposes both shapes via the declared VERSION; round-tripping between them happens at the CardDAV layer when an old client requests a 4.0-only card. - **xCard XML / jCard JSON (RFC 6351 / 7095)** — the JSON AST this module emits is convertible to jCard but the framework does not currently ship the canonicalization. - **Vendor extensions** — operator extends via opts.extraProperties until the relevant slice lands.
b.safeVcard.parse(text, opts?) #
{
profile: "strict" | "balanced" | "permissive", // default strict
compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2", // -> strict
extraProperties: string[], // operator-extended allowlist
}
Parse RFC 6350 vCard 4.0 text into a JSON AST. Returns { vcards: [{ version, properties: { FN: [{ params, value }], ... } }, ...] }.
Throws SafeVcardError with codes: safe-vcard/oversize-bytes / oversize-line-bytes / oversize-cards / oversize-properties-per-card / oversize-embed / missing-vcard / unterminated-vcard / unknown-property / control-char-in-value / bad-line / bad-input / bad-opt.
var ast = b.safeVcard.parse(
"BEGIN:VCARD\r\n" +
"VERSION:4.0\r\n" +
"FN:Alice Example\r\n" +
"EMAIL:alice@example.com\r\n" +
"TEL;TYPE=cell:+1-555-0100\r\n" +
"END:VCARD\r\n"
);
ast.vcards[0].properties.FN[0].value; // -> "Alice Example"
b.safeVcard.compliancePosture(name) #
Return the effective profile NAME for a compliance posture, or null for a name this parser does not map. Unlike the content-guard variant this returns the resolved profile string (every line-protocol parser composes gateContract.ALL_STRICT_POSTURES, so "hipaa" / "pci-dss" / "gdpr" / "soc2" all resolve to "strict") and never throws — the parser shape carries no overlay-clone, no buildProfile, and no loadRulePack. Wired by gateContract.defineParser.
b.safeVcard.compliancePosture("hipaa"); // → "strict"
b.safeVcard.compliancePosture("not-a-regime"); // → null
Last updated 2026-08-08T16:39:49.652Z by seeder.