From b49d4f55b25d935fd59df61cd441bc5194c5eb7f Mon Sep 17 00:00:00 2001 From: smizrahi Date: Wed, 6 May 2026 15:31:03 +0200 Subject: [PATCH] =?UTF-8?q?fix(mail):=20Some=20links=20broken=20in=20HTML?= =?UTF-8?q?=20rendering=20due=20to=20libxml2=20(SOPE)=20only=20supporting?= =?UTF-8?q?=20HTML4=20=E2=80=94=20when=20an=20anchor=20has=20no=20content?= =?UTF-8?q?=20between=20its=20opening=20and=20closing=20tags,=20store=20it?= =?UTF-8?q?=20and=20re-open=20it=20around=20the=20next=20element,=20closin?= =?UTF-8?q?g=20=20after=20that=20element's=20end=20tag.=20Closes=20#59?= =?UTF-8?q?61.=20Closes=20#6172.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UI/MailPartViewers/UIxMailPartHTMLViewer.m | 96 +++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index 0b5e6c0b0..65f5ff397 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -130,6 +130,13 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content) BOOL hasEmbeddedCSS; xmlCharEncoding contentEncoding; BOOL rawContent; + // Wrap libxml2-orphaned around the following block element + // (HTML5 pattern ...
auto-closed by libxml2 HTML4 DTD). + NSString *pendingAnchorTag; + NSString *anchorWrapTag; + NSUInteger anchorWrapDepth; + NSString *lastAnchorOpenString; + NSUInteger lastAnchorOpenEnd; } - (NSString *) result; @@ -175,6 +182,9 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content) [result release]; [css release]; [ignoreTag release]; + [pendingAnchorTag release]; + [anchorWrapTag release]; + [lastAnchorOpenString release]; [super dealloc]; } @@ -228,6 +238,15 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content) inCSSDeclaration = NO; hasEmbeddedCSS = NO; embeddedCSSLevel = 0; + + [pendingAnchorTag release]; + [anchorWrapTag release]; + [lastAnchorOpenString release]; + pendingAnchorTag = nil; + anchorWrapTag = nil; + lastAnchorOpenString = nil; + anchorWrapDepth = 0; + lastAnchorOpenEnd = 0; } - (void) endDocument @@ -494,6 +513,32 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content) } else { + // If a libxml2-orphaned is waiting, re-open it around the next + // block element (HTML5 pattern ...
). + // Skip if the next element is itself (avoids invalid nesting). + if (pendingAnchorTag != nil) + { + if ([lowerName isEqualToString: @"a"]) + { + [pendingAnchorTag release]; + pendingAnchorTag = nil; + } + else + { + [result appendString: pendingAnchorTag]; + [anchorWrapTag release]; + anchorWrapTag = [lowerName copy]; + anchorWrapDepth = 1; + [pendingAnchorTag release]; + pendingAnchorTag = nil; + } + } + else if (anchorWrapTag != nil + && [lowerName isEqualToString: anchorWrapTag]) + { + anchorWrapDepth++; + } + resultPart = [NSMutableString string]; [resultPart appendFormat: @"<%@", _rawName]; @@ -599,6 +644,13 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content) [resultPart appendString: @"/"]; [resultPart appendString: @">"]; [result appendString: resultPart]; + + if ([lowerName isEqualToString: @"a"]) + { + [lastAnchorOpenString release]; + lastAnchorOpenString = [resultPart copy]; + lastAnchorOpenEnd = [result length]; + } } } } @@ -655,8 +707,42 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content) } else { + // Empty auto-closed by libxml2 right after opening: + // strip it from result and remember it to wrap the next block. + if ([lowerName isEqualToString: @"a"] + && lastAnchorOpenString != nil + && [result length] == lastAnchorOpenEnd) + { + NSUInteger tagLen = [lastAnchorOpenString length]; + [result deleteCharactersInRange: + NSMakeRange(lastAnchorOpenEnd - tagLen, tagLen)]; + [pendingAnchorTag release]; + pendingAnchorTag = lastAnchorOpenString; + lastAnchorOpenString = nil; + lastAnchorOpenEnd = 0; + return; + } + if ([lowerName isEqualToString: @"a"]) + { + [lastAnchorOpenString release]; + lastAnchorOpenString = nil; + lastAnchorOpenEnd = 0; + } + //NSLog (@"%@", _localName); [result appendFormat: @"", _localName]; + + if (anchorWrapTag != nil + && [lowerName isEqualToString: anchorWrapTag]) + { + anchorWrapDepth--; + if (anchorWrapDepth == 0) + { + [result appendString: @""]; + [anchorWrapTag release]; + anchorWrapTag = nil; + } + } } } } @@ -673,9 +759,17 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content) else if (inBody) { NSString *s; - + s = [NSString stringWithCharacters: _chars length: _len]; + if (pendingAnchorTag != nil + && [[s stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0) + { + [pendingAnchorTag release]; + pendingAnchorTag = nil; + } + // HACK: This is to avoid appending the useless junk in the tag // that Outlook adds. It seems to confuse the XML parser for // forwarded messages as we get this in the _body_ of the email