another.im-ios/Monal/Classes/XMPPIQ.m
2024-11-18 15:53:52 +01:00

467 lines
17 KiB
Objective-C

//
// XMPPIQ.m
// Monal
//
// Created by Anurodh Pokharel on 6/30/13.
//
//
#import "XMPPIQ.h"
#import "XMPPDataForm.h"
#import "HelperTools.h"
#import "SignalPreKey.h"
#import "MLContact.h"
@class MLContact;
NSString* const kiqGetType = @"get";
NSString* const kiqSetType = @"set";
NSString* const kiqResultType = @"result";
NSString* const kiqErrorType = @"error";
@implementation XMPPIQ
-(id) initInternalWithId:(NSString*) iqid andType:(NSString*) iqType
{
self = [super initWithElement:@"iq"];
[self setXMLNS:@"jabber:client"];
self.id = iqid;
if(iqType)
self.attributes[@"type"] = iqType;
return self;
}
-(id) initWithType:(NSString*) iqType
{
return [self initInternalWithId:[[NSUUID UUID] UUIDString] andType:iqType];
}
-(id) initWithType:(NSString*) iqType to:(NSString*) to
{
self = [self initWithType:iqType];
if(to)
[self setiqTo:to];
return self;
}
-(id) initAsResponseTo:(XMPPIQ*) iq
{
self = [self initInternalWithId:[iq findFirst:@"/@id"] andType:kiqResultType];
if(iq.from)
[self setiqTo:iq.from];
return self;
}
-(id) initAsErrorTo:(XMPPIQ*) iq
{
self = [self initInternalWithId:[iq findFirst:@"/@id"] andType:kiqErrorType];
if(iq.from)
[self setiqTo:iq.from];
return self;
}
#pragma mark iq set
// direct push registration at xmpp server without registration at appserver
-(void) setPushEnableWithNode:(NSString*) node onAppserver:(NSString*) jid
{
NSMutableString* pushModule = [NSMutableString new];
#ifdef IS_ALPHA
[pushModule appendString:@"monalAlpha"];
#else //IS_ALPHA
#if TARGET_OS_MACCATALYST && defined(IS_QUICKSY)
[pushModule appendString:@"quicksyProdCatalyst"];
#elif TARGET_OS_MACCATALYST
[pushModule appendString:@"monalProdCatalyst"];
#elif defined(IS_QUICKSY)
[pushModule appendString:@"quicksyProdiOS"];
#else
[pushModule appendString:@"monalProdiOS"];
#endif
#endif
if([[HelperTools defaultsDB] boolForKey:@"isSandboxAPNS"])
{
[pushModule appendString:@"-sandbox"];
DDLogInfo(@"Detected APNS sandbox, using sandbox push module: %@", pushModule);
}
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"enable" andNamespace:@"urn:xmpp:push:0" withAttributes:@{
@"jid": jid,
@"node": node
} andChildren:@[
[[XMPPDataForm alloc] initWithType:@"submit" formType:@"http://jabber.org/protocol/pubsub#publish-options" andDictionary:@{
@"pushModule": pushModule
}]
] andData:nil]];
}
-(void) setPushDisable:(NSString*) node onPushServer:(NSString*) pushServer
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"disable" andNamespace:@"urn:xmpp:push:0" withAttributes:@{
@"jid": pushServer,
@"node": node
} andChildren:@[] andData:nil]];
}
-(void) setBindWithResource:(NSString*) resource
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"bind" andNamespace:@"urn:ietf:params:xml:ns:xmpp-bind" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"resource" andData:resource]
] andData:nil]];
}
-(void) setMucListQueryFor:(NSString*) listType
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#admin" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{@"affiliation": listType} andChildren:@[] andData:nil]
] andData:nil]];
}
-(void) setAdhocDiscoNode
{
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/disco#items" withAttributes:@{
@"node": @"http://jabber.org/protocol/commands",
} andChildren:@[] andData:nil];
[self addChildNode:queryNode];
}
-(void) setDiscoInfoNode
{
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/disco#info"];
[self addChildNode:queryNode];
}
-(void) setDiscoItemNode
{
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/disco#items"];
[self addChildNode:queryNode];
}
-(void) setDiscoInfoWithFeatures:(NSSet*) features identity:(MLXMLNode*) identity andNode:(NSString*) node
{
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/disco#info"];
if(node)
[queryNode.attributes setObject:node forKey:@"node"];
for(NSString* feature in features)
{
MLXMLNode* featureNode = [[MLXMLNode alloc] initWithElement:@"feature"];
featureNode.attributes[@"var"] = feature;
[queryNode addChildNode:featureNode];
}
[queryNode addChildNode:identity];
[self addChildNode:queryNode];
}
-(void) setiqTo:(NSString*) to
{
if(to)
self.to = to;
}
-(void) setPing
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"ping" andNamespace:@"urn:xmpp:ping"]];
}
-(void) setPurgeOfflineStorage
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"offline" andNamespace:@"http://jabber.org/protocol/offline" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"purge"]
] andData:nil]];
}
#pragma mark - MAM
-(void) mamArchivePref
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"prefs" andNamespace:@"urn:xmpp:mam:2"]];
}
-(void) updateMamArchivePrefDefault:(NSString *) pref
{
/**
pref is aways, never or roster
*/
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"prefs" andNamespace:@"urn:xmpp:mam:2" withAttributes:@{@"default": pref} andChildren:@[] andData:nil]];
}
-(void) setMAMQueryLatestMessagesForJid:(NSString* _Nullable) jid before:(NSString* _Nullable) uid
{
//set iq id to mam query id
self.id = [NSString stringWithFormat:@"MLhistory:%@", [[NSUUID UUID] UUIDString]];
XMPPDataForm* form = [[XMPPDataForm alloc] initWithType:@"submit" andFormType:@"urn:xmpp:mam:2"];
if(jid)
form[@"with"] = jid;
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"urn:xmpp:mam:2" withAttributes:@{
@"queryid": self.id
} andChildren:@[
form,
[[MLXMLNode alloc] initWithElement:@"set" andNamespace:@"http://jabber.org/protocol/rsm" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"max" andData:@"50"],
[[MLXMLNode alloc] initWithElement:@"before" andData:uid]
] andData:nil]
] andData:nil];
[self addChildNode:queryNode];
}
-(void) setMAMQueryForLatestId
{
//set iq id to mam query id
self.id = [NSString stringWithFormat:@"MLignore:%@", [[NSUUID UUID] UUIDString]];
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"urn:xmpp:mam:2" withAttributes:@{
@"queryid": self.id
} andChildren:@[
[[XMPPDataForm alloc] initWithType:@"submit" formType:@"urn:xmpp:mam:2" andDictionary:@{
@"end": [HelperTools generateDateTimeString:[NSDate date]]
}],
[[MLXMLNode alloc] initWithElement:@"set" andNamespace:@"http://jabber.org/protocol/rsm" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"max" andData:@"1"],
[[MLXMLNode alloc] initWithElement:@"before"]
] andData:nil]
] andData:nil];
[self addChildNode:queryNode];
}
-(void) setMAMQueryAfter:(NSString*) uid
{
//set iq id to mam query id
self.id = [NSString stringWithFormat:@"MLcatchup:%@", [[NSUUID UUID] UUIDString]];
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"urn:xmpp:mam:2" withAttributes:@{
@"queryid": self.id
} andChildren:@[
[[XMPPDataForm alloc] initWithType:@"submit" andFormType:@"urn:xmpp:mam:2"],
[[MLXMLNode alloc] initWithElement:@"set" andNamespace:@"http://jabber.org/protocol/rsm" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"max" andData:@"50"],
[[MLXMLNode alloc] initWithElement:@"after" andData:uid]
] andData:nil]
] andData:nil];
[self addChildNode:queryNode];
}
-(void) setMAMQueryAfterTimestamp:(NSDate* _Nullable) timestamp
{
//set iq id to mam query id
self.id = [NSString stringWithFormat:@"MLcatchup:%@", [[NSUUID UUID] UUIDString]];
MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"urn:xmpp:mam:2" withAttributes:@{
@"queryid": self.id
} andChildren:@[
//query whole archive if the timestamp is nil (e.g. we never received any message contained in this archive
//nor did our stanzaid priming archive query succeed)
(timestamp==nil ?
[[XMPPDataForm alloc] initWithType:@"submit" andFormType:@"urn:xmpp:mam:2"]
:
[[XMPPDataForm alloc] initWithType:@"submit" formType:@"urn:xmpp:mam:2" andDictionary:@{
@"start": [HelperTools generateDateTimeString:timestamp]
}]
),
[[MLXMLNode alloc] initWithElement:@"set" andNamespace:@"http://jabber.org/protocol/rsm" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"max" andData:@"50"]
] andData:nil]
] andData:nil];
[self addChildNode:queryNode];
#ifdef IS_ALPHA
if(timestamp == nil)
showXMLErrorOnAlpha(nil, self, @"setMAMQueryAfterTimestamp: called with nil timestamp!");
#endif
}
-(void) setRemoveFromRoster:(MLContact*) contact
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"jabber:iq:roster" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{
@"jid": contact.contactJid,
@"subscription": @"remove"
} andChildren:@[] andData:nil]
] andData:nil]];
}
-(void) setUpdateRosterItem:(MLContact* _Nonnull) contact withName:(NSString* _Nonnull) name
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"jabber:iq:roster" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{
@"jid": contact.contactJid,
@"name": name,
} andChildren:@[] andData:nil]
] andData:nil]];
}
-(void) setRosterRequest:(NSString*) version
{
NSDictionary* attrs = @{};
if(version && ![version isEqual:@""])
attrs = @{@"ver": version};
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"jabber:iq:roster" withAttributes:attrs andChildren:@[] andData:nil]];
}
-(void) setVersion
{
NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"jabber:iq:version" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"name" andData:@"Monal"],
#if TARGET_OS_MACCATALYST
[[MLXMLNode alloc] initWithElement:@"os" andData:[NSString stringWithFormat:@"macOS %lu", osVersion.majorVersion]],
#else
[[MLXMLNode alloc] initWithElement:@"os" andData:[NSString stringWithFormat:@"iOS %lu", osVersion.majorVersion]],
#endif
[[MLXMLNode alloc] initWithElement:@"version" andData:[HelperTools appBuildVersionInfoFor:MLVersionTypeIQ]]
] andData:nil]];
}
-(void) setBlocked:(BOOL) blocked forJid:(NSString* _Nonnull) blockedJid
{
MLXMLNode* blockNode = [[MLXMLNode alloc] initWithElement:(blocked ? @"block" : @"unblock") andNamespace:@"urn:xmpp:blocking"];
MLXMLNode* itemNode = [[MLXMLNode alloc] initWithElement:@"item"];
[itemNode.attributes setObject:blockedJid forKey:@"jid"];
[blockNode addChildNode:itemNode];
[self addChildNode:blockNode];
}
-(void) requestBlockList
{
MLXMLNode* blockNode = [[MLXMLNode alloc] initWithElement:@"blocklist" andNamespace:@"urn:xmpp:blocking"];
[self addChildNode:blockNode];
}
-(void) httpUploadforFile:(NSString *) file ofSize:(NSNumber *) filesize andContentType:(NSString *) contentType
{
MLXMLNode* requestNode = [[MLXMLNode alloc] initWithElement:@"request" andNamespace:@"urn:xmpp:http:upload:0"];
requestNode.attributes[@"filename"] = file;
requestNode.attributes[@"size"] = [NSString stringWithFormat:@"%@", filesize];
requestNode.attributes[@"content-type"] = contentType;
[self addChildNode:requestNode];
}
#pragma mark iq get
-(void) getEntitySoftwareVersionInfo
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"jabber:iq:version"]];
}
#pragma mark MUC
-(void) setGetRoomConfig
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner"]];
}
-(void) setRoomConfig:(XMPPDataForm*) configForm
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner" withAttributes:@{} andChildren:@[configForm] andData:nil]];
}
-(void) setInstantRoom
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner" withAttributes:@{} andChildren:@[
[[XMPPDataForm alloc] initWithType:@"submit" andFormType:@"http://jabber.org/protocol/muc#roomconfig"]
] andData:nil]];
}
-(void) setRemoveVcardAvatar
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"vCard" andNamespace:@"vcard-temp" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"PHOTO" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"PHOTO" andData:nil],
[[MLXMLNode alloc] initWithElement:@"BINVAL" andData:nil],
] andData:nil]
] andData:nil]];
}
-(void) setVcardAvatarWithData:(NSData*) imageData andType:(NSString*) imageType
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"vCard" andNamespace:@"vcard-temp" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"PHOTO" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"PHOTO" andData:imageType],
[[MLXMLNode alloc] initWithElement:@"BINVAL" andData:[HelperTools encodeBase64WithData:imageData]],
] andData:nil]
] andData:nil]];
}
-(void) setVcardQuery
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"vCard" andNamespace:@"vcard-temp"]];
}
#pragma mark - Account Management
-(void) submitRegToken:(NSString*) token
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"preauth" andNamespace:@"urn:xmpp:pars:0" withAttributes:@{
@"token": token
} andChildren:@[] andData:nil]];
}
-(void) getRegistrationFields
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:kRegisterNameSpace]];
}
/*
This is really hardcoded for yax.im might work for others
*/
-(void) registerUser:(NSString*) user withPassword:(NSString*) newPass captcha:(NSString* _Nullable) captcha andHiddenFields:(NSDictionary* _Nullable) hiddenFields
{
//if no reg form was provided both of these are nil --> don't try to send a reg form in our response
if(captcha != nil && hiddenFields != nil)
{
NSMutableDictionary* fields = [NSMutableDictionary dictionaryWithDictionary:@{
@"username": user,
@"password": newPass,
}];
if(captcha)
fields[@"ocr"] = captcha;
[fields addEntriesFromDictionary:hiddenFields];
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:kRegisterNameSpace withAttributes:@{} andChildren:@[
[[XMPPDataForm alloc] initWithType:@"submit" formType:kRegisterNameSpace andDictionary:fields]
] andData:nil]];
}
else
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:kRegisterNameSpace withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"username" andData:user],
[[MLXMLNode alloc] initWithElement:@"password" andData:newPass],
] andData:nil]];
}
}
-(void) changePasswordForUser:(NSString*) user newPassword:(NSString*) newPass
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:kRegisterNameSpace withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"username" andData:user],
[[MLXMLNode alloc] initWithElement:@"password" andData:newPass],
] andData:nil]];
}
-(void) setMucAdminQueryWithAffiliation:(NSString*) affiliation forJid:(NSString*) jid
{
[self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#admin" withAttributes:@{} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{
@"affiliation": affiliation,
@"jid": jid,
} andChildren:@[] andData:nil],
] andData:nil]];
}
#ifdef IS_QUICKSY
-(void) setQuicksyPhoneBook:(NSArray*) numbers
{
MLXMLNode* envelope = [[MLXMLNode alloc] initWithElement:@"phone-book" andNamespace:@"im.quicksy.synchronization:0"];
for(NSString* number in numbers)
{
[envelope addChildNode:[[MLXMLNode alloc] initWithElement:@"entry" withAttributes:@{
@"number": number,
} andChildren:@[] andData:nil]];
}
[self addChildNode:envelope];
}
#endif
@end