fixup! TF-4224 Reduce ReDoS vulnerability
This commit is contained in:
+8
-6
@@ -8,6 +8,12 @@ class NormalizeLineHeightInStyleTransformer extends DomTransformer {
|
|||||||
|
|
||||||
static const _removableLineHeights = ['1px', '100%'];
|
static const _removableLineHeights = ['1px', '100%'];
|
||||||
static final _multipleSpacesRegex = RegExp(r' {2,}');
|
static final _multipleSpacesRegex = RegExp(r' {2,}');
|
||||||
|
static final _lineHeightPattern = RegExp(
|
||||||
|
r'line-height\s*:\s*(?:' +
|
||||||
|
_removableLineHeights.map(RegExp.escape).join('|') +
|
||||||
|
r')\s*;?',
|
||||||
|
caseSensitive: false,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> process({
|
Future<void> process({
|
||||||
@@ -16,11 +22,7 @@ class NormalizeLineHeightInStyleTransformer extends DomTransformer {
|
|||||||
Map<String, String>? mapUrlDownloadCID,
|
Map<String, String>? mapUrlDownloadCID,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final alternation = _removableLineHeights.map(RegExp.escape).join('|');
|
|
||||||
final pattern = RegExp(
|
|
||||||
r'line-height\s*:\s*(?:' + alternation + r')\s*;?',
|
|
||||||
caseSensitive: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
final elements = document.querySelectorAll('[style*="line-height"]');
|
final elements = document.querySelectorAll('[style*="line-height"]');
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ class NormalizeLineHeightInStyleTransformer extends DomTransformer {
|
|||||||
final originalStyle = element.attributes['style'];
|
final originalStyle = element.attributes['style'];
|
||||||
if (originalStyle == null) continue;
|
if (originalStyle == null) continue;
|
||||||
|
|
||||||
var updatedStyle = originalStyle.replaceAll(pattern, '').trim();
|
var updatedStyle = originalStyle.replaceAll(_lineHeightPattern, '').trim();
|
||||||
|
|
||||||
// Remove extra spaces (>=2 spaces → 1 space)
|
// Remove extra spaces (>=2 spaces → 1 space)
|
||||||
updatedStyle = updatedStyle.replaceAll(_multipleSpacesRegex, ' ');
|
updatedStyle = updatedStyle.replaceAll(_multipleSpacesRegex, ' ');
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ class HtmlUtils {
|
|||||||
multiLine: true,
|
multiLine: true,
|
||||||
);
|
);
|
||||||
static final _protocolRegex = RegExp(r'^(?:https?|ftp|mailto|file)');
|
static final _protocolRegex = RegExp(r'^(?:https?|ftp|mailto|file)');
|
||||||
|
static final _tagRemovalRegex = RegExp(
|
||||||
|
'</?(?:${validTags.map(RegExp.escape).join('|')})(?:\\s+[^>]*)?>',
|
||||||
|
caseSensitive: false,
|
||||||
|
);
|
||||||
|
|
||||||
static const removeLineHeight1px = (
|
static const removeLineHeight1px = (
|
||||||
script: '''
|
script: '''
|
||||||
@@ -861,12 +865,7 @@ class HtmlUtils {
|
|||||||
} while (decoded != cleaned && iterations < maxIterations);
|
} while (decoded != cleaned && iterations < maxIterations);
|
||||||
|
|
||||||
// Delete all remaining HTML tags → replace tag with space to avoid text sticking
|
// Delete all remaining HTML tags → replace tag with space to avoid text sticking
|
||||||
final String tagsPipe = validTags.map(RegExp.escape).join('|');
|
cleaned = cleaned.replaceAll(_tagRemovalRegex, ' ');
|
||||||
final tagRegex = RegExp(
|
|
||||||
'</?(?:$tagsPipe)(?:\\s+[^>]*)?>',
|
|
||||||
caseSensitive: false,
|
|
||||||
);
|
|
||||||
cleaned = cleaned.replaceAll(tagRegex, ' ');
|
|
||||||
|
|
||||||
// Normalize whitespace
|
// Normalize whitespace
|
||||||
cleaned = cleaned.replaceAll(_whitespaceNormalizationRegex, ' ').trim();
|
cleaned = cleaned.replaceAll(_whitespaceNormalizationRegex, ' ').trim();
|
||||||
|
|||||||
@@ -815,6 +815,32 @@ void main() {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
test('Does not corrupt plain text that looks like base64', () {
|
||||||
|
// "useruser" is 8 chars (multiple of 4) and fits the base64 regex.
|
||||||
|
// base64.decode("useruser") produces random bytes.
|
||||||
|
const input = 'useruser';
|
||||||
|
final result = StringConvert.extractNamedAddresses(input);
|
||||||
|
expect(result.first.address, equals('useruser'));
|
||||||
|
});
|
||||||
|
test('Handles brackets with no name', () {
|
||||||
|
const input = '<only-email@example.com>';
|
||||||
|
final result = StringConvert.extractNamedAddresses(input);
|
||||||
|
expect(result.first.name, isEmpty);
|
||||||
|
expect(result.first.address, equals('only-email@example.com'));
|
||||||
|
});
|
||||||
|
test('Handles names containing commas', () {
|
||||||
|
const input =
|
||||||
|
'"Doe, John" <john@example.com>, "Smith, Jane" <jane@example.com>';
|
||||||
|
final result = StringConvert.extractNamedAddresses(input);
|
||||||
|
expect(result[0].name, equals('Doe, John'));
|
||||||
|
expect(result[1].name, equals('Smith, Jane'));
|
||||||
|
});
|
||||||
|
test('Handles malformed input gracefully', () {
|
||||||
|
const input = '"Unclosed Quote <email@example.com>';
|
||||||
|
final result = StringConvert.extractNamedAddresses(input);
|
||||||
|
// Ensure it doesn't crash and returns at least the email
|
||||||
|
expect(result, isNotEmpty);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('StringConvert.convertHtmlContentToTextContent', () {
|
group('StringConvert.convertHtmlContentToTextContent', () {
|
||||||
|
|||||||
Reference in New Issue
Block a user