From 37fcdbe534c5d6a18ad73020346f4480b90fd2db Mon Sep 17 00:00:00 2001 From: Fathi Ben Nasr Date: Thu, 22 Oct 2026 20:55:38 +0000 Subject: [PATCH 1/2] MailSo: keep the folding space before an encoded word that follows a plain word MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HeaderCollection::Parse() trimmed the folding whitespace in front of every continuation line starting with an encoded word. RFC 2047 6.2 ignores only whitespace BETWEEN two adjacent encoded words; after a plain word, RFC 5322 unfolding keeps it. Symfony Mailer folds subjects exactly there, so "de vos\r\n =?utf-8?Q?=C3=A9quipements?=" was displayed "voséquipements". The message list (IMAP ENVELOPE) was fine; the opened message was not. Test: php test/header-unfold.php (fails before the fix). --- .../MailSo/Mime/HeaderCollection.php | 12 ++--- test/header-unfold.php | 50 +++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 test/header-unfold.php diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php index 5b3674dbe..a0b162bfd 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php @@ -136,12 +136,12 @@ class HeaderCollection extends \MailSo\Base\Collection $sHeadersValue = \rtrim($sHeadersValue); } - if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2)) { - $sHeadersValue = \ltrim($sHeadersValue); - } - - if ('=?' === \substr($sHeadersValue, 0, 2)) { - $sValue .= $sHeadersValue; + // RFC 2047 6.2: only whitespace between two encoded words is + // dropped; after a plain word the folding space is a real space. + if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2) && '?=' === \substr($sValue, -2)) { + $sValue .= \ltrim($sHeadersValue); + } else if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2)) { + $sValue .= ' '.\ltrim($sHeadersValue); } else { $sValue .= "\n".$sHeadersValue; } diff --git a/test/header-unfold.php b/test/header-unfold.php new file mode 100644 index 000000000..9152e11a6 --- /dev/null +++ b/test/header-unfold.php @@ -0,0 +1,50 @@ +ValueByName('Subject'); +} + +$cases = [ + // plain word, then a folded encoded word: the space stays + "Traiter les =?utf-8?Q?vuln=C3=A9rabilit=C3=A9s?= de vos\r\n =?utf-8?Q?=C3=A9quipements?= : plan de =?utf-8?Q?rem=C3=A9diation?= et\r\n checklist =?utf-8?Q?sign=C3=A9e?=" + => 'Traiter les vulnérabilités de vos équipements : plan de remédiation et checklist signée', + // two encoded words split across lines: no space between them (RFC 2047 §6.2) + "=?utf-8?Q?r=C3=A9pon?=\r\n =?utf-8?Q?se?=" => 'réponse', + "=?utf-8?B?w6l0w6k=?=\r\n\t=?utf-8?B?IGZpbg==?=" => 'été fin', + // encoded word, then a folded plain word: the space stays + "=?utf-8?Q?r=C3=A9union?=\r\n demain" => 'réunion demain', + // no encoding at all + "Hello\r\n world" => 'Hello world', +]; +foreach ($cases as $raw => $want) { + $got = preg_replace('/\s+/', ' ', subject($raw)); + check($got === $want, "got [{$got}], want [{$want}]"); +} + +echo "header-unfold: ".count($cases)." checks OK\n"; From 7c432e392ba1b921e5bc10e80cd9e9ec90b0f1e6 Mon Sep 17 00:00:00 2001 From: Fathi Ben Nasr Date: Thu, 22 Oct 2026 20:55:38 +0000 Subject: [PATCH 2/2] MailSo: a folded plain-text line no longer leaves two spaces Unfolding appended a newline before the continuation line, which kept its own leading whitespace; Header turns the newline into a space, so every fold became two spaces ("et checklist"), carried into reply subjects. The test now compares exactly. --- .../0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php | 8 +++----- test/header-unfold.php | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php index a0b162bfd..2704909c9 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php @@ -136,14 +136,12 @@ class HeaderCollection extends \MailSo\Base\Collection $sHeadersValue = \rtrim($sHeadersValue); } - // RFC 2047 6.2: only whitespace between two encoded words is - // dropped; after a plain word the folding space is a real space. + // RFC 5322 unfolding removes the line break and keeps the folding + // whitespace; RFC 2047 6.2 drops it only between two encoded words. if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2) && '?=' === \substr($sValue, -2)) { $sValue .= \ltrim($sHeadersValue); - } else if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2)) { - $sValue .= ' '.\ltrim($sHeadersValue); } else { - $sValue .= "\n".$sHeadersValue; + $sValue .= ' '.\ltrim($sHeadersValue); } } else { if (null !== $sName) { diff --git a/test/header-unfold.php b/test/header-unfold.php index 9152e11a6..4b33c8d2f 100644 --- a/test/header-unfold.php +++ b/test/header-unfold.php @@ -43,7 +43,8 @@ $cases = [ "Hello\r\n world" => 'Hello world', ]; foreach ($cases as $raw => $want) { - $got = preg_replace('/\s+/', ' ', subject($raw)); + // exact: a fold is one space, never two ("et checklist" ended up in replies) + $got = subject($raw); check($got === $want, "got [{$got}], want [{$want}]"); }