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:
@@ -609,11 +609,22 @@ func newVC3_Class*(value: string, group = none[string]()): VC3_Class =
|
|||||||
|
|
||||||
func newVC3_Key*(
|
func newVC3_Key*(
|
||||||
value: string,
|
value: string,
|
||||||
valueType = some("uri"),
|
valueType = none[string](),
|
||||||
keyType = none[string](),
|
keyType = none[string](),
|
||||||
isInline = false,
|
isInline = false,
|
||||||
group = none[string]()): VC3_Key =
|
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(
|
return assignFields(
|
||||||
VC3_Key(name: $pnKey, binaryType: keyType),
|
VC3_Key(name: $pnKey, binaryType: keyType),
|
||||||
value, valueType, keyType, isInline, group)
|
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":
|
if r.valueType.isSome and r.valueType.get == "date-time":
|
||||||
result &= ";VALUE=date-time:" & r.value.format(DATETIME_FMT)
|
result &= ";VALUE=date-time:" & r.value.format(DATETIME_FMT)
|
||||||
elif r.valueType.isSome and r.valueType.get == "date":
|
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:
|
else:
|
||||||
result &= r.value.format(DATETIME_FMT)
|
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))
|
result.add(newVC3_Class(group = group, value = p.readValue))
|
||||||
|
|
||||||
of $pnKey:
|
of $pnKey:
|
||||||
|
let valueType = params.getSingleValue("VALUE")
|
||||||
let isInline = params.existsWithValue("ENCODING", "B")
|
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(
|
result.add(newVC3_Key(
|
||||||
group = group,
|
group = group,
|
||||||
value = p.readBinaryValue(isInline, "KEY"),
|
value = p.readBinaryValue(isInline, "KEY"),
|
||||||
valueType = params.getSingleValue("VALUE"),
|
valueType = valueType,
|
||||||
keyType = params.getSingleValue("TYPE"),
|
keyType = params.getSingleValue("TYPE"),
|
||||||
isInline = isInline))
|
isInline = isInline))
|
||||||
|
|
||||||
|
|||||||
@@ -182,21 +182,44 @@ suite "vcard/vcard3":
|
|||||||
serialized.contains("SOUND;ENCODING=b;TYPE=WAVE:" & payload)
|
serialized.contains("SOUND;ENCODING=b;TYPE=WAVE:" & payload)
|
||||||
serialized.contains("KEY;ENCODING=b;TYPE=PGP:" & payload)
|
serialized.contains("KEY;ENCODING=b;TYPE=PGP:" & payload)
|
||||||
|
|
||||||
test "spec: uri-backed binary properties round-trip as uris":
|
test "spec: PHOTO, LOGO, and SOUND may round-trip as uris":
|
||||||
let serialized = $parseSingleVCard3(vcard3Doc(
|
let serialized = $parseSingleVCard3(vcard3Doc(
|
||||||
"VERSION:3.0",
|
"VERSION:3.0",
|
||||||
"FN:John Smith",
|
"FN:John Smith",
|
||||||
"N:Smith;John;;;",
|
"N:Smith;John;;;",
|
||||||
"PHOTO;VALUE=uri:http://example.test/photo.jpg",
|
"PHOTO;VALUE=uri:http://example.test/photo.jpg",
|
||||||
"LOGO;VALUE=uri:http://example.test/logo.gif",
|
"LOGO;VALUE=uri:http://example.test/logo.gif",
|
||||||
"SOUND;VALUE=uri:http://example.test/sound.wav",
|
"SOUND;VALUE=uri:http://example.test/sound.wav"))
|
||||||
"KEY;VALUE=uri:http://example.test/key.asc"))
|
|
||||||
|
|
||||||
check:
|
check:
|
||||||
serialized.contains("PHOTO;VALUE=uri:http://example.test/photo.jpg")
|
serialized.contains("PHOTO;VALUE=uri:http://example.test/photo.jpg")
|
||||||
serialized.contains("LOGO;VALUE=uri:http://example.test/logo.gif")
|
serialized.contains("LOGO;VALUE=uri:http://example.test/logo.gif")
|
||||||
serialized.contains("SOUND;VALUE=uri:http://example.test/sound.wav")
|
serialized.contains("SOUND;VALUE=uri:http://example.test/sound.wav")
|
||||||
serialized.contains("KEY;VALUE=uri:http://example.test/key.asc")
|
|
||||||
|
test "spec: KEY does not allow uri values":
|
||||||
|
expect(VCardParsingError):
|
||||||
|
discard parseVCards(vcard3Doc(
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:John Smith",
|
||||||
|
"N:Smith;John;;;",
|
||||||
|
"KEY;VALUE=uri:http://example.test/key.asc"))
|
||||||
|
|
||||||
|
test "spec: KEY may contain text values that look like uris":
|
||||||
|
let serialized = $parseSingleVCard3(vcard3Doc(
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:John Smith",
|
||||||
|
"N:Smith;John;;;",
|
||||||
|
"KEY;TYPE=PGP:http://example.test/key.pgp"))
|
||||||
|
|
||||||
|
check serialized.contains("KEY;TYPE=PGP:http://example.test/key.pgp")
|
||||||
|
|
||||||
|
test "spec: KEY parameters must use name=value syntax in vCard 3":
|
||||||
|
expect(VCardParsingError):
|
||||||
|
discard parseVCards(vcard3Doc(
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:John Smith",
|
||||||
|
"N:Smith;John;;;",
|
||||||
|
"KEY;PGP:http://example.test/key.pgp"))
|
||||||
|
|
||||||
test "spec: quoted parameter values are accepted":
|
test "spec: quoted parameter values are accepted":
|
||||||
let parsed = parseSingleVCard3(vcard3Doc(
|
let parsed = parseSingleVCard3(vcard3Doc(
|
||||||
|
|||||||
Reference in New Issue
Block a user