diff --git a/Monal/MonalXMPPUnitTests/ParserTest.m b/Monal/MonalXMPPUnitTests/ParserTest.m index 2ee2568b6..fdc91c341 100644 --- a/Monal/MonalXMPPUnitTests/ParserTest.m +++ b/Monal/MonalXMPPUnitTests/ParserTest.m @@ -12,7 +12,8 @@ #import #import "MLBasePaser.h" -NSString* _rawXML = @"\n\ +static NSMutableArray* _parsedStanzas; +static NSString* _rawXML = @"\n\ \n\ \ SCRAM-SHA-1PLAIN\n\ @@ -20,6 +21,7 @@ \n\ Message text\n\ This will NOT be used\n\ + aGVsbG8gd29ybGQh\n\ \n\ \ http://jabber.org/protocol/muc#roominfo200testchat gruppe\n\ @@ -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. @@ -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"]; + 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:@"//{http://jabber.org/protocol/pubsub}pubsub/subscription", @"result", @"eu.siacs.conversations.axolotl.devicelist", "subscribed", @"user@example.com"]; + if(i == 3) + { + XCTAssertEqual(result.count, 1, "we expect exactly one match"); + XCTAssertEqualObjects([result[0] XMLString], @"", "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:@"/"], "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) { @@ -93,28 +328,26 @@ -(void) testParseXML id result00 = [parsedStanza find:@"/{jabber:client}iq/{http://jabber.org/protocol/pubsub}pubsub/items@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:@"//{http://jabber.org/protocol/pubsub}pubsub/subscription", @"result", @"eu.siacs.conversations.axolotl.devicelist", "subscribed", @"user@example.com"]; @@ -124,7 +357,7 @@ -(void) testParseXML XCTAssertEqualObjects([result04[0] XMLString], @"", "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) @@ -154,5 +387,6 @@ -(void) testParseXML [xmlParser setDelegate:delegate]; [xmlParser parse]; //blocking operation } +*/ @end