Fix quoted MIME-DIR parameter parsing

Correct the vCard 3 parameter parser so quoted parameter values are
consumed according to the MIME-DIR grammar instead of failing
immediately on the opening double quote.

The fix explicitly advances past the opening quote, reads the quoted
qsafe-char sequence, and strips the surrounding quotes from the returned
parameter value. Unquoted parameter handling is unchanged.

Add private parser coverage for quoted parameter values and quoted
values containing commas, plus a public regression test showing that
quoted LANGUAGE and TYPE parameters are accepted by the vCard 3 parser.

AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
This commit is contained in:
2026-03-28 10:32:52 -05:00
parent 201556ecbe
commit 6e6e06bdc4
2 changed files with 34 additions and 2 deletions

View File

@@ -902,11 +902,12 @@ proc readParamValue(p: var VCardParser): string =
## Read a single parameter value at the current read position or error.
p.setBookmark
if p.peek == '"':
discard p.read
while QSAFE_CHARS.contains(p.peek): discard p.read
if p.read != '"':
p.error("quoted parameter value expected to end with a " &
"double quote (\")")
result = p.readSinceBookmark[0 ..< ^1]
result = p.readSinceBookmark[1 ..< ^1]
else:
while SAFE_CHARS.contains(p.peek): discard p.read
result = p.readSinceBookmark
@@ -1393,6 +1394,24 @@ proc runVCard3PrivateTests*() =
assert p.read == '='
assert p.readParamValue == "Fun&Games%"
# "readParamValue quoted":
block:
var p = initParser("FN;LANGUAGE=\"en\":John Smith")
assert p.readName == "FN"
assert p.read == ';'
assert p.readName == "LANGUAGE"
assert p.read == '='
assert p.readParamValue == "en"
# "readParamValue quoted with comma":
block:
var p = initParser("LABEL;TYPE=\"HOME,POSTAL\":123 Main St.")
assert p.readName == "LABEL"
assert p.read == ';'
assert p.readName == "TYPE"
assert p.read == '='
assert p.readParamValue == "HOME,POSTAL"
# "readParams":
block:
var p = initParser("TEL;TYPE=WORK;TYPE=Fun&Games%,Extra:+15551234567")
@@ -1407,6 +1426,15 @@ proc runVCard3PrivateTests*() =
assert params[1].values[0] == "Fun&Games%"
assert params[1].values[1] == "Extra"
# "readParams quoted":
block:
var p = initParser("FN;LANGUAGE=\"en\":John Smith")
assert p.readName == "FN"
let params = p.readParams
assert params.len == 1
assert params[0].name == "LANGUAGE"
assert params[0].values == @["en"]
# "readValue":
block:
var p = initParser("TEL;TYPE=WORK:+15551234567\r\nFN:John Smith\r\n")

View File

@@ -202,9 +202,13 @@ suite "vcard/vcard3":
let parsed = parseSingleVCard3(vcard3Doc(
"VERSION:3.0",
"FN;LANGUAGE=\"en\":John Smith",
"LABEL;TYPE=\"HOME,POSTAL\":123 Main St.",
"N:Smith;John;;;"))
check parsed.fn.language == some("en")
check:
parsed.fn.language == some("en")
parsed.label.len == 1
parsed.label[0].adrType == @["HOME,POSTAL"]
test "spec: PROFILE is exposed as the standard property type":
let parsed = parseSingleVCard3(vcard3Doc(