Fix vCard 3 REV and KEY value handling

Bring the remaining vCard 3 REV and KEY behavior into line with RFC
2426.

For REV, serialize VALUE=date using the date form instead of incorrectly
emitting VALUE=date-time with a timestamp payload.

For KEY, stop defaulting constructed values to VALUE=uri. The vCard 3
specification defines KEY as binary by default and allows it to be reset
to text, but not to uri. Tighten both construction and parsing
accordingly: reject VALUE=uri for KEY, enforce the relationship between
VALUE=binary and ENCODING=b, and reject VALUE=text when ENCODING=b is
present.

Update the regression coverage to reflect the spec boundary: PHOTO,
LOGO, and SOUND may round-trip as uris; KEY may contain text that looks
like a URI; KEY does not allow VALUE=uri; and vCard 3 KEY parameters
still require name=value syntax.

AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
This commit is contained in:
2026-03-28 10:46:37 -05:00
parent 6e6e06bdc4
commit 8f2a05cde6
2 changed files with 50 additions and 7 deletions

View File

@@ -609,11 +609,22 @@ func newVC3_Class*(value: string, group = none[string]()): VC3_Class =
func newVC3_Key*(
value: string,
valueType = some("uri"),
valueType = none[string](),
keyType = none[string](),
isInline = false,
group = none[string]()): VC3_Key =
if valueType.isSome:
if valueType.get == $vtUri:
raise newException(ValueError,
"KEY content does not support the '" & $vtUri & "' value type")
elif valueType.get == $vtBinary and not isInline:
raise newException(ValueError,
"KEY content with VALUE=binary must also specify ENCODING=b")
elif valueType.get == $vtText and isInline:
raise newException(ValueError,
"KEY content with ENCODING=b cannot use VALUE=text")
return assignFields(
VC3_Key(name: $pnKey, binaryType: keyType),
value, valueType, keyType, isInline, group)
@@ -855,7 +866,7 @@ proc serialize(r: VC3_Rev): string =
if r.valueType.isSome and r.valueType.get == "date-time":
result &= ";VALUE=date-time:" & r.value.format(DATETIME_FMT)
elif r.valueType.isSome and r.valueType.get == "date":
result &= ";VALUE=date-time:" & r.value.format(DATETIME_FMT)
result &= ";VALUE=date:" & r.value.format(DATE_FMT)
else:
result &= r.value.format(DATETIME_FMT)
@@ -1264,11 +1275,20 @@ proc parseContentLines*(p: var VCardParser): seq[VC3_Property] =
result.add(newVC3_Class(group = group, value = p.readValue))
of $pnKey:
let valueType = params.getSingleValue("VALUE")
let isInline = params.existsWithValue("ENCODING", "B")
if valueType.isSome:
if valueType.get == $vtUri:
p.error("invalid VALUE for KEY content. " &
"Expected '" & $vtText & "' or '" & $vtBinary & "'")
elif valueType.get == $vtBinary and not isInline:
p.error("KEY content with VALUE=binary must also specify ENCODING=b")
elif valueType.get == $vtText and isInline:
p.error("KEY content with ENCODING=b cannot use VALUE=text")
result.add(newVC3_Key(
group = group,
value = p.readBinaryValue(isInline, "KEY"),
valueType = params.getSingleValue("VALUE"),
valueType = valueType,
keyType = params.getSingleValue("TYPE"),
isInline = isInline))