From addb2a2d8d60f63e64d5700ede03abe502a82ce9 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Sat, 28 Mar 2026 21:41:55 -0500 Subject: [PATCH] Validate vCard 4 MEMBER and PID semantics --- src/vcard/vcard4.nim | 43 +++++++++++++++++++++++++++++++++++++++++++ tests/tvcard4.nim | 3 ++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/vcard/vcard4.nim b/src/vcard/vcard4.nim index 58c5271..9cd95c9 100644 --- a/src/vcard/vcard4.nim +++ b/src/vcard/vcard4.nim @@ -1186,6 +1186,49 @@ func validate*(vc4: VCard4): void = of vpcAny: discard + if vc4.member.len > 0: + if vc4.kind.isNone or vc4.kind.get.value.toLowerAscii != "group": + raise newException(ValueError, + "MEMBER properties require the KIND property to be set to 'group'") + + var clientPidMaps = initTable[int, string]() + for clientPidMap in vc4.clientpidmap: + if clientPidMap.id <= 0: + raise newException(ValueError, + "CLIENTPIDMAP identifiers must be positive integers") + if clientPidMaps.contains(clientPidMap.id): + raise newException(ValueError, + "CLIENTPIDMAP identifier $# appears more than once" % [$clientPidMap.id]) + clientPidMaps[clientPidMap.id] = clientPidMap.uri + + var referencedSourceIds = initHashSet[int]() + for prop in vc4.content: + var pidParam = none[VC_Param]() + for param in prop.params: + if param.name == "PID": + pidParam = some(param) + break + if pidParam.isNone: + continue + + let pidValues = + try: + parsePidValues(pidParam.get) + except VCardParsingError as exc: + raise newException(ValueError, exc.msg) + + for pidValue in pidValues: + if pidValue.propertyId <= 0 or pidValue.sourceId <= 0: + raise newException(ValueError, + "PID identifiers must be positive integers") + referencedSourceIds.incl(pidValue.sourceId) + + for sourceId in referencedSourceIds: + if not clientPidMaps.contains(sourceId): + raise newException(ValueError, + "PID source identifier $# is missing a matching CLIENTPIDMAP" % + [$sourceId]) + # Setters # ============================================================================= diff --git a/tests/tvcard4.nim b/tests/tvcard4.nim index 1028547..deb8884 100644 --- a/tests/tvcard4.nim +++ b/tests/tvcard4.nim @@ -141,7 +141,8 @@ suite "vcard/vcard4": let parsed = parseSingleVCard4(vcard4Doc( "VERSION:4.0", "FN:John Smith", - "EMAIL;PID=1.7:test@example.com")) + "EMAIL;PID=1.7:test@example.com", + "CLIENTPIDMAP:7;urn:uuid:device-7")) check: parsed.email.len == 1 parsed.email[0].pid == @[PidValue(propertyId: 1, sourceId: 7)]