From be3630d009600a69ba012e2d3fc6fe727445de7b Mon Sep 17 00:00:00 2001 From: smizrahi Date: Fri, 2 Oct 2026 17:16:13 +0200 Subject: [PATCH] fix(mail): Keep content of Outlook downlevel-revealed conditional comments. Fixes #6167. --- SoObjects/SOGo/NSString+Utilities.m | 75 ++++++++++++++++++++++++++--- Tests/Unit/TestNSString+Utilities.m | 10 +++- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 9a835676b..5cbc74d97 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -1089,14 +1089,75 @@ static int cssEscapingCount; } - (NSString *) cleanInvalidHTMLTags { - // Clean HTML invalid tags as reported in https://bugs.sogo.nu/view.php?id=5755 - NSString *s; - - s = [NSString stringWithString: self]; - s = [s stringByReplacingOccurrencesOfString:@"" withString:@""]; - s = [s stringByReplacingOccurrencesOfString:@" or ) as an + // unterminated comment and drops the rest of the document, so remove it. + // Only do it outside of comments: the Outlook "downlevel-revealed" pattern + // ... ends its first comment with + // "", and removing it would hide the content in between. + NSString *result; + unichar *in, *out; + NSUInteger len, i, j, o; - return s; + len = [self length]; + if (len == 0) + return [NSString string]; + + // Work on raw buffers: linear time, bounded memory (no per-comment objects) + in = NSZoneMalloc (NULL, len * sizeof (unichar)); + out = NSZoneMalloc (NULL, len * sizeof (unichar)); + [self getCharacters: in]; + i = o = 0; + + while (i < len) + { + if (i + 3 < len + && in[i] == '<' && in[i+1] == '!' && in[i+2] == '-' && in[i+3] == '-') + { + j = i + 4; + if (j < len && in[j] == '>') + { + // + i = j + 1; + continue; + } + if (j + 1 < len && in[j] == '-' && in[j+1] == '>') + { + // + i = j + 2; + continue; + } + + // Regular comment: keep it untouched up to its end ("-->" or "--!>", + // as libxml2 accepts both), or up to the end if unterminated + while (j < len) + { + if (j + 2 < len && in[j] == '-' && in[j+1] == '-' && in[j+2] == '>') + { + j += 3; + break; + } + if (j + 3 < len && in[j] == '-' && in[j+1] == '-' + && in[j+2] == '!' && in[j+3] == '>') + { + j += 4; + break; + } + j++; + } + memcpy (out + o, in + i, (j - i) * sizeof (unichar)); + o += j - i; + i = j; + } + else + out[o++] = in[i++]; + } + + result = [NSString stringWithCharacters: out length: o]; + NSZoneFree (NULL, in); + NSZoneFree (NULL, out); + + return result; } @end diff --git a/Tests/Unit/TestNSString+Utilities.m b/Tests/Unit/TestNSString+Utilities.m index b2ed4e7fd..8918bc440 100644 --- a/Tests/Unit/TestNSString+Utilities.m +++ b/Tests/Unit/TestNSString+Utilities.m @@ -127,7 +127,15 @@ - (void) test_stringCleanInvalidHTMLTags { testEquals([[NSString stringWithString:@"
Test
"] cleanInvalidHTMLTags], @"
Test
"); - testEquals([[NSString stringWithString:@"
"] cleanInvalidHTMLTags], @"
"); + testEquals([[NSString stringWithString:@"
Test
"] cleanInvalidHTMLTags], @"
Test
"); + testEquals([[NSString stringWithString:@"
ABCD
"] cleanInvalidHTMLTags], @"
ABCD
"); + testEquals([[NSString stringWithString:@"
"] cleanInvalidHTMLTags], @"
"); + // Outlook "downlevel-revealed" conditional comment: content must stay visible + testEquals([[NSString stringWithString:@"
"] cleanInvalidHTMLTags], @"
"); + // libxml2 also ends a comment with "--!>" + testEquals([[NSString stringWithString:@"ABC"] cleanInvalidHTMLTags], @"ABC"); + // unterminated comment is left as is + testEquals([[NSString stringWithString:@"
A