Skip to content

Fix: Calendar event HTML description rendered as plain text#4558

Merged
hoangdat merged 5 commits into
masterfrom
fix/calendar-event-html-description-rendered-as-plain-text
May 28, 2026
Merged

Fix: Calendar event HTML description rendered as plain text#4558
hoangdat merged 5 commits into
masterfrom
fix/calendar-event-html-description-rendered-as-plain-text

Conversation

@dab246
Copy link
Copy Markdown
Member

@dab246 dab246 commented May 25, 2026

Problem

Calendar event (ICS) descriptions that contain HTML markup were displayed as raw, unrendered text in the email viewer. Instead of seeing formatted content, users saw the literal HTML tags — e.g. a description like:

<p>We are delighted to extend a warm invitation...</p>

was shown as the visible characters <p>We are delighted to extend a warm invitation...</p> instead of a rendered paragraph.

Root Cause

The transformer pipeline in _parseCalendarEventAction was treating HTML descriptions as plain text:

// Before — plain-text pipeline
SanitizeAutolinkHtmlTransformers(),        // escapeHtml: true → HTML-escapes everything
SanitizePlainTextHtmlOutputTransformer(),  // strips all tags except <a>
NewLineTransformer(),

SanitizeAutolinkHtmlTransformers with escapeHtml: true calls htmlEscape.convert() on the raw text before any DOM parsing, converting < and > to &lt;/&gt;. This is correct for plain-text emails, but ICS descriptions are typically HTML — so the markup was rendered as visible escaped characters instead of formatted content.

Fix

Introduce AutolinkTextNodeTransformer — a DOM transformer that walks the already-parsed DOM to linkify bare URLs and email addresses in text nodes — and replace the plain-text pipeline with an HTML-aware named factory:

// After — HTML-aware pipeline (TransformConfiguration.forCalendarEvent)
// Text transformers:
StandardizeHtmlSanitizingTransformers(),  // sanitizes HTML (strips dangerous tags/attrs)
NewLineTransformer(),
// DOM transformers:
AutolinkTextNodeTransformer(),            // linkifies bare URLs/emails in text nodes
SanitizeHyperLinkTagInHtmlTransformer(),  // sanitizes href, adds target="_blank" + rel="noreferrer"

AutolinkTextNodeTransformer walks the parsed DOM tree rather than using a regex over raw text, so tag boundaries come from the document structure. This eliminates any ReDoS risk and avoids double-linkifying content inside existing <a>, <script>, <style>, or <textarea> elements.

SanitizeAutolinkFilter was also refactored alongside this change: the escapeHtml flag was removed (it always HTML-escapes text elements now), and _linkifyOption/_linkifier were made static to avoid re-allocating per call.

Security

XSS is blocked by StandardizeHtmlSanitizingTransformers, which strips <script> tags entirely, removes event-handler attributes (onerror, onclick, …), and strips javascript: URIs from href attributes. This is verified by the updated tests in single_email_controller_test.dart and calendar_event_description_transform_test.dart.

Note: the XSS neutralisation behaviour changed. The old pipeline rendered <script>alert(1)</script> as visible escaped text (&lt;script&gt;…). The new pipeline removes the element entirely. Both outcomes are safe; the new one produces cleaner output.

Summary by CodeRabbit

  • New Features

    • Calendar event descriptions now auto-linkify plain URLs and email addresses into safe, clickable links.
  • Improvements

    • Safer, standardized HTML sanitization for calendar event content.
    • Preserves rich HTML formatting (links, lists, basic styling) while stripping unsafe attributes and scripts.
  • Tests

    • Expanded test coverage for calendar description rendering, sanitization, and linkification behaviors.

Review Change Stack

Signed-off-by: dab246 <tdvu@linagora.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca291cb6-2132-45a2-b1d8-802262b6c624

📥 Commits

Reviewing files that changed from the base of the PR and between 2a08d81 and f863961.

📒 Files selected for processing (3)
  • core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart
  • core/test/utils/autolink_text_node_transformer_test.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
  • core/test/utils/autolink_text_node_transformer_test.dart

Walkthrough

Adds AutolinkTextNodeTransformer (DOM-level URL/email linkification), refactors SanitizeAutolinkFilter to a simpler linkify-and-escape flow, introduces TransformConfiguration.forCalendarEvent(), updates SingleEmailController to use that factory, and updates/expands tests across transformer, local transform, repository, and controller layers to match new sanitization and passthrough behavior.

Suggested reviewers

  • tddang-linagora
  • chibenwa
  • hoangdat
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: addressing the issue where calendar event HTML descriptions were being rendered as plain escaped text instead of as parsed HTML.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/calendar-event-html-description-rendered-as-plain-text

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Comment thread lib/features/email/presentation/controller/single_email_controller.dart Outdated
@github-actions
Copy link
Copy Markdown

This PR has been deployed to https://linagora.github.io/tmail-flutter/4558.

@chibenwa
Copy link
Copy Markdown
Member

chibenwa commented May 25, 2026

@coderabbitai review this please

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

@chibenwa Sure, I'll review the PR right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

@chibenwa Sure, triggering a review of the PR now!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
test/features/email/data/repository/calendar_event_repository_impl_test.dart (1)

153-161: ⚡ Quick win

Assert the forwarded TransformConfiguration explicitly.

Current delegation checks use any for the second argument, so a wrong config could still pass. Verify the exact transformConfiguration object in these assertions.

Suggested test tightening
 Future<void> expectDelegated({
   required String raw,
   required String sanitized,
 }) async {
   when(htmlDatasource.transformHtmlEmailContent(any, any))
     .thenAnswer((_) async => sanitized);
   final result = await transformSingle(CalendarEvent(description: raw));
-  verify(htmlDatasource.transformHtmlEmailContent(raw, any)).called(1);
+  verify(htmlDatasource.transformHtmlEmailContent(raw, transformConfiguration)).called(1);
   expect(result.first.calendarEventList.first.description, sanitized);
 }

 ...

 test('should call htmlDataSource with the raw description', () async {
@@
-  verify(htmlDatasource.transformHtmlEmailContent(rawDescription, any)).called(1);
+  verify(htmlDatasource.transformHtmlEmailContent(rawDescription, transformConfiguration)).called(1);
 });

Also applies to: 195-206

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/features/email/data/repository/calendar_event_repository_impl_test.dart`
around lines 153 - 161, The test helper expectDelegated currently verifies
htmlDatasource.transformHtmlEmailContent(raw, any) which allows any
TransformConfiguration; change the assertions to verify the exact
TransformConfiguration being forwarded by constructing or capturing the expected
TransformConfiguration and using that in verify (e.g.,
verify(htmlDatasource.transformHtmlEmailContent(raw, expectedConfig)) or use a
capture/argThat predicate) so the transformSingle(CalendarEvent...) call is
asserted to pass the precise TransformConfiguration; apply the same tightening
for the other occurrences around lines 195-206 where
htmlDatasource.transformHtmlEmailContent is verified.
core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart (2)

9-12: ⚡ Quick win

Add method documentation to clarify parameter usage.

The htmlEscape parameter is passed to SanitizeAutolinkFilter but text content is not escaped due to escapeHtml: false. Without documentation, it's unclear why the parameter is passed if escaping is disabled. Add a comment or method doc clarifying that htmlEscape is still used internally by the filter for building link tags, just not for escaping text content.

📝 Suggested documentation
  `@override`
+ /// Processes text by sanitizing autolinks without HTML-escaping the content.
+ ///
+ /// The [htmlEscape] parameter is passed to [SanitizeAutolinkFilter] for
+ /// building link tags, but text content is preserved as-is (not escaped)
+ /// due to `escapeHtml: false`.
  String process(String text, HtmlEscape htmlEscape) {
    return SanitizeAutolinkFilter(htmlEscape, escapeHtml: false).process(text);
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart`
around lines 9 - 12, Add a concise method/doc comment to the process(String
text, HtmlEscape htmlEscape) method explaining that although escapeHtml is set
to false when constructing SanitizeAutolinkFilter, the htmlEscape parameter is
still required and passed through because the SanitizeAutolinkFilter uses it
internally when building link tags (not for escaping plain text content);
reference the process method name, the htmlEscape parameter,
SanitizeAutolinkFilter, and the escapeHtml flag in the comment so readers know
why htmlEscape is provided despite escaping being disabled.

6-7: ⚡ Quick win

Add class-level documentation.

The class lacks documentation explaining its purpose, security implications, and how it differs from similar transformers. This is important context for a security-sensitive component that deliberately disables HTML escaping.

Consider adding DartDoc:

/// A text transformer that sanitizes autolinks while preserving HTML markup.
///
/// This transformer delegates to [SanitizeAutolinkFilter] with `escapeHtml: false`,
/// which means HTML tags in the input text are preserved as-is rather than being
/// HTML-escaped. This allows rich-text content (like calendar event descriptions)
/// to render as formatted HTML.
///
/// **Security**: This transformer does NOT provide XSS protection. It must be
/// followed by [StandardizeHtmlSanitizingTransformers] or equivalent to remove
/// dangerous HTML elements, attributes, and JavaScript.
class SanitizeAutolinkUnescapeHtmlTransformer extends TextTransformer {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart`
around lines 6 - 7, Add a DartDoc comment to the
SanitizeAutolinkUnescapeHtmlTransformer class that explains its purpose
(sanitizes autolinks while preserving HTML), notes it delegates to
SanitizeAutolinkFilter with escapeHtml: false, and clearly states the security
implications (it does NOT provide XSS protection and must be followed by
StandardizeHtmlSanitizingTransformers or equivalent to remove dangerous
tags/attributes/JS); reference the class name
SanitizeAutolinkUnescapeHtmlTransformer and the related SanitizeAutolinkFilter
and StandardizeHtmlSanitizingTransformers in the comment so future maintainers
know usage and safety requirements.
lib/features/email/presentation/controller/single_email_controller.dart (1)

1257-1267: ⚡ Quick win

Consider extracting the transformer configuration to a reusable method.

A previous review comment suggested extracting this transformer list to a specific method that can be reused in tests. This would improve maintainability and ensure consistency between production and test configurations.

♻️ Suggested refactoring

Add a static factory method to create the configuration:

static TransformConfiguration createCalendarEventTransformConfiguration() {
  return TransformConfiguration.create(
    customTextTransformers: const [
      SanitizeAutolinkUnescapeHtmlTransformer(),
      StandardizeHtmlSanitizingTransformers(),
      NewLineTransformer(),
    ],
    customDomTransformers: [
      SanitizeHyperLinkTagInHtmlTransformer(),
    ]
  );
}

Then use it here:

  void _parseCalendarEventAction({
    required AccountId accountId,
    required Set<Id> blobIds,
  }) {
    log("SingleEmailController::_parseCalendarEventAction:blobIds: $blobIds");
    consumeState(_parseCalendarEventInteractor!.execute(
      accountId,
      blobIds,
-     TransformConfiguration.create(
-       customTextTransformers: const [
-         SanitizeAutolinkUnescapeHtmlTransformer(),
-         StandardizeHtmlSanitizingTransformers(),
-         NewLineTransformer(),
-       ],
-       customDomTransformers: [
-         SanitizeHyperLinkTagInHtmlTransformer(),
-       ]
-     )
+     createCalendarEventTransformConfiguration()
    ));
  }

And reuse the same method in test files for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/email/presentation/controller/single_email_controller.dart`
around lines 1257 - 1267, Extract the inline TransformConfiguration.create call
into a reusable static factory method (e.g.
createCalendarEventTransformConfiguration) that returns
TransformConfiguration.create with the same customTextTransformers
(SanitizeAutolinkUnescapeHtmlTransformer, StandardizeHtmlSanitizingTransformers,
NewLineTransformer) and customDomTransformers
(SanitizeHyperLinkTagInHtmlTransformer); replace the inline call with a call to
that new static method in single_email_controller and import/use the same
factory in tests so production and test transformer lists remain identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart`:
- Around line 9-12: Add a concise method/doc comment to the process(String text,
HtmlEscape htmlEscape) method explaining that although escapeHtml is set to
false when constructing SanitizeAutolinkFilter, the htmlEscape parameter is
still required and passed through because the SanitizeAutolinkFilter uses it
internally when building link tags (not for escaping plain text content);
reference the process method name, the htmlEscape parameter,
SanitizeAutolinkFilter, and the escapeHtml flag in the comment so readers know
why htmlEscape is provided despite escaping being disabled.
- Around line 6-7: Add a DartDoc comment to the
SanitizeAutolinkUnescapeHtmlTransformer class that explains its purpose
(sanitizes autolinks while preserving HTML), notes it delegates to
SanitizeAutolinkFilter with escapeHtml: false, and clearly states the security
implications (it does NOT provide XSS protection and must be followed by
StandardizeHtmlSanitizingTransformers or equivalent to remove dangerous
tags/attributes/JS); reference the class name
SanitizeAutolinkUnescapeHtmlTransformer and the related SanitizeAutolinkFilter
and StandardizeHtmlSanitizingTransformers in the comment so future maintainers
know usage and safety requirements.

In `@lib/features/email/presentation/controller/single_email_controller.dart`:
- Around line 1257-1267: Extract the inline TransformConfiguration.create call
into a reusable static factory method (e.g.
createCalendarEventTransformConfiguration) that returns
TransformConfiguration.create with the same customTextTransformers
(SanitizeAutolinkUnescapeHtmlTransformer, StandardizeHtmlSanitizingTransformers,
NewLineTransformer) and customDomTransformers
(SanitizeHyperLinkTagInHtmlTransformer); replace the inline call with a call to
that new static method in single_email_controller and import/use the same
factory in tests so production and test transformer lists remain identical.

In
`@test/features/email/data/repository/calendar_event_repository_impl_test.dart`:
- Around line 153-161: The test helper expectDelegated currently verifies
htmlDatasource.transformHtmlEmailContent(raw, any) which allows any
TransformConfiguration; change the assertions to verify the exact
TransformConfiguration being forwarded by constructing or capturing the expected
TransformConfiguration and using that in verify (e.g.,
verify(htmlDatasource.transformHtmlEmailContent(raw, expectedConfig)) or use a
capture/argThat predicate) so the transformSingle(CalendarEvent...) call is
asserted to pass the precise TransformConfiguration; apply the same tightening
for the other occurrences around lines 195-206 where
htmlDatasource.transformHtmlEmailContent is verified.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ace5a593-95b7-4122-a9d8-4ac5d608fc7c

📥 Commits

Reviewing files that changed from the base of the PR and between a41f83d and 1693db2.

📒 Files selected for processing (5)
  • core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart
  • lib/features/email/presentation/controller/single_email_controller.dart
  • test/features/email/data/local/calendar_event_description_transform_test.dart
  • test/features/email/data/repository/calendar_event_repository_impl_test.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart

@dab246 dab246 marked this pull request as ready for review May 26, 2026 01:26
@dab246 dab246 requested a review from hoangdat May 26, 2026 01:42
codescene-delta-analysis[bot]

This comment was marked as outdated.

});
});

group('HTML passthrough — HTML descriptions rendered as formatted content', () {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about the case

    test('SHOULD preserve an existing HTML link destination and text', () async {
        // Real-world calendar invites embed join links as HTML <a href>.
        const description = '<a href="https://example.com/meeting">Join Zoom</a>';
        final result = await transformDescription(description);
        // The original destination URL must survive intact.
        expect(result, contains('href="https://example.com/meeting"'));
        // The visible link text must survive.
        expect(result, contains('Join Zoom'));
        // The markup must not be mangled into garbage by the linkifier.
        expect(result, isNot(contains('<ahref')));
      });

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

});
});

group('HTML passthrough — HTML descriptions rendered as formatted content', () {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more coverage test

      test('SHOULD NOT corrupt an HTML link whose visible text is also a URL', () async {
        // href and anchor text both contain a URL — the linkifier must not
        // double-process the attribute and break the tag.
        const description =
            '<a href="https://example.com/a">https://example.com/a</a>';
        final result = await transformDescription(description);
        expect(result, contains('href="https://example.com/a"'));
        expect(result, isNot(contains('<ahref')));
      });

      test('SHOULD preserve a URL sitting in an <img> src attribute without mangling text', () async {
        // URL lives in the src attribute, not body text. <img> may be stripped
        // by the sanitizer, but the surrounding description must stay intact.
        const description =
            '<img src="https://cdn.example.com/a.png"> Team photo';
        final result = await transformDescription(description);
        expect(result, contains('Team photo'));
        expect(result, isNot(contains('<ahref')));
      });

      test('SHOULD autolink a bare URL that appears inside an HTML element', () async {
        // Positive guard: a URL in text content (not an attribute) must still
        // become a clickable link, even when wrapped in an element.
        const description =
            '<p>Join the call at https://meet.example.com/room/9</p>';
        final result = await transformDescription(description);
        expect(result, contains('href="https://meet.example.com/room/9"'));
        expect(result, contains('<p>'));
      });

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@dab246 dab246 requested a review from hoangdat May 27, 2026 05:25
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart (2)

39-39: 💤 Low value

Logging every processed result may be too verbose for production.

The current log() call will output the full processed HTML for every calendar event description. This could generate significant log volume when users view multiple calendar events. Consider using logTrace() (for diagnostic purposes only) or removing this log entirely after initial validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart`
at line 39, Replace the verbose public log in SanitizeAutolinkFilter::process
that prints the full processed HTML by either removing it or switching it to a
diagnostic-only call such as logTrace() (or the project's equivalent trace-level
logger), so that the HTML payload is not always emitted in production; locate
the line that calls log('SanitizeAutolinkFilter::process:htmlTextBuffer =
$result') inside the process method and change it to a trace/debug-level log or
delete the call depending on whether you want retained diagnostic visibility.

25-27: 💤 Low value

Consider \" inside quoted HTML attribute values in _htmlTagPattern

_htmlTagPattern handles quoted attributes by forbidding the delimiting quote character inside the value, so it won’t correctly match malformed inputs like title="He said \"hello\"" as a single tag. Existing tests already cover the intended > case via &gt; and the fallback for unclosed tags (core/test/sanitize_autolink_filter_test.dart). A repo-wide search also found no \" sequences in test/data. Add a small test or document that only standard HTML escaping (e.g., &quot;) is supported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart`
around lines 25 - 27, The _htmlTagPattern regex does not allow backslash-escaped
quote characters inside quoted attribute values (e.g., title="He said
\"hello\""), so either update the pattern or explicitly document/testing the
supported behavior: either (A) broaden _htmlTagPattern to accept
backslash-escaped quotes inside double- and single-quoted attributes (so it
treats \" and \' as part of the value) by altering the RegExp in
sanitize_autolink_filter.dart, or (B) add a small unit test in
core/test/sanitize_autolink_filter_test.dart and a short comment above the
static _htmlTagPattern explaining that only standard HTML escaping (e.g.,
&quot;) is supported and backslash-escaped quotes are not recognized; pick one
approach and implement it consistently (update the RegExp if you need to accept
\" sequences, or add the test+doc if you want to explicitly reject them).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart`:
- Line 39: Replace the verbose public log in SanitizeAutolinkFilter::process
that prints the full processed HTML by either removing it or switching it to a
diagnostic-only call such as logTrace() (or the project's equivalent trace-level
logger), so that the HTML payload is not always emitted in production; locate
the line that calls log('SanitizeAutolinkFilter::process:htmlTextBuffer =
$result') inside the process method and change it to a trace/debug-level log or
delete the call depending on whether you want retained diagnostic visibility.
- Around line 25-27: The _htmlTagPattern regex does not allow backslash-escaped
quote characters inside quoted attribute values (e.g., title="He said
\"hello\""), so either update the pattern or explicitly document/testing the
supported behavior: either (A) broaden _htmlTagPattern to accept
backslash-escaped quotes inside double- and single-quoted attributes (so it
treats \" and \' as part of the value) by altering the RegExp in
sanitize_autolink_filter.dart, or (B) add a small unit test in
core/test/sanitize_autolink_filter_test.dart and a short comment above the
static _htmlTagPattern explaining that only standard HTML escaping (e.g.,
&quot;) is supported and backslash-escaped quotes are not recognized; pick one
approach and implement it consistently (update the RegExp if you need to accept
\" sequences, or add the test+doc if you want to explicitly reject them).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7cb0c434-d236-492a-9d9d-670fc074a10f

📥 Commits

Reviewing files that changed from the base of the PR and between b02511f and 17033b3.

📒 Files selected for processing (3)
  • core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart
  • core/test/sanitize_autolink_filter_test.dart
  • test/features/email/data/local/calendar_event_description_transform_test.dart


// Matches a complete HTML tag, correctly handling quoted attribute values
// that may contain the > character (e.g. title="a > b").
static final _htmlTagPattern = RegExp(r'''<(?:[^>"']*|"[^"]*"|'[^']*')*>''');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is ReDoS, should to find other approach to handle it. How about using DOM transformer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oke, we will use new transformer for auto link

…plain text

Signed-off-by: dab246 <tdvu@linagora.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart (1)

26-37: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid returning raw input when sanitization fails.

If linkify() throws, this returns unsanitized inputText, which can reintroduce raw HTML/script payloads in a failure path. Return an escaped fallback instead.

🔧 Proposed fix
   String process(String inputText) {
     try {
       if (inputText.isEmpty) return '';

       final result = _linkifyText(inputText);

       log('SanitizeAutolinkFilter::process:htmlTextBuffer = $result');
       return result;
-    } catch (e) {
+    } catch (e) {
       logWarning('$runtimeType::process:Exception = $e');
-      return inputText;
+      return htmlEscape.convert(inputText);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart`
around lines 26 - 37, process currently returns the raw unsanitized inputText on
exceptions from _linkifyText, which can reintroduce unsafe HTML; change the
catch in SanitizeAutolinkFilter::process to return an escaped fallback (e.g.,
run inputText through the project's HTML-escape utility or encoder) instead of
inputText, and keep or augment the logWarning('$runtimeType::process:Exception =
$e') so the error is recorded while ensuring the returned string is safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart`:
- Around line 30-33: The current SanitizeAutolinkFilter::process implementation
logs the full transformed content stored in result (produced by _linkifyText),
which can include sensitive user data; change the logging to avoid emitting the
payload — either remove the log or replace it with non-sensitive metadata such
as result.length or a boolean (e.g., isEmpty) and, if detailed content is
needed, gate it behind a diagnostic/debug flag that is off in production. Ensure
the log message no longer includes htmlTextBuffer or result contents but
provides minimal context (e.g., "SanitizeAutolinkFilter::process: length=...")
and keep _linkifyText usage unchanged.

In `@core/test/utils/autolink_text_node_transformer_test.dart`:
- Around line 581-588: The test currently only asserts that
AutolinkTextNodeTransformer exists; update it to also assert ordering by
importing SanitizeHyperLinkTagInHtmlTransformer, find its index (e.g.,
sanitizeIdx = transformers.indexWhere((t) => t is
SanitizeHyperLinkTagInHtmlTransformer)), and add an expectation that autolinkIdx
is lessThan(sanitizeIdx) so AutolinkTextNodeTransformer comes before
SanitizeHyperLinkTagInHtmlTransformer in
TransformConfiguration.forCalendarEvent().domTransformers.

---

Outside diff comments:
In `@core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart`:
- Around line 26-37: process currently returns the raw unsanitized inputText on
exceptions from _linkifyText, which can reintroduce unsafe HTML; change the
catch in SanitizeAutolinkFilter::process to return an escaped fallback (e.g.,
run inputText through the project's HTML-escape utility or encoder) instead of
inputText, and keep or augment the logWarning('$runtimeType::process:Exception =
$e') so the error is recorded while ensuring the returned string is safe.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e8ec236c-d9b5-436d-a4ca-9b702b967d8d

📥 Commits

Reviewing files that changed from the base of the PR and between 17033b3 and 2a08d81.

📒 Files selected for processing (6)
  • core/lib/presentation/utils/html_transformer/dom/autolink_text_node_transformer.dart
  • core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart
  • core/lib/presentation/utils/html_transformer/transform_configuration.dart
  • core/test/sanitize_autolink_filter_test.dart
  • core/test/utils/autolink_text_node_transformer_test.dart
  • test/features/email/data/local/calendar_event_description_transform_test.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/lib/presentation/utils/html_transformer/transform_configuration.dart

Comment thread core/test/utils/autolink_text_node_transformer_test.dart
…red as plain text

Signed-off-by: dab246 <tdvu@linagora.com>
Copy link
Copy Markdown

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@dab246 dab246 requested a review from hoangdat May 28, 2026 04:04
@hoangdat hoangdat merged commit 6b9dd53 into master May 28, 2026
18 checks passed
@hoangdat hoangdat deleted the fix/calendar-event-html-description-rendered-as-plain-text branch May 28, 2026 08:27
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.

3 participants