diff --git a/lib/__tests__/email-composer-utils.test.ts b/lib/__tests__/email-composer-utils.test.ts index 3b0502b28..117614a79 100644 --- a/lib/__tests__/email-composer-utils.test.ts +++ b/lib/__tests__/email-composer-utils.test.ts @@ -375,6 +375,19 @@ describe("parseRecipientList / formatRecipientList", () => { it("parses an empty string to an empty array", () => { expect(parseRecipientList("")).toEqual([]); }); + + it("keeps a display name with quotes as one recipient (no injected To)", () => { + // A sender named `Support", ceo@corp.example, "x` used to come back from + // the draft stash as three recipients, one of them the injected ceo. + const list = [{ name: 'Support", ceo@corp.example, "x', email: "help@vendor.example" }]; + const serialized = formatRecipientList(list); + expect(parseRecipientList(serialized)).toEqual(list); + }); + + it("round-trips backslashes in a quoted name", () => { + const list = [{ name: 'a\\b, "c"', email: "x@example.com" }, { email: "y@example.com" }]; + expect(parseRecipientList(formatRecipientList(list))).toEqual(list); + }); }); describe("splitPastedRecipients", () => { diff --git a/lib/email-composer-utils.ts b/lib/email-composer-utils.ts index de98389da..c69aa6d93 100644 --- a/lib/email-composer-utils.ts +++ b/lib/email-composer-utils.ts @@ -254,7 +254,8 @@ function angleRunCloses(value: string, from: number): boolean { let inQuotes = false; for (let i = from + 1; i < value.length; i++) { const ch = value[i]; - if (ch === '"') inQuotes = !inQuotes; + if (inQuotes && ch === '\\') i++; + else if (ch === '"') inQuotes = !inQuotes; else if (inQuotes) continue; else if (ch === '>') return true; else if (ch === '<') return false; @@ -280,7 +281,13 @@ export function splitRecipients(value: string, separators = ','): string[] { let inGroup = false; for (let i = 0; i < value.length; i++) { const ch = value[i]; - if (ch === '"') { + if (inQuotes && ch === '\\' && i + 1 < value.length) { + // A quoted-pair: formatRecipient writes a `"` in a display name as + // `\"`. Taking it as the closing quote let a sender's name like + // `Support", ceo@corp.example, "x` split into an extra recipient. + current += ch + value[i + 1]; + i++; + } else if (ch === '"') { inQuotes = !inQuotes; current += ch; } else if (ch === '<' && !inQuotes) { @@ -353,7 +360,8 @@ function findTopLevelColon(value: string): number { let inAngle = false; for (let i = 0; i < value.length; i++) { const ch = value[i]; - if (ch === '"') inQuotes = !inQuotes; + if (inQuotes && ch === '\\') i++; + else if (ch === '"') inQuotes = !inQuotes; else if (ch === '<' && !inQuotes) inAngle = true; else if (ch === '>' && !inQuotes) inAngle = false; else if (ch === ':' && !inQuotes && !inAngle) return i;