Skip to content

Commit

Permalink
Introduce new xml parser tests for various conversion commands
Browse files Browse the repository at this point in the history
  • Loading branch information
tmolitor-stud-tu committed Feb 10, 2025
1 parent 5718be9 commit 60b5bf1
Showing 1 changed file with 247 additions and 13 deletions.
260 changes: 247 additions & 13 deletions Monal/MonalXMPPUnitTests/ParserTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
#import <monalxmpp/HelperTools.h>
#import "MLBasePaser.h"

NSString* _rawXML = @"<?xml version='1.0'?>\n\
static NSMutableArray<MLXMLNode*>* _parsedStanzas;
static NSString* _rawXML = @"<?xml version='1.0'?>\n\
<stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client' xml:lang='en' from='example.org' id='a344b8bb-518e-4456-9140-d15f66c1d2db'>\n\
\
<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>SCRAM-SHA-1</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>\n\
\
<message from='[email protected]' id='some_id' xmlns='jabber:client'>\n\
<body>Message text</body>\n\
<body xmlns='urn:some:different:namespace'>This will NOT be used</body>\n\
<some xmlns='urn:some:different:namespace' fin='true' hello='0' world='1' number='42' uuid='18382ACA-EF9D-4BC9-8779-7901C63B6631' id='18382ACA' when='2002-09-10T23:08:25Z'>aGVsbG8gd29ybGQh</some>\n\
</message>\n\
\
<iq id='18382ACA-EF9D-4BC9-8779-7901C63B6631' to='[email protected]/Monal-iOS.ef313600' xmlns='jabber:client' type='result' from='[email protected]'><query xmlns='http://jabber.org/protocol/disco#info'><feature var='http://jabber.org/protocol/muc#request'/><feature var='muc_hidden'/><feature var='muc_unsecured'/><feature var='muc_membersonly'/><feature var='muc_unmoderated'/><feature var='muc_persistent'/><identity type='text' name='testchat gruppe' category='conference'/><feature var='urn:xmpp:mam:2'/><feature var='urn:xmpp:sid:0'/><feature var='muc_nonanonymous'/><feature var='http://jabber.org/protocol/muc'/><feature var='http://jabber.org/protocol/muc#stable_id'/><feature var='http://jabber.org/protocol/muc#self-ping-optimization'/><feature var='jabber:iq:register'/><feature var='vcard-temp'/><x type='result' xmlns='jabber:x:data'><field type='hidden' var='FORM_TYPE'><value>http://jabber.org/protocol/muc#roominfo</value></field><field label='Description' var='muc#roominfo_description' type='text-single'><value/></field><field label='Number of occupants' var='muc#roominfo_occupants' type='text-single'><value>2</value></field><field label='Allow members to invite new members' var='{http://prosody.im/protocol/muc}roomconfig_allowmemberinvites' type='boolean'><value>0</value></field><field label='Allow users to invite other users' var='muc#roomconfig_allowinvites' type='boolean'><value>0</value></field><field label='Title' var='muc#roomconfig_roomname' type='text-single'><value>testchat gruppe</value></field><field type='boolean' var='muc#roomconfig_changesubject'/><field type='text-single' var='{http://modules.prosody.im/mod_vcard_muc}avatar#sha1'/><field type='text-single' var='muc#roominfo_lang'><value/></field></x></query></iq>\n\
Expand Down Expand Up @@ -65,6 +67,32 @@ @interface ParserTest : XCTestCase

@implementation ParserTest

//parse complete xml document once and collect parsed stanzas in _parsedStanzas to be processed by our individual tests later on
+(void) setUp
{
//yes, but this is not insecure because these are string literals boxed into an NSArray below rather than containing unchecked user input
//see here: https://releases.llvm.org/13.0.0/tools/clang/docs/DiagnosticsReference.html#wformat-security
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
_parsedStanzas = [NSMutableArray new];
MLBasePaser* delegate = [[MLBasePaser alloc] initWithCompletion:^(MLXMLNode* _Nullable parsedStanza) {
if(parsedStanza != nil)
{
DDLogInfo(@"Got new parsed stanza: %@", parsedStanza);
[_parsedStanzas addObject:parsedStanza];
}
}];
#pragma clang diagnostic pop

//create xml parser, configure our delegate and feed it with data
NSXMLParser* xmlParser = [[NSXMLParser alloc] initWithData:[_rawXML dataUsingEncoding:NSUTF8StringEncoding]];
[xmlParser setShouldProcessNamespaces:YES];
[xmlParser setShouldReportNamespacePrefixes:YES]; //for debugging only
[xmlParser setShouldResolveExternalEntities:NO];
[xmlParser setDelegate:delegate];
[xmlParser parse]; //blocking operation
}

-(void) setUp
{
// Put setup code here. This method is called before the invocation of each test method in the class.
Expand All @@ -75,14 +103,221 @@ -(void) tearDown
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

-(void) testParseConversionBase64
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some#|base64"];
if(i == 1)
XCTAssertEqualObjects(result, [@"hello world!" dataUsingEncoding:NSUTF8StringEncoding]);
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseConversionBoolean01
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@fin|bool"];
if(i == 1)
XCTAssertEqualObjects(result, @YES);
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseConversionBoolean02
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@hello|bool"];
if(i == 1)
XCTAssertEqualObjects(result, @NO);
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseConversionBoolean03
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@world|bool"];
if(i == 1)
XCTAssertEqualObjects(result, @YES);
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}


-(void) testParseConversionInt
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@number|int"];
if(i == 1)
XCTAssertEqualObjects(result, @42);
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseConversionUUID
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@uuid|uuid"];
if(i == 1)
XCTAssertEqualObjects([result UUIDString], @"18382ACA-EF9D-4BC9-8779-7901C63B6631");
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseConversionUUIDCast01
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@uuid|uuidcast"];
if(i == 1)
XCTAssertEqualObjects([result UUIDString], @"18382ACA-EF9D-4BC9-8779-7901C63B6631");
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseConversionUUIDCast02
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@id|uuidcast"];
if(i == 1)
XCTAssertEqualObjects([result UUIDString], @"43363852-14A2-D540-E8CE-6BEA040CD228");
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseConversionDatetime
{
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMddHHmmss"];
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
NSDate* expectedDate = [formatter dateFromString:@"20020910230825"];

for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"{urn:some:different:namespace}some@when|datetime"];
if(i == 1)
XCTAssertEqualObjects(result, expectedDate);
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseNoMatch
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//no stanza should match
id result = [_parsedStanzas[i] find:@"/{jabber:client}iq/{http://jabber.org/protocol/pubsub}pubsub/items<node~eu\\.siacs\\.conversations\\.axolotl\\.bundles:[0-9]+>@node"];
XCTAssertEqualObjects(result, @[], "no stanzas should match this");
}
}

-(void) testParseSaslMechanisms
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 0 should match
NSSet* result = [NSSet setWithArray:[_parsedStanzas[i] find:@"{urn:ietf:params:xml:ns:xmpp-sasl}mechanisms/mechanism#"]];
if(i == 0)
XCTAssertEqualObjects(result, ([NSSet setWithArray:@[@"SCRAM-SHA-1", @"PLAIN"]]), "wrong mechanisms extracted");
else
XCTAssertEqualObjects(result, [NSSet new], "all other stanzas should not match: %lu", i);
}
}

-(void) testParseMessageBody
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 1 should match
id result = [_parsedStanzas[i] findFirst:@"body#"];
if(i == 1)
XCTAssertEqualObjects(result, @"Message text");
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseDataFormsSubquery
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 2 should match
id result = [_parsedStanzas[i] findFirst:@"{http://jabber.org/protocol/disco#info}query/\\{http://jabber.org/protocol/muc#roominfo}result@muc#roomconfig_roomname\\"];
if(i == 2)
XCTAssertEqualObjects(result, @"testchat gruppe");
else
XCTAssertNil(result, "all other stanzas should not match: %lu", i);
}
}

-(void) testParseWithFormatParameters
{
for(unsigned long i=0; i<_parsedStanzas.count; i++)
{
//stanza 3 should match
NSArray* result = [_parsedStanzas[i] find:@"/<type=%@>/{http://jabber.org/protocol/pubsub}pubsub/subscription<node=%@><subscription=%s><jid=%@>", @"result", @"eu.siacs.conversations.axolotl.devicelist", "subscribed", @"[email protected]"];
if(i == 3)
{
XCTAssertEqual(result.count, 1, "we expect exactly one match");
XCTAssertEqualObjects([result[0] XMLString], @"<subscription node='eu.siacs.conversations.axolotl.devicelist' subid='6795F13596465' subscription='subscribed' jid='[email protected]'/>", "failed to properly extract and stringify MLXMLNode");
}
else
XCTAssertEqualObjects(result, @[], "all other stanzas should not match: %lu", i);
}
}

-(void) testParseCapsHash
{
//stanza 4 should match, no non-match handling here
unsigned long i = 4;

//gajim disco hash testcase
XCTAssertTrue([_parsedStanzas[i] check:@"/<id=disco1>"], "expected iq response having id 'disco1'");

//the the original implementation is in MLIQProcessor $$class_handler(handleEntityCapsDisco)
NSMutableArray* identities = [NSMutableArray new];
for(MLXMLNode* identity in [_parsedStanzas[i] find:@"{http://jabber.org/protocol/disco#info}query/identity"])
[identities addObject:[NSString stringWithFormat:@"%@/%@/%@/%@", [identity findFirst:@"/@category"], [identity findFirst:@"/@type"], ([identity check:@"/@xml:lang"] ? [identity findFirst:@"/@xml:lang"] : @""), ([identity check:@"/@name"] ? [identity findFirst:@"/@name"] : @"")]];
NSSet* features = [NSSet setWithArray:[_parsedStanzas[i] find:@"{http://jabber.org/protocol/disco#info}query/feature@var"]];
NSArray* forms = [_parsedStanzas[i] find:@"{http://jabber.org/protocol/disco#info}query/{jabber:x:data}x"];
NSString* ver = [HelperTools getEntityCapsHashForIdentities:identities andFeatures:features andForms:forms];
DDLogDebug(@"Caps hash calculated: %@", ver);
XCTAssertEqualObjects(ver, @"q07IKJEyjvHSyhy//CH0CxmKi8w=", "Caps hash NOT equal to testcase hash 'q07IKJEyjvHSyhy//CH0CxmKi8w='!");
}

/*
-(void) testParseXML
{
//yes, but this is not insecure because these are string literals boxed into an NSArray below rather than containing unchecked user input
//see here: https://releases.llvm.org/13.0.0/tools/clang/docs/DiagnosticsReference.html#wformat-security
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
__block int stanzaCounter = 0;
__block unsigned long stanzaCounter = 0;
MLBasePaser* delegate = [[MLBasePaser alloc] initWithCompletion:^(MLXMLNode* _Nullable parsedStanza) {
if(parsedStanza != nil)
{
Expand All @@ -93,28 +328,26 @@ -(void) testParseXML
id result00 = [parsedStanza find:@"/{jabber:client}iq/{http://jabber.org/protocol/pubsub}pubsub/items<node~eu\\.siacs\\.conversations\\.axolotl\\.bundles:[0-9]+>@node"];
XCTAssertEqualObjects(result00, @[], "no stanzas should match this");
/*
//stanza 1
NSSet* result01 = [NSSet setWithArray:[parsedStanza find:@"{urn:ietf:params:xml:ns:xmpp-sasl}mechanisms/mechanism#"]];
if(stanzaCounter == 1)
XCTAssertEqualObjects(result01, [NSSet setWithArray:@[@"SCRAM-SHA-1", @"PLAIN"]], "wrong mechanisms extracted");
else
XCTAssertEqualObjects(result01, [NSSet new], "all other stanzas should not match: %d", stanzaCounter);
*/
// //stanza 1
// NSSet* result01 = [NSSet setWithArray:[parsedStanza find:@"{urn:ietf:params:xml:ns:xmpp-sasl}mechanisms/mechanism#"]];
// if(stanzaCounter == 1)
// XCTAssertEqualObjects(result01, [NSSet setWithArray:@[@"SCRAM-SHA-1", @"PLAIN"]], "wrong mechanisms extracted");
// else
// XCTAssertEqualObjects(result01, [NSSet new], "all other stanzas should not match: %lu", stanzaCounter);
//stanza 2
id result02 = [parsedStanza findFirst:@"body#"];
if(stanzaCounter == 2)
XCTAssertEqualObjects(result02, @"Message text");
else
XCTAssertNil(result02, "all other stanzas should not match: %d", stanzaCounter);
XCTAssertNil(result02, "all other stanzas should not match: %lu", stanzaCounter);
//stanza 3
id result03 = [parsedStanza findFirst:@"{http://jabber.org/protocol/disco#info}query/\\{http://jabber.org/protocol/muc#roominfo}result@muc#roomconfig_roomname\\"];
if(stanzaCounter == 3)
XCTAssertEqualObjects(result03, @"testchat gruppe");
else
XCTAssertNil(result03, "all other stanzas should not match: %d", stanzaCounter);
XCTAssertNil(result03, "all other stanzas should not match: %lu", stanzaCounter);
//stanza 4
NSArray* result04 = [parsedStanza find:@"/<type=%@>/{http://jabber.org/protocol/pubsub}pubsub/subscription<node=%@><subscription=%s><jid=%@>", @"result", @"eu.siacs.conversations.axolotl.devicelist", "subscribed", @"[email protected]"];
Expand All @@ -124,7 +357,7 @@ -(void) testParseXML
XCTAssertEqualObjects([result04[0] XMLString], @"<subscription node='eu.siacs.conversations.axolotl.devicelist' subid='6795F13596465' subscription='subscribed' jid='[email protected]'/>", "failed to properly extract and stringify MLXMLNode");
}
else
XCTAssertEqualObjects(result04, @[], "all other stanzas should not match: %d", stanzaCounter);
XCTAssertEqualObjects(result04, @[], "all other stanzas should not match: %lu", stanzaCounter);
//stanza 5 (no non-match handling here)
if(stanzaCounter == 5)
Expand Down Expand Up @@ -154,5 +387,6 @@ -(void) testParseXML
[xmlParser setDelegate:delegate];
[xmlParser parse]; //blocking operation
}
*/

@end

0 comments on commit 60b5bf1

Please sign in to comment.