diff --git a/core/test/utils/redos_vulnerability_test.dart b/core/test/utils/redos_vulnerability_test.dart index 5be4bed6f..e8f4f14a0 100644 --- a/core/test/utils/redos_vulnerability_test.dart +++ b/core/test/utils/redos_vulnerability_test.dart @@ -174,6 +174,134 @@ void main() { expect(stopwatch.elapsedMilliseconds, lessThan(500)); expect(result, contains('content')); }); + + test('Should handle Gmail newsletter HTML structure', () { + // Real pattern from Gmail promotional emails + const gmailHtml = ''' +
+ + + + +
+

Hello User,

+

Visit https://example.com/products for our latest offers.

+
+
+ '''; + + final stopwatch = Stopwatch()..start(); + + // Extract plain text + final plainText = HtmlUtils.extractPlainText(gmailHtml); + + // Wrap URLs + final linkedHtml = HtmlUtils.wrapPlainTextLinks(gmailHtml); + + stopwatch.stop(); + + // Performance check + expect(stopwatch.elapsedMilliseconds, lessThan(200), + reason: 'Gmail HTML processing should be fast'); + + // Functional check + expect(plainText, contains('Hello User')); + expect(plainText, contains('Visit')); + expect(linkedHtml, contains(''; + + // 129 characters (over limit, should still process fast) + final over128 = ''; + + // Very long (1000 chars - malicious) + final malicious = ''; + + final stopwatch1 = Stopwatch()..start(); + HtmlUtils.addQuoteToggle('Text $exactly128 end'); + stopwatch1.stop(); + + final stopwatch2 = Stopwatch()..start(); + HtmlUtils.addQuoteToggle('Text $over128 end'); + stopwatch2.stop(); + + final stopwatch3 = Stopwatch()..start(); + HtmlUtils.addQuoteToggle('Text $malicious end'); + stopwatch3.stop(); + + // All should complete quickly (bounded quantifier prevents backtracking) + expect(stopwatch1.elapsedMilliseconds, lessThan(100), + reason: '128 chars (boundary) should be fast'); + expect(stopwatch2.elapsedMilliseconds, lessThan(100), + reason: '129 chars (over boundary) should be fast'); + expect(stopwatch3.elapsedMilliseconds, lessThan(100), + reason: '1000 chars (malicious) should be fast'); + }); + + test('Should handle deeply nested HTML structure', () { + // 50 levels (realistic maximum) + final deepNesting = '${'
' * 50}

Deep content with https://example.com link

${'
' * 50}'; + + // 100 levels (extreme) + final extremeNesting = '${'' * 100}Content${'' * 100}'; + + final stopwatch1 = Stopwatch()..start(); + final plainText1 = HtmlUtils.extractPlainText(deepNesting); + final linked1 = HtmlUtils.wrapPlainTextLinks(deepNesting); + stopwatch1.stop(); + + final stopwatch2 = Stopwatch()..start(); + final plainText2 = HtmlUtils.extractPlainText(extremeNesting); + stopwatch2.stop(); + + // Performance checks + expect(stopwatch1.elapsedMilliseconds, lessThan(200), + reason: '50 levels of nesting should process quickly'); + expect(stopwatch2.elapsedMilliseconds, lessThan(300), + reason: '100 levels of nesting should process quickly'); + + // Functional checks + expect(plainText1, contains('Deep content')); + expect(linked1, contains('