fix(html): prevent invalid UTF-8 from blanking mail body#13352
Merged
Conversation
Html::convertLinks() passes the plain-text body to UrlLinker::escapeHtml(), which calls htmlspecialchars() without ENT_SUBSTITUTE/ENT_IGNORE. On any invalid UTF-8 byte, that call returns an empty string for the entire input, not just the bad character - so one malformed byte anywhere in a message wiped the whole visible body. Reproduced with a real report: a Domino/Notes-originated reply contained an emoji encoded as a raw UTF-16 surrogate pair (CESU-8, each half UTF-8-encoded separately instead of combined into one 4-byte sequence), which is invalid UTF-8 and triggered the bug. Repair the body via mb_convert_encoding() before it reaches convertLinks() when it isn't valid UTF-8, so only the malformed bytes degrade instead of the entire message. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
kesselb
approved these changes
Jul 24, 2026
There was a problem hiding this comment.
Pull request overview
Fixes a mail-rendering edge case in OCA\Mail\Service\Html::convertLinks() where invalid UTF-8 bytes could cause the entire plain-text body to become empty during HTML escaping/linkification, improving resilience against malformed real-world message encodings (e.g., CESU-8 / surrogate pairs).
Changes:
- Add UTF-8 validity check and attempt to “repair” invalid input before passing it into
UrlLinker/htmlspecialchars. - Add unit tests covering several invalid UTF-8 scenarios and a valid multibyte control case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lib/Service/Html.php | Adds UTF-8 validation and re-encoding before running linkification/escaping to prevent full-body blanking on invalid bytes. |
| tests/Unit/Service/HtmlTest.php | Adds regression tests for invalid UTF-8 inputs to ensure link conversion does not produce an empty output and preserves visible content. |
Comment on lines
+62
to
+68
| if (!mb_check_encoding($data, 'UTF-8')) { | ||
| // Some senders (e.g. Lotus Notes/Domino) declare a message as UTF-8 while | ||
| // actually sending a different charset. UrlLinker's escapeHtml() calls | ||
| // htmlspecialchars() without ENT_SUBSTITUTE/ENT_IGNORE, which returns an | ||
| // empty string for the whole input on the first invalid byte it hits. | ||
| $data = mb_convert_encoding($data, 'UTF-8', 'UTF-8'); | ||
| } |
Contributor
|
/backport to stable5.10 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Html::convertLinks() passes the plain-text body to UrlLinker::escapeHtml(), which calls htmlspecialchars() without ENT_SUBSTITUTE/ENT_IGNORE. On any invalid UTF-8 byte, that call returns an empty string for the entire input, not just the bad character - so one malformed byte anywhere in a message wiped the whole visible body.
Reproduced with a real report: a Domino/Notes-originated reply contained an emoji encoded as a raw UTF-16 surrogate pair (CESU-8, each half UTF-8-encoded separately instead of combined into one 4-byte sequence), which is invalid UTF-8 and triggered the bug.
Repair the body via mb_convert_encoding() before it reaches convertLinks() when it isn't valid UTF-8, so only the malformed bytes degrade instead of the entire message.
Assisted-by: Claude:claude-sonnet-4-6
🤖 AI (if applicable)