Skip to content

MimePart: do not quote a display name that is MIME-encoded - #106

Open
hubipe wants to merge 1 commit into
nette:masterfrom
hubipe:fix-encoded-word-quotes
Open

MimePart: do not quote a display name that is MIME-encoded#106
hubipe wants to merge 1 commit into
nette:masterfrom
hubipe:fix-encoded-word-quotes

Conversation

@hubipe

@hubipe hubipe commented Aug 14, 2026

Copy link
Copy Markdown
Q A
Bug fix? yes
New feature? no
BC break? no API change, but the bytes of encoded display names change (see below)

Problem

encodeSequence() wrapped a display name in a quoted-string before handing it to iconv_mime_encode(), so the quotes ended up inside the base64 payload of the encoded-word. Clients decode them as literal characters, and every name containing diacritics reached the recipient with visible quotation marks around it:

$mail->setFrom('objednavky@domena.cz', 'Objednávky domena.cz');
// From: =?UTF-8?B?Ik9iamVkbsOhdmt5IGRvbWVuYS5jeiI=?= <objednavky@domena.cz>
// displayed as: "Objednávky domena.cz" <objednavky@domena.cz>

Quoting is also unnecessary on that path. An encoded-word is built from =?, the charset token and base64 characters, all of which are safe inside a phrase; the original dots, commas, parentheses and quotes cannot break the header once they are encoded. RFC 2047 §5 puts it the other way round: an encoded-word must not appear within a quoted-string.

Solution

Drop the $escape() call on the encoded-word branch. The same call on the literal branch stays – there the name really is emitted as-is, and characters outside atext do need a quoted-string.

$mail->setFrom('objednavky@domena.cz', 'Objednávky domena.cz');
// From: =?UTF-8?B?T2JqZWRuw6F2a3kgZG9tZW5hLmN6?= <objednavky@domena.cz>
// displayed as: Objednávky domena.cz <objednavky@domena.cz>

SequenceValue (RFC 2231 Content-Disposition filenames) is untouched – there the quotes are added after encoding, i.e. outside the encoded text, which is what that grammar requires.

Changed test expectations

Four existing tests asserted the old bytes, so their expectations are updated. Every new value decodes back to exactly the name that was passed in – verified with iconv_mime_decode():

test before → after (decoded)
Mail.email.phpt "Žluťoučký kůň"Žluťoučký kůň
Mail.email.phpt "Žluťoučký \"kůň\""Žluťoučký "kůň"
Mail.email.multiple.phpt, Mail.longLines.phpt "Řehoř Řízek"Řehoř Řízek, "Luboš Smažák"Luboš Smažák
Mail.headers.002.phpt "Kdo uteče, obědvá"Kdo uteče, obědvá

Two of those deserve a note, because they look like the cases the quoting was meant to protect:

  • Žluťoučký "kůň" – the quotes the caller supplied were previously backslash-escaped and the escapes were base64-encoded, so the recipient saw "Žluťoučký \"kůň\"". They now survive verbatim.
  • Kdo uteče, obědvá – the comma is inside the base64 payload, so it cannot be read as an address separator. Mail.headers.002.phpt now asserts this explicitly.

tests/Mail/Mail.email.encodedName.phpt is added as a regression test, covering both paths: an ASCII name outside atext still becomes a real quoted-string, an encoded name carries no quotes, caller-supplied quotes round-trip, and a comma in an encoded name does not split the address list.

docs/internals.md gains a bullet on why the quoting belongs to the literal path only.

Compatibility

No API or behaviour change for callers – getFrom() etc. already returned the unquoted name, this only makes the wire format agree with it. What does change is the encoded bytes of any non-ASCII display name, so anything asserting header bytes verbatim (as the four tests above did) will need updating. Worth deciding whether this lands on master or a patch branch.

Verification

All four CI jobs run locally on PHP 8.4:

vendor/bin/tester tests -s      # OK (56 tests)
vendor/bin/phpstan analyse      # [OK] No errors
code-checker --strict-types     # clean (only pre-existing HEREDOC tips in untouched files)
coding-standard/ecs check       # Code style checks passed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant