823 lines
41 KiB
Mathematica
823 lines
41 KiB
Mathematica
|
//
|
||
|
// MLIQProcessor.m
|
||
|
// Monal
|
||
|
//
|
||
|
// Created by Anurodh Pokharel on 11/27/19.
|
||
|
// Copyright © 2019 Monal.im. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import <stdatomic.h>
|
||
|
#import "MLIQProcessor.h"
|
||
|
#import "MLConstants.h"
|
||
|
#import "MLHandler.h"
|
||
|
#import "DataLayer.h"
|
||
|
#import "MLImageManager.h"
|
||
|
#import "HelperTools.h"
|
||
|
#import "MLNotificationQueue.h"
|
||
|
#import "MLContactSoftwareVersionInfo.h"
|
||
|
#import "MLOMEMO.h"
|
||
|
|
||
|
|
||
|
/**
|
||
|
Validate and process any iq elements.
|
||
|
@link https://xmpp.org/rfcs/rfc6120.html#stanzas-semantics-iq
|
||
|
*/
|
||
|
@implementation MLIQProcessor
|
||
|
|
||
|
+(void) processUnboundIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account
|
||
|
{
|
||
|
//only handle these iqs if the remote user is on our roster,
|
||
|
//if the are coming from our own domain,
|
||
|
//or if they are from a muc group, not a channel
|
||
|
MLContact* contact = [MLContact createContactFromJid:iqNode.fromUser andAccountID:account.accountID];
|
||
|
if(!(
|
||
|
//we have to check for .isMuc because mucs always set .isSubscribedFrom to YES
|
||
|
(!contact.isMuc && contact.isSubscribedFrom) ||
|
||
|
contact.isSelfChat ||
|
||
|
[account.connectionProperties.identity.domain isEqualToString:iqNode.fromUser] ||
|
||
|
(contact.isMuc && [kMucTypeGroup isEqualToString:contact.mucType])
|
||
|
))
|
||
|
DDLogWarn(@"Invalid sender for iq (!subscribedFrom || isMuc), ignoring: %@", iqNode);
|
||
|
|
||
|
if([iqNode check:@"/<type=get>"])
|
||
|
[self processGetIq:iqNode forAccount:account];
|
||
|
else if([iqNode check:@"/<type=set>"])
|
||
|
[self processSetIq:iqNode forAccount:account];
|
||
|
else if([iqNode check:@"/<type=result>"])
|
||
|
[self processResultIq:iqNode forAccount:account];
|
||
|
else if([iqNode check:@"/<type=error>"])
|
||
|
[self processErrorIq:iqNode forAccount:account];
|
||
|
else
|
||
|
DDLogWarn(@"Ignoring invalid iq type: %@", [iqNode findFirst:@"/@type"]);
|
||
|
}
|
||
|
|
||
|
+(void) processGetIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account
|
||
|
{
|
||
|
if([iqNode check:@"{urn:xmpp:ping}ping"])
|
||
|
{
|
||
|
XMPPIQ* pong = [[XMPPIQ alloc] initAsResponseTo:iqNode];
|
||
|
[pong setiqTo:iqNode.from];
|
||
|
[account send:pong];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"{jabber:iq:version}query"] && [[HelperTools defaultsDB] boolForKey: @"allowVersionIQ"])
|
||
|
{
|
||
|
XMPPIQ* versioniq = [[XMPPIQ alloc] initAsResponseTo:iqNode];
|
||
|
[versioniq setiqTo:iqNode.from];
|
||
|
[versioniq setVersion];
|
||
|
[account send:versioniq];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"{http://jabber.org/protocol/disco#info}query"])
|
||
|
{
|
||
|
XMPPIQ* discoInfoResponse = [[XMPPIQ alloc] initAsResponseTo:iqNode];
|
||
|
[discoInfoResponse setDiscoInfoWithFeatures:account.capsFeatures identity:account.capsIdentity andNode:[iqNode findFirst:@"{http://jabber.org/protocol/disco#info}query@node"]];
|
||
|
[account send:discoInfoResponse];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DDLogWarn(@"Got unhandled get IQ: %@", iqNode);
|
||
|
[self respondWithErrorTo:iqNode onAccount:account];
|
||
|
}
|
||
|
|
||
|
+(void) processSetIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account
|
||
|
{
|
||
|
//these iqs will be ignored if not matching an outgoing or incoming call
|
||
|
//--> no presence leak if the call was not outgoing, because the jmi stanzas creating the call will
|
||
|
//not be processed without isSubscribedFrom in the first place
|
||
|
if(([iqNode check:@"{urn:xmpp:jingle:1}jingle"] && ![iqNode check:@"{urn:xmpp:jingle:1}jingle<action=transport-info>"]))
|
||
|
{
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalIncomingSDP object:account userInfo:@{@"iqNode": iqNode}];
|
||
|
return;
|
||
|
}
|
||
|
if([iqNode check:@"{urn:xmpp:jingle:1}jingle<action=transport-info>"])
|
||
|
{
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalIncomingICECandidate object:account userInfo:@{@"iqNode": iqNode}];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//its a roster push (sanity check will be done in processRosterWithAccount:andIqNode:)
|
||
|
if([iqNode check:@"{jabber:iq:roster}query"])
|
||
|
{
|
||
|
//this will only return YES, if the roster push was allowed and processed successfully
|
||
|
if([self processRosterWithAccount:account andIqNode:iqNode])
|
||
|
{
|
||
|
//send empty result iq as per RFC 6121 requirements
|
||
|
XMPPIQ* reply = [[XMPPIQ alloc] initAsResponseTo:iqNode];
|
||
|
[reply setiqTo:iqNode.from];
|
||
|
[account send:reply];
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"{urn:xmpp:blocking}block"] || [iqNode check:@"{urn:xmpp:blocking}unblock"])
|
||
|
{
|
||
|
//make sure we don't process blocking updates not coming from our own account
|
||
|
if([account.connectionProperties.serverDiscoFeatures containsObject:@"urn:xmpp:blocking"] && (iqNode.from == nil || [iqNode.fromUser isEqualToString:account.connectionProperties.identity.jid]))
|
||
|
{
|
||
|
BOOL blockingUpdated = NO;
|
||
|
// mark jid as unblocked
|
||
|
if([iqNode check:@"{urn:xmpp:blocking}unblock"])
|
||
|
{
|
||
|
NSArray* unBlockItems = [iqNode find:@"{urn:xmpp:blocking}unblock/item@@"];
|
||
|
for(NSDictionary* item in unBlockItems)
|
||
|
{
|
||
|
if(item && item[@"jid"])
|
||
|
[[DataLayer sharedInstance] unBlockJid:item[@"jid"] withAccountID:account.accountID];
|
||
|
}
|
||
|
if(unBlockItems && unBlockItems.count == 0)
|
||
|
{
|
||
|
// remove all blocks
|
||
|
[account updateLocalBlocklistCache:[[NSSet<NSString*> alloc] init]];
|
||
|
}
|
||
|
blockingUpdated = YES;
|
||
|
}
|
||
|
// mark jid as blocked
|
||
|
if([iqNode check:@"{urn:xmpp:blocking}block"])
|
||
|
{
|
||
|
for(NSDictionary* item in [iqNode find:@"{urn:xmpp:blocking}block/item@@"])
|
||
|
{
|
||
|
if(item && item[@"jid"])
|
||
|
[[DataLayer sharedInstance] blockJid:item[@"jid"] withAccountID:account.accountID];
|
||
|
}
|
||
|
blockingUpdated = YES;
|
||
|
}
|
||
|
if(blockingUpdated)
|
||
|
{
|
||
|
// notify the views
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalBlockListRefresh object:account userInfo:@{@"accountID": account.accountID}];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
DDLogWarn(@"Invalid sender for blocklist, ignoring iq: %@", iqNode);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DDLogWarn(@"Got unhandled set IQ: %@", iqNode);
|
||
|
[self respondWithErrorTo:iqNode onAccount:account];
|
||
|
}
|
||
|
|
||
|
+(void) processResultIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account
|
||
|
{
|
||
|
//WARNING: be careful adding stateless result handlers here (those can impose security risks!)
|
||
|
|
||
|
DDLogWarn(@"Got unhandled result IQ: %@", iqNode);
|
||
|
[self respondWithErrorTo:iqNode onAccount:account];
|
||
|
}
|
||
|
|
||
|
+(void) processErrorIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account
|
||
|
{
|
||
|
DDLogWarn(@"Got unhandled error IQ: %@", iqNode);
|
||
|
}
|
||
|
|
||
|
$$class_handler(handleCatchup, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$BOOL(secondTry))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogWarn(@"Mam catchup query returned error: %@", [iqNode findFirst:@"error"]);
|
||
|
|
||
|
//handle weird XEP-0313 monkey-patching XEP-0059 behaviour (WHY THE HELL??)
|
||
|
if(!secondTry && [iqNode check:@"error/{urn:ietf:params:xml:ns:xmpp-stanzas}item-not-found"])
|
||
|
{
|
||
|
//latestMessage can be nil, thus [latestMessage timestamp] will return nil and setMAMQueryAfterTimestamp:nil
|
||
|
//will query the whole archive since dawn of time
|
||
|
MLMessage* latestMessage = [[DataLayer sharedInstance] messageForHistoryID:[[DataLayer sharedInstance] getBiggestHistoryId]];
|
||
|
DDLogInfo(@"Querying COMPLETE muc mam:2 archive at %@ after timestamp %@ for catchup", account.connectionProperties.identity.jid, [latestMessage timestamp]);
|
||
|
XMPPIQ* mamQuery = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[mamQuery setMAMQueryAfterTimestamp:[latestMessage timestamp]];
|
||
|
[account sendIq:mamQuery withHandler:$newHandler(self, handleCatchup, $BOOL(secondTry, YES))];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
[HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to query for new messages on account %@", @""), account.connectionProperties.identity.jid] withNode:iqNode andAccount:account andIsSevere:YES];
|
||
|
[account mamFinishedFor:account.connectionProperties.identity.jid];
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
if(![[iqNode findFirst:@"{urn:xmpp:mam:2}fin@complete|bool"] boolValue] && [iqNode check:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"])
|
||
|
{
|
||
|
DDLogVerbose(@"Paging through mam catchup results at %@ with after: %@", account.connectionProperties.identity.jid, [iqNode findFirst:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"]);
|
||
|
//do RSM forward paging
|
||
|
XMPPIQ* pageQuery = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[pageQuery setMAMQueryAfter:[iqNode findFirst:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"]];
|
||
|
[account sendIq:pageQuery withHandler:$newHandler(self, handleCatchup, $BOOL(secondTry, NO))];
|
||
|
}
|
||
|
else if([[iqNode findFirst:@"{urn:xmpp:mam:2}fin@complete|bool"] boolValue])
|
||
|
{
|
||
|
DDLogVerbose(@"Mam catchup finished for %@", account.connectionProperties.identity.jid);
|
||
|
[account mamFinishedFor:account.connectionProperties.identity.jid];
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleMamResponseWithLatestId, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogWarn(@"Mam latest stanzaid query %@ returned error: %@", iqNode.id, [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to query newest stanzaid for account %@", @""), account.connectionProperties.identity.jid] withNode:iqNode andAccount:account andIsSevere:YES];
|
||
|
return;
|
||
|
}
|
||
|
DDLogVerbose(@"Got latest stanza id to prime database with: %@", [iqNode findFirst:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"]);
|
||
|
//only do this if we got a valid stanza id (not null)
|
||
|
//if we did not get one we will get one when receiving the next message in this smacks session
|
||
|
//if the smacks session times out before we get a message and someone sends us one or more messages before we had a chance to establish
|
||
|
//a new smacks session, this messages will get lost because we don't know how to query the archive for this message yet
|
||
|
//once we successfully receive the first mam-archived message stanza (could even be an XEP-184 ack for a sent message),
|
||
|
//no more messages will get lost
|
||
|
//we ignore this single message loss here, because it should be super rare and solving it would be really complicated
|
||
|
if([iqNode check:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"])
|
||
|
[[DataLayer sharedInstance] setLastStanzaId:[iqNode findFirst:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"] forAccount:account.accountID];
|
||
|
[account mamFinishedFor:account.connectionProperties.identity.jid];
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleCarbonsEnabled, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogWarn(@"carbon enable iq returned error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to enable carbons for account %@", @""), account.connectionProperties.identity.jid] withNode:iqNode andAccount:account andIsSevere:YES];
|
||
|
return;
|
||
|
}
|
||
|
account.connectionProperties.usingCarbons2 = YES;
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleBind, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogWarn(@"Binding our resource returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
if([iqNode check:@"error<type=cancel>"])
|
||
|
{
|
||
|
[HelperTools postError:NSLocalizedString(@"XMPP Bind Error", @"") withNode:iqNode andAccount:account andIsSevere:YES];
|
||
|
[account disconnect]; //don't try again until next process start/unfreeze
|
||
|
}
|
||
|
else if([iqNode check:@"error<type=modify>"])
|
||
|
[account bindResource:[HelperTools encodeRandomResource]]; //try to bind a new resource
|
||
|
else
|
||
|
[account reconnect]; //just try to reconnect (wait error type and all other error types not expected for bind)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DDLogInfo(@"Now bound to fullJid: %@", [iqNode findFirst:@"{urn:ietf:params:xml:ns:xmpp-bind}bind/jid#"]);
|
||
|
[account.connectionProperties.identity bindJid:[iqNode findFirst:@"{urn:ietf:params:xml:ns:xmpp-bind}bind/jid#"]];
|
||
|
DDLogDebug(@"bareJid=%@, resource=%@, fullJid=%@", account.connectionProperties.identity.jid, account.connectionProperties.identity.resource, account.connectionProperties.identity.fullJid);
|
||
|
|
||
|
//update resource in db (could be changed by server)
|
||
|
NSMutableDictionary* accountDict = [[NSMutableDictionary alloc] initWithDictionary:[[DataLayer sharedInstance] detailsForAccount:account.accountID]];
|
||
|
accountDict[kResource] = account.connectionProperties.identity.resource;
|
||
|
[[DataLayer sharedInstance] updateAccounWithDictionary:accountDict];
|
||
|
|
||
|
if(account.connectionProperties.supportsSM3)
|
||
|
{
|
||
|
MLXMLNode *enableNode = [[MLXMLNode alloc]
|
||
|
initWithElement:@"enable"
|
||
|
andNamespace:@"urn:xmpp:sm:3"
|
||
|
withAttributes:@{@"resume": @"true"}
|
||
|
andChildren:@[]
|
||
|
andData:nil
|
||
|
];
|
||
|
[account send:enableNode];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//init session and query disco, roster etc.
|
||
|
[account initSession];
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
//proxy handler
|
||
|
$$class_handler(handleRoster, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
[self processRosterWithAccount:account andIqNode:iqNode];
|
||
|
$$
|
||
|
|
||
|
+(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode
|
||
|
{
|
||
|
//check sanity of from according to RFC 6121:
|
||
|
// https://tools.ietf.org/html/rfc6121#section-2.1.3 (roster get)
|
||
|
// https://tools.ietf.org/html/rfc6121#section-2.1.6 (roster push)
|
||
|
if(
|
||
|
iqNode.from != nil &&
|
||
|
![iqNode.from isEqualToString:account.connectionProperties.identity.jid] &&
|
||
|
![iqNode.from isEqualToString:account.connectionProperties.identity.domain]
|
||
|
)
|
||
|
{
|
||
|
DDLogWarn(@"Invalid sender for roster, ignoring iq: %@", iqNode);
|
||
|
return NO;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogWarn(@"Roster query returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:NSLocalizedString(@"XMPP Roster Error", @"") withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return NO;
|
||
|
}
|
||
|
|
||
|
NSArray* rosterList = [iqNode find:@"{jabber:iq:roster}query/item"];
|
||
|
for(MLXMLNode* contactNode in rosterList)
|
||
|
{
|
||
|
NSMutableDictionary* contact = [contactNode findFirst:@"/@@"];
|
||
|
|
||
|
//ignore roster entries without jid (is this even possible?)
|
||
|
if(contact[@"jid"] == nil)
|
||
|
continue;
|
||
|
|
||
|
//ignore roster entries providing a full jid instead of bare jids (is that even legitimate?)
|
||
|
NSDictionary* splitJid = [HelperTools splitJid:contact[@"jid"]];
|
||
|
if(splitJid[@"resource"] != nil)
|
||
|
continue;
|
||
|
|
||
|
contact[@"jid"] = [[NSString stringWithFormat:@"%@", contact[@"jid"]] lowercaseString];
|
||
|
MLContact* contactObj = [MLContact createContactFromJid:contact[@"jid"] andAccountID:account.accountID];
|
||
|
BOOL isKnownUser = [[DataLayer sharedInstance] contactDictionaryForUsername:contact[@"jid"] forAccount:account.accountID] != nil;
|
||
|
if([[contact objectForKey:@"subscription"] isEqualToString:kSubRemove])
|
||
|
{
|
||
|
if(contactObj.isMuc)
|
||
|
DDLogWarn(@"Got roster remove request for MUC, ignoring it (possibly even triggered by us).");
|
||
|
else
|
||
|
{
|
||
|
[[DataLayer sharedInstance] removeBuddy:contact[@"jid"] forAccount:account.accountID];
|
||
|
[contactObj removeShareInteractions];
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRemoved object:account userInfo:@{@"contact": contactObj}];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if([[contact objectForKey:@"subscription"] isEqualToString:kSubFrom]) //already subscribed
|
||
|
{
|
||
|
[[DataLayer sharedInstance] deleteContactRequest:contactObj];
|
||
|
}
|
||
|
else if([[contact objectForKey:@"subscription"] isEqualToString:kSubBoth])
|
||
|
{
|
||
|
// We and the contact are interested
|
||
|
[[DataLayer sharedInstance] deleteContactRequest:contactObj];
|
||
|
}
|
||
|
|
||
|
if(contactObj.isMuc)
|
||
|
{
|
||
|
DDLogWarn(@"Removing muc '%@' from contactlist, got 'normal' roster entry!", contact[@"jid"]);
|
||
|
[[DataLayer sharedInstance] removeBuddy:contact[@"jid"] forAccount:account.accountID];
|
||
|
[contactObj removeShareInteractions];
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRemoved object:account userInfo:@{@"contact": contactObj}];
|
||
|
contactObj = [MLContact createContactFromJid:contact[@"jid"] andAccountID:account.accountID];
|
||
|
}
|
||
|
|
||
|
DDLogVerbose(@"Adding contact %@ (%@) to database", contact[@"jid"], [contact objectForKey:@"name"]);
|
||
|
[[DataLayer sharedInstance] addContact:contact[@"jid"]
|
||
|
forAccount:account.accountID
|
||
|
nickname:[contact objectForKey:@"name"] ? [contact objectForKey:@"name"] : @""];
|
||
|
|
||
|
DDLogVerbose(@"Setting subscription status '%@' (ask=%@) for contact %@", contact[@"subscription"], contact[@"ask"], contact[@"jid"]);
|
||
|
[[DataLayer sharedInstance] setSubscription:[contact objectForKey:@"subscription"]
|
||
|
andAsk:[contact objectForKey:@"ask"]
|
||
|
forContact:contact[@"jid"]
|
||
|
andAccount:account.accountID];
|
||
|
|
||
|
NSSet* groups = [NSSet setWithArray:[contactNode find:@"group#"]];
|
||
|
DDLogVerbose(@"Setting following groups: %@ for contact %@", groups, contact[@"jid"]);
|
||
|
[[DataLayer sharedInstance] setGroups:groups
|
||
|
forContact:contact[@"jid"]
|
||
|
inAccount:account.accountID];
|
||
|
|
||
|
#ifndef DISABLE_OMEMO
|
||
|
if(contactObj.isMuc == NO)
|
||
|
{
|
||
|
//request omemo devicelist, but only if this is a new user
|
||
|
//(we could get a roster with already known users if roster version is not supported by the server)
|
||
|
if(!isKnownUser && !([contact[@"subscription"] isEqualToString:kSubBoth] || [contact[@"subscription"] isEqualToString:kSubTo]))
|
||
|
[account.omemo subscribeAndFetchDevicelistIfNoSessionExistsForJid:contact[@"jid"]];
|
||
|
}
|
||
|
#endif// DISABLE_OMEMO
|
||
|
|
||
|
//regenerate avatar if the nickame has changed
|
||
|
if(![contactObj.nickName isEqualToString:[contact objectForKey:@"name"]])
|
||
|
[[MLImageManager sharedInstance] purgeCacheForContact:contact[@"jid"] andAccount:account.accountID];
|
||
|
|
||
|
//TODO: save roster groups to new db table
|
||
|
|
||
|
//send out kMonalContactRefresh notification
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:account userInfo:@{
|
||
|
@"contact": [MLContact createContactFromJid:contact[@"jid"] andAccountID:account.accountID]
|
||
|
}];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"{jabber:iq:roster}query@ver"])
|
||
|
[[DataLayer sharedInstance] setRosterVersion:[iqNode findFirst:@"{jabber:iq:roster}query@ver"] forAccount:account.accountID];
|
||
|
|
||
|
return YES;
|
||
|
}
|
||
|
|
||
|
//features advertised on our own jid/account
|
||
|
$$class_handler(handleAccountDiscoInfo, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Disco info query to our account returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:NSLocalizedString(@"XMPP Account Info Error", @"") withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
NSSet* features = [NSSet setWithArray:[iqNode find:@"{http://jabber.org/protocol/disco#info}query/feature@var"]];
|
||
|
account.connectionProperties.accountDiscoFeatures = features;
|
||
|
|
||
|
if(
|
||
|
[iqNode check:@"{http://jabber.org/protocol/disco#info}query/identity<category=pubsub><type=pep>"] && //xep-0163 support
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#filtered-notifications"] && //needed for xep-0163 support
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#publish-options"] && //needed for xep-0223 support
|
||
|
//important xep-0060 support (aka basic support)
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#publish"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#subscribe"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#create-nodes"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#delete-items"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#delete-nodes"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#persistent-items"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#retrieve-items"] &&
|
||
|
//not advertised in ejabberd 22.05 but supported
|
||
|
//[features containsObject:@"http://jabber.org/protocol/pubsub#config-node"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#auto-create"] &&
|
||
|
// [features containsObject:@"http://jabber.org/protocol/pubsub#last-published"] &&
|
||
|
// [features containsObject:@"http://jabber.org/protocol/pubsub#create-and-configure"] &&
|
||
|
YES
|
||
|
) {
|
||
|
DDLogInfo(@"Supports pubsub (pep)");
|
||
|
account.connectionProperties.supportsPubSub = YES;
|
||
|
|
||
|
//modern pep support
|
||
|
account.connectionProperties.supportsModernPubSub = NO;
|
||
|
if(
|
||
|
//needed for xep-0402
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#item-ids"] &&
|
||
|
[features containsObject:@"http://jabber.org/protocol/pubsub#multi-items"] &&
|
||
|
YES
|
||
|
) {
|
||
|
DDLogInfo(@"Supports modern pep multi-items");
|
||
|
account.connectionProperties.supportsModernPubSub = YES;
|
||
|
}
|
||
|
|
||
|
account.connectionProperties.supportsPubSubMax = NO;
|
||
|
if([features containsObject:@"http://jabber.org/protocol/pubsub#config-node-max"])
|
||
|
{
|
||
|
DDLogInfo(@"Supports pep 'max' item count");
|
||
|
account.connectionProperties.supportsPubSubMax = YES;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//bookmarks2 needs modern pubsub features
|
||
|
if(account.connectionProperties.supportsModernPubSub && [features containsObject:@"urn:xmpp:bookmarks:1#compat-pep"])
|
||
|
{
|
||
|
DDLogInfo(@"supports XEP-0402 compat-pep");
|
||
|
account.connectionProperties.supportsBookmarksCompat = YES;
|
||
|
}
|
||
|
|
||
|
if([features containsObject:@"urn:xmpp:push:0"])
|
||
|
{
|
||
|
DDLogInfo(@"supports push");
|
||
|
[account enablePush];
|
||
|
}
|
||
|
|
||
|
if([features containsObject:@"urn:xmpp:mam:2"])
|
||
|
{
|
||
|
DDLogInfo(@"supports mam:2");
|
||
|
|
||
|
//query mam since last received stanza ID because we could not resume the smacks session
|
||
|
//(we would not have landed here if we were able to resume the smacks session)
|
||
|
//this will do a catchup of everything we might have missed since our last connection
|
||
|
//we possibly receive sent messages, too (this will update the stanzaid in database and gets deduplicate by messageid,
|
||
|
//which is guaranteed to be unique (because monal uses uuids for outgoing messages)
|
||
|
NSString* lastStanzaId = [[DataLayer sharedInstance] lastStanzaIdForAccount:account.accountID];
|
||
|
[account delayIncomingMessageStanzasForArchiveJid:account.connectionProperties.identity.jid];
|
||
|
XMPPIQ* mamQuery = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
if(lastStanzaId)
|
||
|
{
|
||
|
DDLogInfo(@"Querying mam:2 archive after stanzaid '%@' for catchup", lastStanzaId);
|
||
|
[mamQuery setMAMQueryAfter:lastStanzaId];
|
||
|
[account sendIq:mamQuery withHandler:$newHandler(self, handleCatchup, $BOOL(secondTry, NO))];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DDLogInfo(@"Querying mam:2 archive for latest stanzaid to prime database");
|
||
|
[mamQuery setMAMQueryForLatestId];
|
||
|
[account sendIq:mamQuery withHandler:$newHandler(self, handleMamResponseWithLatestId)];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//we don't support MAM --> tell the system to finish the catchup without MAM
|
||
|
DDLogError(@"Server does not support MAM, marking mam catchup as 'finished' for jid %@", account.connectionProperties.identity.jid);
|
||
|
[account mamFinishedFor:account.connectionProperties.identity.jid];
|
||
|
}
|
||
|
|
||
|
atomic_thread_fence(memory_order_seq_cst); //make sure our connection properties are "visible" to other threads before marking them as such
|
||
|
account.connectionProperties.accountDiscoDone = YES;
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalAccountDiscoDone object:account];
|
||
|
$$
|
||
|
|
||
|
//features advertised on our server
|
||
|
$$class_handler(handleServerDiscoInfo, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Disco info query to our server returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:NSLocalizedString(@"XMPP Disco Info Error", @"") withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
NSSet* features = [NSSet setWithArray:[iqNode find:@"{http://jabber.org/protocol/disco#info}query/feature@var"]];
|
||
|
account.connectionProperties.serverDiscoFeatures = features;
|
||
|
|
||
|
if([features containsObject:@"urn:xmpp:carbons:2"])
|
||
|
{
|
||
|
DDLogInfo(@"got disco result with carbons ns");
|
||
|
if(!account.connectionProperties.usingCarbons2)
|
||
|
{
|
||
|
DDLogInfo(@"enabling carbons");
|
||
|
XMPPIQ* carbons = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[carbons addChildNode:[[MLXMLNode alloc] initWithElement:@"enable" andNamespace:@"urn:xmpp:carbons:2"]];
|
||
|
[account sendIq:carbons withHandler:$newHandler(self, handleCarbonsEnabled)];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if([features containsObject:@"urn:xmpp:blocking"])
|
||
|
[account fetchBlocklist];
|
||
|
|
||
|
if(!account.connectionProperties.supportsHTTPUpload && [features containsObject:@"urn:xmpp:http:upload:0"])
|
||
|
{
|
||
|
DDLogInfo(@"supports http upload with server: %@", iqNode.from);
|
||
|
account.connectionProperties.supportsHTTPUpload = YES;
|
||
|
account.connectionProperties.uploadServer = iqNode.from;
|
||
|
account.connectionProperties.uploadSize = [[iqNode findFirst:@"{http://jabber.org/protocol/disco#info}query/\\{urn:xmpp:http:upload:0}result@max-file-size\\|int"] integerValue];
|
||
|
DDLogInfo(@"Upload max filesize: %lu", account.connectionProperties.uploadSize);
|
||
|
}
|
||
|
|
||
|
//query external services to learn stun/turn servers
|
||
|
if([features containsObject:@"urn:xmpp:extdisco:2"])
|
||
|
[account queryExternalServicesOn:iqNode.fromUser];
|
||
|
|
||
|
//get the server's contact addresses (XEP-0157)
|
||
|
XMPPDataForm* dataForm = [iqNode findFirst:@"{http://jabber.org/protocol/disco#info}query/\\{http://jabber.org/network/serverinfo}result\\"];
|
||
|
NSMutableDictionary<NSString*, NSArray*>* resultDictionary = [NSMutableDictionary dictionary];
|
||
|
for (NSString* fieldName in dataForm.allKeys)
|
||
|
{
|
||
|
if ([fieldName hasSuffix:@"-addresses"])
|
||
|
{
|
||
|
NSArray* addresses = [dataForm getField:fieldName][@"allValues"];
|
||
|
if (addresses != nil && addresses.count > 0)
|
||
|
resultDictionary[fieldName] = addresses;
|
||
|
}
|
||
|
}
|
||
|
account.connectionProperties.serverContactAddresses = [resultDictionary copy];
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleServiceDiscoInfo, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
NSSet* features = [NSSet setWithArray:[iqNode find:@"{http://jabber.org/protocol/disco#info}query/feature@var"]];
|
||
|
|
||
|
if(!account.connectionProperties.supportsHTTPUpload && [features containsObject:@"urn:xmpp:http:upload:0"])
|
||
|
{
|
||
|
DDLogInfo(@"supports http upload with server: %@", iqNode.from);
|
||
|
account.connectionProperties.supportsHTTPUpload = YES;
|
||
|
account.connectionProperties.uploadServer = iqNode.from;
|
||
|
account.connectionProperties.uploadSize = [[iqNode findFirst:@"{http://jabber.org/protocol/disco#info}query/\\{urn:xmpp:http:upload:0}result@max-file-size\\|int"] integerValue];
|
||
|
DDLogInfo(@"Upload max filesize: %lu", account.connectionProperties.uploadSize);
|
||
|
}
|
||
|
|
||
|
if([features containsObject:@"http://jabber.org/protocol/muc"])
|
||
|
account.connectionProperties.conferenceServers[iqNode.fromUser] = [iqNode findFirst:@"{http://jabber.org/protocol/disco#info}query"];
|
||
|
|
||
|
//query external services to learn stun/turn servers
|
||
|
if([features containsObject:@"urn:xmpp:extdisco:2"])
|
||
|
[account queryExternalServicesOn:iqNode.fromUser];
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleServerDiscoItems, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
account.connectionProperties.discoveredServices = [NSMutableArray new];
|
||
|
for(NSDictionary* item in [iqNode find:@"{http://jabber.org/protocol/disco#items}query/item@@"])
|
||
|
{
|
||
|
[account.connectionProperties.discoveredServices addObject:item];
|
||
|
if(![[item objectForKey:@"jid"] isEqualToString:account.connectionProperties.identity.domain])
|
||
|
{
|
||
|
XMPPIQ* discoInfo = [[XMPPIQ alloc] initWithType:kiqGetType];
|
||
|
[discoInfo setiqTo:[item objectForKey:@"jid"]];
|
||
|
[discoInfo setDiscoInfoNode];
|
||
|
[account sendIq:discoInfo withHandler:$newHandler(self, handleServiceDiscoInfo)];
|
||
|
}
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleAdhocDisco, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Adhoc command disco query to '%@' returned an error: %@", iqNode.from, [iqNode findFirst:@"error"]);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
account.connectionProperties.discoveredAdhocCommands = [NSMutableDictionary new];
|
||
|
for(MLXMLNode* item in [iqNode find:@"{http://jabber.org/protocol/disco#items}query<node=http://jabber.org/protocol/commands>/item"])
|
||
|
{
|
||
|
if(![[item findFirst:@"/@jid"] isEqualToString:account.connectionProperties.identity.domain])
|
||
|
continue;
|
||
|
account.connectionProperties.discoveredAdhocCommands[[item findFirst:@"/@node"]] = nilWrapper([item findFirst:@"/@name"]);
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
|
||
|
$$class_handler(handleExternalDisco, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"External service disco query to '%@' returned an error: %@", iqNode.from, [iqNode findFirst:@"error"]);
|
||
|
//[HelperTools postError:NSLocalizedString(@"XMPP External Service Disco Error", @"") withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for(MLXMLNode* service in [iqNode find:@"{urn:xmpp:extdisco:2}services/service"])
|
||
|
{
|
||
|
if([service check:@"/<type=stun>"] || [service check:@"/<type=turn>"])
|
||
|
{
|
||
|
NSMutableDictionary* info = [NSMutableDictionary dictionaryWithDictionary:@{@"directoryJid": iqNode.from}];
|
||
|
[info addEntriesFromDictionary:[service findFirst:@"/@@"]];
|
||
|
[account.connectionProperties.discoveredStunTurnServers addObject:info];
|
||
|
}
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
//entity caps of some contact
|
||
|
$$class_handler(handleEntityCapsDisco, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
NSMutableArray* identities = [NSMutableArray new];
|
||
|
for(MLXMLNode* identity in [iqNode 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:[iqNode find:@"{http://jabber.org/protocol/disco#info}query/feature@var"]];
|
||
|
NSArray* forms = [iqNode find:@"{http://jabber.org/protocol/disco#info}query/{jabber:x:data}x"];
|
||
|
NSString* ver = [HelperTools getEntityCapsHashForIdentities:identities andFeatures:features andForms:forms];
|
||
|
[[DataLayer sharedInstance] setCaps:features forVer:ver onAccountID:account.accountID];
|
||
|
[account markCapsQueryCompleteFor:ver];
|
||
|
|
||
|
//send out kMonalContactRefresh notification
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:account userInfo:@{
|
||
|
@"contact": [MLContact createContactFromJid:iqNode.fromUser andAccountID:account.accountID]
|
||
|
}];
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleMamPrefs, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"MAM prefs query returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:NSLocalizedString(@"XMPP mam preferences error", @"") withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return;
|
||
|
}
|
||
|
if([iqNode check:@"{urn:xmpp:mam:2}prefs@default"])
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMLMAMPref object:self userInfo:@{@"mamPref": [iqNode findFirst:@"{urn:xmpp:mam:2}prefs@default"]}];
|
||
|
else
|
||
|
{
|
||
|
DDLogError(@"MAM prefs query returned unexpected result: %@", iqNode);
|
||
|
[HelperTools postError:NSLocalizedString(@"Unexpected mam preferences result", @"") withNode:nil andAccount:account andIsSevere:NO];
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleSetMamPrefs, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Setting MAM prefs returned an error, ignoring: %@", [iqNode findFirst:@"error"]);
|
||
|
return;
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handlePushEnabled, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, selectedPushServer))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Enabling push returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:NSLocalizedString(@"Error registering push", @"") withNode:iqNode andAccount:account andIsSevere:YES];
|
||
|
account.connectionProperties.pushEnabled = NO;
|
||
|
return;
|
||
|
}
|
||
|
// save used push server to db
|
||
|
[[DataLayer sharedInstance] updateUsedPushServer:selectedPushServer forAccount:account.accountID];
|
||
|
DDLogInfo(@"Push is enabled now");
|
||
|
account.connectionProperties.pushEnabled = YES;
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleBlocklist, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
if(![account.connectionProperties.serverDiscoFeatures containsObject:@"urn:xmpp:blocking"])
|
||
|
{
|
||
|
DDLogWarn(@"Ignoring blocklist update, server does not announce support for blocking!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Blocklist fetch returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:NSLocalizedString(@"Failed to load blocklist", @"") withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"{urn:xmpp:blocking}blocklist"])
|
||
|
{
|
||
|
NSMutableSet<NSString*>* blockedJids = [[NSMutableSet<NSString*> alloc] init];
|
||
|
for(NSDictionary* item in [iqNode find:@"{urn:xmpp:blocking}blocklist/item@@"])
|
||
|
if(item && item[@"jid"])
|
||
|
[blockedJids addObject:item[@"jid"]];
|
||
|
[account updateLocalBlocklistCache:blockedJids];
|
||
|
// notify the views
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalBlockListRefresh object:account userInfo:@{@"accountID": account.accountID}];
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleBlocked, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, blockedJid))
|
||
|
if(![account.connectionProperties.serverDiscoFeatures containsObject:@"urn:xmpp:blocking"])
|
||
|
{
|
||
|
DDLogWarn(@"Ignoring block result, server does not announce support for blocking!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Blocking returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to block contact %@", @""), blockedJid] withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return;
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleVersionResponse, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode))
|
||
|
NSString* iqAppName = [iqNode findFirst:@"{jabber:iq:version}query/name#"];
|
||
|
NSString* iqAppVersion = [iqNode findFirst:@"{jabber:iq:version}query/version#"];
|
||
|
NSString* iqPlatformOS = [iqNode findFirst:@"{jabber:iq:version}query/os#"];
|
||
|
|
||
|
//server version info is the only case where there will be no resource --> return here
|
||
|
if([iqNode.fromUser isEqualToString:account.connectionProperties.identity.domain])
|
||
|
{
|
||
|
account.connectionProperties.serverVersion = [[MLContactSoftwareVersionInfo alloc] initWithJid:iqNode.fromUser andRessource:iqNode.fromResource andAppName:iqAppName andAppVersion:iqAppVersion andPlatformOS:iqPlatformOS andLastInteraction:[NSDate date]];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DDLogVerbose(@"Updating software version info for %@", iqNode.from);
|
||
|
NSDate* lastInteraction = [[DataLayer sharedInstance] lastInteractionOfJid:iqNode.fromUser andResource:iqNode.fromResource forAccountID:account.accountID];
|
||
|
MLContactSoftwareVersionInfo* newSoftwareVersionInfo = [[MLContactSoftwareVersionInfo alloc] initWithJid:iqNode.fromUser andRessource:iqNode.fromResource andAppName:iqAppName andAppVersion:iqAppVersion andPlatformOS:iqPlatformOS andLastInteraction:lastInteraction];
|
||
|
|
||
|
[[DataLayer sharedInstance] setSoftwareVersionInfoForContact:iqNode.fromUser
|
||
|
resource:iqNode.fromResource
|
||
|
andAccount:account.accountID
|
||
|
withSoftwareInfo:newSoftwareVersionInfo];
|
||
|
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalXmppUserSoftWareVersionRefresh
|
||
|
object:account
|
||
|
userInfo:@{@"versionInfo": newSoftwareVersionInfo}];
|
||
|
$$
|
||
|
|
||
|
$$class_handler(handleModerationResponse, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(MLMessage*, msg))
|
||
|
[msg updateWithMessage:[[DataLayer sharedInstance] messageForHistoryID:msg.messageDBId]]; //make sure our msg is up to date
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Moderating message %@ returned an error: %@", msg, [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to moderate message in group/channel '%@'", @""), iqNode.fromUser] withNode:iqNode andAccount:account andIsSevere:YES];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DDLogInfo(@"Successfully moderated message in muc: %@", msg);
|
||
|
[[DataLayer sharedInstance] retractMessageHistory:msg.messageDBId];
|
||
|
|
||
|
//update ui
|
||
|
DDLogInfo(@"Sending out kMonalDeletedMessageNotice notification for historyId %@", msg.messageDBId);
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalDeletedMessageNotice object:account userInfo:@{
|
||
|
@"message": msg,
|
||
|
@"historyId": msg.messageDBId,
|
||
|
@"contact": msg.contact,
|
||
|
}];
|
||
|
|
||
|
//update unread count in active chats list
|
||
|
[msg.contact updateUnreadCount];
|
||
|
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:account userInfo:@{
|
||
|
@"contact": msg.contact,
|
||
|
}];
|
||
|
$$
|
||
|
|
||
|
#ifdef IS_QUICKSY
|
||
|
$$class_handler(handleQuicksyPhoneBook, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSDictionary*, numbers))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Quicksy phonebook synchronize returned an error: %@", [iqNode findFirst:@"error"]);
|
||
|
[HelperTools postError:NSLocalizedString(@"Failed to synchronize phonebook", @"") withNode:iqNode andAccount:account andIsSevere:NO];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for(MLXMLNode* entry in [iqNode find:@"{im.quicksy.synchronization:0}phone-book/entry"])
|
||
|
{
|
||
|
NSString* nick = numbers[[entry findFirst:@"/@number"]];
|
||
|
for(NSString* jid in [entry find:@"jid#"])
|
||
|
{
|
||
|
DDLogDebug(@"Adding '%@' with nick '%@' to local roster...", jid, nick);
|
||
|
[[DataLayer sharedInstance] addContact:jid forAccount:account.accountID nickname:nick];
|
||
|
#ifndef DISABLE_OMEMO
|
||
|
// Request omemo devicelist
|
||
|
[account.omemo subscribeAndFetchDevicelistIfNoSessionExistsForJid:jid];
|
||
|
#endif// DISABLE_OMEMO
|
||
|
|
||
|
}
|
||
|
}
|
||
|
$$
|
||
|
#endif
|
||
|
|
||
|
+(void) respondWithErrorTo:(XMPPIQ*) iqNode onAccount:(xmpp*) account
|
||
|
{
|
||
|
XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode];
|
||
|
[errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"cancel"} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"service-unavailable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"],
|
||
|
] andData:nil]];
|
||
|
[account send:errorIq];
|
||
|
}
|
||
|
|
||
|
@end
|