Merge pull request #395 from kah-ja/fix/imip-card-template-injection

fix(mail): stop interpreting mail data in compiled part viewers
This commit is contained in:
WoodySlum
2026-08-27 11:04:41 +02:00
committed by GitHub
9 changed files with 55 additions and 17 deletions
+6 -5
View File
@@ -942,12 +942,13 @@ static int cssEscapingCount;
NSScanner *theScanner;
NSError *error;
NSUInteger numberOfMatches;
NSRegularExpression *regex;
NSRegularExpression *regex, *importRegex;
text = nil;
error = nil;
result = [NSString stringWithString: self];
regex = nil;
importRegex = nil;
NS_DURING
{
@@ -1056,9 +1057,9 @@ static int cssEscapingCount;
result = [NSString stringWithString: newResult];
// Remove @import css (in style tags)
regex = [NSRegularExpression regularExpressionWithPattern:@"(<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e.*)([\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*@[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*m[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*o[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t)(.*<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*\\/[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*>)"
importRegex = [NSRegularExpression regularExpressionWithPattern:@"(<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e.*)([\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*@[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*i[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*m[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*p[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*o[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t)(.*<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*\\/[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*s[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*t[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*y[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*l[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*e[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*>)"
options: NSRegularExpressionCaseInsensitive error:&error];
newResult = [regex stringByReplacingMatchesInString:result options:0 range:NSMakeRange(0, [result length]) withTemplate:@"onrep***="];
newResult = [importRegex stringByReplacingMatchesInString:result options:0 range:NSMakeRange(0, [result length]) withTemplate:@"$1@im****$3"];
result = [NSString stringWithString: newResult];
@@ -1075,8 +1076,8 @@ static int cssEscapingCount;
}
newResult = result;
while([regex numberOfMatchesInString:newResult options:0 range:NSMakeRange(0, [newResult length])] > 0) {
newResult = [regex stringByReplacingMatchesInString:newResult options:0 range:NSMakeRange(0, [newResult length]) withTemplate:@"$1@im****$3"];
while([importRegex numberOfMatchesInString:newResult options:0 range:NSMakeRange(0, [newResult length])] > 0) {
newResult = [importRegex stringByReplacingMatchesInString:newResult options:0 range:NSMakeRange(0, [newResult length]) withTemplate:@"$1@im****$3"];
}
result = [NSString stringWithString: newResult];
}
+2
View File
@@ -110,6 +110,8 @@
testEquals([[NSString stringWithString:@"foobar <img onload=foo bar"] stringWithoutHTMLInjection: NO stripAngular: NO], @"foobar <img onl***=foo bar");
testEquals([[NSString stringWithString:@"foobar <img onmouseover=foo bar"] stringWithoutHTMLInjection: NO stripAngular: NO], @"foobar <img onmouseo***=foo bar");
testEquals([[NSString stringWithString:@"<!DOCTYPE html><html><head><style>@import url(https://foo.bar/malicious.css);.foo{background-color: red; @import url(https://bar.foo/malicious2.css);</style></head><body><table><tr><td>A</td><td>B</td><td>C</td></tr></table></body></html>"] stringWithoutHTMLInjection: NO stripAngular: NO], @"<!DOCTYPE html><html><head><style>@im**** url(https://foo.bar/malicious.css);.foo{background-color: red; @im**** url(https://bar.foo/malicious2.css);</style></head><body><table><tr><td>A</td><td>B</td><td>C</td></tr></table></body></html>");
// the @import cleanup must still run when angular interpolation is stripped as well
testEquals([[NSString stringWithString:@"<style>@import url(https://foo.bar/malicious.css);</style>"] stringWithoutHTMLInjection: NO stripAngular: YES], @"<style>@im**** url(https://foo.bar/malicious.css);</style>");
}
- (void) test_stringCleanInvalidHTMLTags
@@ -46,6 +46,9 @@
- (BOOL) isEndDateOnSameDay;
- (BOOL) hasLocation;
- (NSString *)location;
- (NSString *) organizerHref;
- (NSString *) userComment;
- (NSString *) eventDescription;
@end
@@ -32,6 +32,7 @@
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <NGImap4/NGImap4EnvelopeAddress.h>
@@ -417,6 +418,18 @@
return YES;
}
- (NSString *) organizerHref
{
NSString *address;
address = [[[self inEvent] organizer] rfc822Email];
if (![address length])
return nil;
return [[NSString stringWithFormat: @"mailto:%@", address]
stringByEscapingHTMLAttributeValue];
}
- (NSString *) organizerDisplayName
{
iCalPerson *organizer;
@@ -612,4 +625,14 @@
return [[self inEvent] location];
}
- (NSString *) userComment
{
return [[[self inEvent] userComment] stringByEscapingHTMLString];
}
- (NSString *) eventDescription
{
return [[[self authorativeEvent] comment] stringByEscapingHTMLString];
}
@end /* UIxMailPartICalViewer */
+1
View File
@@ -86,6 +86,7 @@
- (NSString *)preferredPathExtension;
- (NSString *)filename;
- (NSString *)filenameForDisplay;
- (NSString *)filenameForTitle;
- (NSFormatter *)sizeFormatter;
/* caches */
+8
View File
@@ -340,6 +340,14 @@
: (id)@"untitled";
}
- (NSString *) filenameForTitle
{
/* the generic attribute writer escapes & but not the quote, so a quote in the
filename would end the title attribute and start a new one */
return [[self filenameForDisplay] stringByReplacingOccurrencesOfString: @"\""
withString: @""];
}
- (NSFormatter *) sizeFormatter
{
return [UIxMailSizeFormatter sharedMailSizeFormatter];
@@ -17,7 +17,7 @@
</div>
</md-toolbar>
<md-whiteframe class="md-whiteframe-z1" layout="row" layout-align="space-between center">
<pre><var:string value="flatContentAsString" /></pre>
<pre ng-non-bindable=""><var:string value="flatContentAsString" /></pre>
</md-whiteframe>
</div>
</var:if> <!-- if condition="couldParseCalendar" const:negate="1" -->
@@ -137,7 +137,7 @@
<p>
<var:string label:value="Organizer" />
<a var:href="inEvent.organizer.email"
<a ng-non-bindable="" var:href="organizerHref"
><var:string value="organizerDisplayName" /></a>
<var:string label:value="request_info" />
</p>
@@ -147,7 +147,7 @@
<var:if condition="isLoggedInUserAnAttendee" const:negate="YES">
<p>
<var:string label:value="Organizer" />
<a var:href="inEvent.organizer.email">
<a ng-non-bindable="" var:href="organizerHref">
<var:string value="organizerDisplayName" />
</a>
<var:string label:value="request_info_no_attendee" />
@@ -233,14 +233,14 @@
</var:if>
<!-- the user comment is used in replies -->
<var:if condition="inEvent.userComment.isNotEmpty">
<var:if condition="userComment.isNotEmpty">
<div class="pseudo-input-container">
<label class="pseudo-input-label">
<var:string label:value="Comment"/>
</label>
<div>
<div ng-non-bindable="">
<md-content>
<var:string value="inEvent.userComment.stringByDetectingURLs" const:insertBR="1" const:escapeHTML="NO"/>
<var:string value="userComment.stringByDetectingURLs" const:insertBR="1" const:escapeHTML="NO"/>
</md-content>
</div>
</div>
@@ -251,7 +251,7 @@
<var:if condition="hasOrganizer" const:value="true">
<div class="pseudo-input-container">
<label class="pseudo-input-label"><var:string label:value="Organizer"/></label>
<div>
<div ng-non-bindable="">
<var:string value="organizerDisplayName"/>
</div>
</div>
@@ -268,7 +268,7 @@
<var:if condition="hasLocation">
<div class="pseudo-input-container">
<label class="pseudo-input-label"><var:string label:value="Location"/></label>
<div>
<div ng-non-bindable="">
<var:string value="location"/>
</div>
</div>
@@ -294,14 +294,14 @@
</div>
</md-list>
<var:if condition="authorativeEvent.comment.isNotEmpty">
<var:if condition="eventDescription.isNotEmpty">
<div class="pseudo-input-container">
<label class="pseudo-input-label">
<var:string label:value="Description"/>
</label>
<div ng-non-bindable="">
<md-content>
<var:string value="authorativeEvent.comment.stringByDetectingURLs" const:insertBR="1" const:escapeHTML="NO"/>
<var:string value="eventDescription.stringByDetectingURLs" const:insertBR="1" const:escapeHTML="NO"/>
</md-content>
</div>
</div>
@@ -10,7 +10,7 @@
<img var:src="pathToAttachment"
var:title="filenameForDisplay"><!-- image --></img>
<md-card-content>
<p class="md-caption" var:title="filenameForDisplay">
<p class="md-caption" var:title="filenameForTitle">
<var:string value="filenameForDisplay" />
</p>
</md-card-content>
@@ -6,7 +6,7 @@
xmlns:label="OGo:label">
<md-card>
<md-card-content>
<p class="md-caption sg-attachment-name" var:title="filenameForDisplay">
<p class="md-caption sg-attachment-name" var:title="filenameForTitle">
<var:if condition="preferredPathExtension.length"><span class="sg-label-outline"><var:string value="preferredPathExtension"/></span></var:if>
<var:string value="filenameForDisplay"/>
</p>