From a9cfe86a59709d0b93f1c9bd9e62c4a12bf75597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Poizat?= Date: Mon, 15 Dec 2025 08:22:16 +0100 Subject: [PATCH] Use html parser instead of regex to clean composer input Before sending input to the LLM, we clean the composer input to remove html tags. We relied on regex that we not complete enough and could be bypassed easily. So let's use the html parser available in Dart instead. I also added some tests. --- core/lib/utils/string_convert.dart | 31 ++++++++++++++++++------ core/test/utils/string_convert_test.dart | 16 ++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/core/lib/utils/string_convert.dart b/core/lib/utils/string_convert.dart index 018e27692..595ef1958 100644 --- a/core/lib/utils/string_convert.dart +++ b/core/lib/utils/string_convert.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:core/utils/app_logger.dart'; import 'package:core/domain/exceptions/string_exception.dart'; import 'package:core/utils/mail/named_address.dart'; +import 'package:html/dom.dart'; import 'package:html/parser.dart'; import 'package:http_parser/http_parser.dart'; @@ -198,15 +199,29 @@ class StringConvert { } static String convertHtmlContentToTextContent(String htmlContent) { - String textContent = htmlContent.replaceAll(RegExp(r'<[^>]*>'), ''); + final document = parse(htmlContent); - textContent = textContent - .replaceAll(' ', ' ') - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"') - .replaceAll(''', "'"); + // Each paragraph is surrounded by block tags so we add a /n for each block tag + // Even
are surrounded by block tags so we can ignore
and treat them + // as paragraph + const blockTags = [ + 'p', + 'div', + 'li', + 'section', + 'article', + 'header', + 'footer', + 'h1','h2','h3','h4','h5','h6' + ]; + + for (final tag in blockTags) { + document.querySelectorAll(tag).forEach((element) { + element.append(Text('\n')); + }); + } + + final String textContent = document.body?.text ?? ''; return textContent.trim(); } diff --git a/core/test/utils/string_convert_test.dart b/core/test/utils/string_convert_test.dart index 0141cd32a..b72ab12db 100644 --- a/core/test/utils/string_convert_test.dart +++ b/core/test/utils/string_convert_test.dart @@ -816,4 +816,20 @@ void main() { ); }); }); + + group('StringConvert.convertHtmlContentToTextContent', () { + test('should preserve line breaks between paragraphs', () { + const htmlContent = '

First paragraph

Second paragraph

Third paragraph

'; + final result = StringConvert.convertHtmlContentToTextContent(htmlContent); + + expect(result, 'First paragraph\nSecond paragraph\nThird paragraph'); + }); + + test('should preserve line breaks for br', () { + const htmlContent = '

First paragraph


Second paragraph

'; + final result = StringConvert.convertHtmlContentToTextContent(htmlContent); + + expect(result, 'First paragraph\n\nSecond paragraph'); + }); + }); }