fix(mail): sanitize the domain part of generated message-ids

generateMessageID: appended the substring following the last '@'
without sanitizing it, so a sender value such as 'Doe, John <a@b>'
produced 'Message-Id: <uuid@b>>' with a duplicated closing bracket on
calendar invitations. A nil domain also left the message-id
unterminated.

The domain part is now cut at the first address delimiter or
whitespace - a domain is a single token, and anything else would
allow header injection through generated message-ids.

Fixes #6201
This commit is contained in:
Hadrien Blanc authored and QHivert committed 2026-10-07 11:13:33 +02:00
1 parent 5e627db80d
commit 20779cf353
3 files changed
+57 -4

No files matched your search

+9 -3
View File
@@ -564,7 +564,7 @@
{
NSMutableString *messageID;
NSString *_domain;
NSRange r;
NSRange r, cutRange;
messageID = [NSMutableString string];
[messageID appendFormat: @"<%@", [SOGoObject mailUniqueMessageId]];
@@ -578,9 +578,15 @@
}
else
_domain = mailOrDomain;
[messageID appendFormat: @"@%@>", _domain];
_domain = [[_domain componentsSeparatedByString: @">"] objectAtIndex: 0];
cutRange = [_domain rangeOfCharacterFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (cutRange.location != NSNotFound)
_domain = [_domain substringToIndex: cutRange.location];
if ([_domain length] > 0)
[messageID appendFormat: @"@%@", _domain];
}
[messageID appendString: @">"];
return [messageID lowercaseString];
}
+3 -1
View File
@@ -14,6 +14,7 @@ $(TEST_TOOL)_OBJC_FILES += \
SOGoTest.m \
SOGoTestRunner.m \
SaxXMLReaderFactory+SOGoTests.m \
../../SoObjects/Mailer/NSString+Mail.m \
\
TestVersit.m \
TestiCalTimeZonePeriod.m \
@@ -31,6 +32,7 @@ $(TEST_TOOL)_OBJC_FILES += \
TestNSString+Crypto.m \
TestNSString+URLEscaping.m \
TestNSString+Utilities.m \
TestNSString+Mail.m \
TestNSURL+misc.m \
TestNGMailAddressParser.m \
TestNGInternetSocketAddress.m \
@@ -43,7 +45,7 @@ $(TEST_TOOL)_OBJC_FILES += \
TEST_TOOL_NAME = $(TEST_TOOL)
$(TEST_TOOL)_CPPFLAGS += \
-Wall -D_GNU_SOURCE -I../../SOPE/ -I../../SoObjects/ -I../../UI/ -I../../OpenChange
-Wall -D_GNU_SOURCE -I../../SOPE/ -I../../SoObjects/ -I../../UI/ -I../../OpenChange `xml2-config --cflags`
ADDITIONAL_LIB_DIRS += \
-L../../SoObjects/SOGo/SOGo.framework/Versions/Current/sogo -L../../SOPE/NGCards/obj -L../../SOPE/GDLContentStore/obj -lSOGo -lNGMime -lNGCards -lGDLContentStore -lNGExtensions -lSBJson -lobjc \
+45
View File
@@ -0,0 +1,45 @@
#import "SOGoTest.h"
#import "Mailer/NSString+Mail.h"
static NSString *
MessageIDShape(NSString *mailOrDomain)
{
NSString *messageID = [NSString generateMessageID: mailOrDomain];
return [@"<UUID" stringByAppendingString: [messageID substringFromIndex: 37]];
}
@interface TestNSString_plus_Mail : SOGoTest
@end
@implementation TestNSString_plus_Mail
- (void) test_generateMessageID_fromAddressOrDomain
{
testEquals(MessageIDShape(@"user@example.org"), @"<UUID@example.org>");
testEquals(MessageIDShape(@"Example.ORG"), @"<UUID@example.org>");
}
- (void) test_generateMessageID_fromSenderWithDisplayName
{
testEquals(MessageIDShape(@"Doe, John <user@example.org>"), @"<UUID@example.org>");
testEquals(MessageIDShape(@"Doe, John <user@example.org> (work)"), @"<UUID@example.org>");
testEquals(MessageIDShape(@"user@example.org>"), @"<UUID@example.org>");
}
- (void) test_generateMessageID_rejectsInjectedDomains
{
testEquals(MessageIDShape(@"user@example.org\r\nBcc: victim"),
@"<UUID@example.org>");
testEquals(MessageIDShape(@"user@example.org Bcc: victim"),
@"<UUID@example.org>");
}
- (void) test_generateMessageID_withoutDomain
{
testEquals(MessageIDShape(@""), @"<UUID>");
testEquals(MessageIDShape(nil), @"<UUID>");
}
@end