From 08ddc323f906fbc72c5b70684444ef1f61388df0 Mon Sep 17 00:00:00 2001 From: Fathi Ben Nasr Date: Sat, 5 Sep 2026 21:01:25 +0000 Subject: [PATCH] HtmlUtils: strip every data-* attribute, not just the first one BuildHtml() iterates $oElement->attributes -- a live DOMNamedNodeMap -- and calls removeAttribute() on the attribute the foreach is currently on. That ends the iteration: the loop sees no further attribute, so exactly one data-* is removed per element however many it carries. Measured on PHP 8.5.10 / libxml 2.12.5, data-* left behind:

-> none

-> data-b

-> data-b, data-c

-> data-b, data-c, data-d

-> data-b The single call site is buildMessage() in RainLoop/Actions/Messages.php, reached from both DoSaveMessage() and DoSendMessage(), as the docblock on BuildHtml() says. So the attributes that survive go out in the sent message and into the draft that is APPENDed and read back later. It is not the display path: an incoming body is only decoded and charset converted in MailSo\Mail\Message::fromFetchResponse(), and the browser filters it with the allowlists in dev/Common/Html.js. The fix collects the names first, then removes them. --- .../v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php index e073363b3..0300d6c48 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php @@ -160,13 +160,20 @@ abstract class HtmlUtils // Remove all remaining data-* attributes foreach ($xpath->query('//*[@*[starts-with(name(), "data-")]]') as $oElement) { - $sTagNameLower = \strtolower($oElement->nodeName); if ($oElement->hasAttributes()) { + // DOMNamedNodeMap is live: removing the attribute the foreach is + // currently on ends the iteration, so exactly one data-* was + // removed per element however many it carried. Collect the + // names first, then remove. + $aNames = []; foreach ($oElement->attributes as $oAttr) { if ('data-' === \substr(\strtolower($oAttr->nodeName), 0, 5)) { - $oElement->removeAttribute($oAttr->nodeName); + $aNames[] = $oAttr->nodeName; } } + foreach ($aNames as $sName) { + $oElement->removeAttribute($sName); + } } }