Skip to content

markdown() double-encodes HTML entities inside code blocks #2356

@9clg6

Description

@9clg6

Bug Description

The markdown() function in lib/src/utils/markdown.dart pre-escapes all <> patterns before passing text to markdownToHtml():

text.replaceAllMapped(
  RegExp(r'<([^>]*)>'),
  (match) => '&lt;${match.group(1)}&gt;',
)

However, markdownToHtml() (from the markdown package) also escapes special characters inside code blocks. This causes double encoding.

Steps to Reproduce

Send a message containing a code block with angle brackets:

```dart
List<String> items = [];
```

Expected

The formatted_body HTML should contain single-encoded entities inside <code>:

List  &  l t ; String &  g t ;

(i.e. & + lt; and & + gt; — standard HTML encoding of < and >)

Actual

The formatted_body HTML contains double-encoded entities:

List  &  a m p ; l t ; String  &  a m p ; g t ;

(i.e. & + amp;lt; — the & itself got encoded to &amp;, producing &amp;lt;)

Which renders as literal text List&lt;String&gt; instead of List<String>.

Pipeline

  1. Pre-processing regex: <String>&lt;String&gt; (the <> are replaced with entities)
  2. markdownToHtml() code block escaping: &lt;String&gt;&amp;lt;String&amp;gt; (the & gets escaped again)
  3. DOM parser decodes one level: &amp;lt;&lt; (only the outer &amp; is decoded)
  4. User sees: &lt;String&gt; instead of <String>

Additional Issues

The regex <([^>]*)> also has a greedy matching problem with nested generics. For Map<String, List<int>>, the regex matches <String, List<int> as one group (because [^>]* stops at the first >), leaving inconsistent escaping.

Affected Versions

Introduced in PR #2178 ("refactor: Escape HTML tags before markdown rendering", merged 2025-11-06). Still present on main (v7.2.2).

Suggested Fix

The markdown package's markdownToHtml() already handles HTML escaping inside code blocks via its encodeHtml parameter (default true). The pre-escape regex should either:

  • Skip content inside code fences (` and ```)
  • Or be removed entirely if the markdown package's built-in escaping is sufficient for the non-code use case

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions