2016-02-24 16:56:39 +01:00

645 lines
17 KiB
Objective-C

#import "XMPP.h"
#import "XMPPLogging.h"
#import "XMPPBlocking.h"
#import "NSNumber+XMPP.h"
// Log levels: off, error, warn, info, verbose
// Log flags: trace
#ifdef DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
#define QUERY_TIMEOUT 30.0 // NSTimeInterval (double) = seconds
NSString *const XMPPBlockingErrorDomain = @"XMPPBlockingErrorDomain";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef enum XMPPBlockingQueryInfoType {
FetchBlockingList,
BlockUser,
UnblockUser,
UnblockAll,
} XMPPBlockingQueryInfoType;
@interface XMPPBlockingQueryInfo : NSObject
{
XMPPBlockingQueryInfoType type;
XMPPJID *blockingXMPPJID;
NSArray *blockingListItems;
dispatch_source_t timer;
}
@property (nonatomic, readonly) XMPPBlockingQueryInfoType type;
@property (nonatomic, readonly) NSArray *blockingListItems;
@property (nonatomic, readwrite) XMPPJID *blockingXMPPJID;
@property (nonatomic, readwrite) dispatch_source_t timer;
- (void)cancel;
+ (XMPPBlockingQueryInfo *)queryInfoWithType:(XMPPBlockingQueryInfoType)type;
+ (XMPPBlockingQueryInfo *)queryInfoWithType:(XMPPBlockingQueryInfoType)type jid:(XMPPJID *)jid;
+ (XMPPBlockingQueryInfo *)queryInfoWithType:(XMPPBlockingQueryInfoType)type jid:(XMPPJID *)jid items:(NSArray *)items;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface XMPPBlocking (/* Must be nameless for properties */)
- (void)addQueryInfo:(XMPPBlockingQueryInfo *)qi withKey:(NSString *)uuid;
- (void)queryTimeout:(NSString *)uuid;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPBlocking
@synthesize blockingDict = _blockingDict;
- (id)init
{
return [self initWithDispatchQueue:NULL];
}
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
if ((self = [super initWithDispatchQueue:queue]))
{
autoRetrieveBlockingListItems = YES;
autoClearBlockingListInfo = YES;
blockingDict = [[NSMutableDictionary alloc] init];
pendingQueries = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)activate:(XMPPStream *)aXmppStream
{
if ([super activate:aXmppStream])
{
// Reserved for possible future use.
return YES;
}
return NO;
}
- (void)deactivate
{
// Reserved for possible future use.
[super deactivate];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)autoRetrieveBlockingListItems
{
if (dispatch_get_specific(moduleQueueTag))
{
return autoRetrieveBlockingListItems;
}
else
{
__block BOOL result;
dispatch_sync(moduleQueue, ^{
result = autoRetrieveBlockingListItems;
});
return result;
}
}
- (void)setAutoRetrieveBlockingListItems:(BOOL)flag
{
dispatch_block_t block = ^{
autoRetrieveBlockingListItems = flag;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (BOOL)autoClearBlockingListInfo
{
if (dispatch_get_specific(moduleQueueTag))
{
return autoClearBlockingListInfo;
}
else
{
__block BOOL result;
dispatch_sync(moduleQueue, ^{
result = autoClearBlockingListInfo;
});
return result;
}
}
- (void)setAutoClearBlockingListInfo:(BOOL)flag
{
dispatch_block_t block = ^{
autoClearBlockingListInfo = flag;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Public API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)retrieveBlockingListItems
{
XMPPLogTrace();
// <iq type='get' id='blocklist1'>
// <blocklist xmlns='urn:xmpp:blocking'/>
// </iq>
NSXMLElement *block = [NSXMLElement elementWithName:@"blocklist" xmlns:@"urn:xmpp:blocking"];
NSString *uuid = [xmppStream generateUUID];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:uuid child:block];
[xmppStream sendElement:iq];
XMPPBlockingQueryInfo *qi = [XMPPBlockingQueryInfo queryInfoWithType:FetchBlockingList];
[self addQueryInfo:qi withKey:uuid];
}
- (void)clearBlockingListInfo
{
XMPPLogTrace();
if (dispatch_get_specific(moduleQueueTag))
{
[blockingDict removeAllObjects];
}
else
{
dispatch_async(moduleQueue, ^{ @autoreleasepool {
[blockingDict removeAllObjects];
}});
}
}
- (NSArray*)blockingList
{
if (dispatch_get_specific(moduleQueueTag))
{
return [blockingDict allKeys];
}
else
{
__block NSArray *result;
dispatch_sync(moduleQueue, ^{ @autoreleasepool {
result = [[blockingDict allKeys] copy];
}});
return result;
}
}
- (void)blockJID:(XMPPJID*)xmppJID
{
XMPPLogTrace();
id value = blockingDict[[xmppJID full]];
if (value == nil)
{
blockingDict[[xmppJID full]] = [NSNull null];
}
// <iq from='juliet@capulet.com/chamber' type='set' id='block1'>
// <block xmlns='urn:xmpp:blocking'>
// <item jid='romeo@montague.net'/>
// </block>
// </iq>
NSXMLElement *block = [NSXMLElement elementWithName:@"block" xmlns:@"urn:xmpp:blocking"];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"jid" stringValue:[xmppJID full]];
[block addChild:item];
NSString *uuid = [xmppStream generateUUID];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:block];
[iq addAttributeWithName:@"from" stringValue:xmppStream.myJID.bare];
[xmppStream sendElement:iq];
XMPPBlockingQueryInfo *qi = [XMPPBlockingQueryInfo queryInfoWithType:BlockUser];
qi.blockingXMPPJID = xmppJID;
[self addQueryInfo:qi withKey:uuid];
}
- (void)unblockJID:(XMPPJID*)xmppJID
{
XMPPLogTrace();
id value = blockingDict[[xmppJID full]];
if (value != nil)
{
[blockingDict removeObjectForKey:[xmppJID full]];
}
// <iq type='set' id='unblock1'>
// <unblock xmlns='urn:xmpp:blocking'>
// <item jid='romeo@montague.net'/>
// </unblock>
// </iq>
NSXMLElement *block = [NSXMLElement elementWithName:@"unblock" xmlns:@"urn:xmpp:blocking"];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"jid" stringValue:[xmppJID full]];
[block addChild:item];
NSString *uuid = [xmppStream generateUUID];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:block];
[xmppStream sendElement:iq];
XMPPBlockingQueryInfo *qi = [XMPPBlockingQueryInfo queryInfoWithType:UnblockUser];
qi.blockingXMPPJID = xmppJID;
[self addQueryInfo:qi withKey:uuid];
}
- (BOOL)containsJID:(XMPPJID*)xmppJID
{
if (blockingDict[[xmppJID full]])
{
return true;
}
return false;
}
/**
* Unblock all.
*/
- (void)unblockAll
{
XMPPLogTrace();
// <iq type='set' id='unblock2'>
// <unblock xmlns='urn:xmpp:blocking'/>
// </iq>
NSXMLElement *block = [NSXMLElement elementWithName:@"unblock" xmlns:@"urn:xmpp:blocking"];
NSString *uuid = [xmppStream generateUUID];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:block];
[xmppStream sendElement:iq];
XMPPBlockingQueryInfo *qi = [XMPPBlockingQueryInfo queryInfoWithType:UnblockAll];
[self addQueryInfo:qi withKey:uuid];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Query Processing
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)addQueryInfo:(XMPPBlockingQueryInfo *)queryInfo withKey:(NSString *)uuid
{
// Setup timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue);
dispatch_source_set_event_handler(timer, ^{ @autoreleasepool {
[self queryTimeout:uuid];
}});
dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, (QUERY_TIMEOUT * NSEC_PER_SEC));
dispatch_source_set_timer(timer, fireTime, DISPATCH_TIME_FOREVER, 1.0);
dispatch_resume(timer);
queryInfo.timer = timer;
// Add to dictionary
pendingQueries[uuid] = queryInfo;
}
- (void)removeQueryInfo:(XMPPBlockingQueryInfo *)queryInfo withKey:(NSString *)uuid
{
// Invalidate timer
[queryInfo cancel];
// Remove from dictionary
[pendingQueries removeObjectForKey:uuid];
}
- (void)processQuery:(XMPPBlockingQueryInfo *)queryInfo withFailureCode:(XMPPBlockingErrorCode)errorCode
{
NSError *error = [NSError errorWithDomain:XMPPBlockingErrorDomain code:errorCode userInfo:nil];
if (queryInfo.type == FetchBlockingList)
{
[multicastDelegate xmppBlocking:self didNotReceivedBlockingListDueToError:error];
}
else if (queryInfo.type == BlockUser)
{
[multicastDelegate xmppBlocking:self didNotBlockJID:queryInfo.blockingXMPPJID error:error];
}
else if (queryInfo.type == UnblockUser)
{
[multicastDelegate xmppBlocking:self didNotUnblockJID:queryInfo.blockingXMPPJID error:error];
}
else if (queryInfo.type == UnblockAll)
{
[multicastDelegate xmppBlocking:self didNotUnblockAllDueToError:error];
}
}
- (void)queryTimeout:(NSString *)uuid
{
XMPPBlockingQueryInfo *queryInfo = pendingQueries[uuid];
if (queryInfo)
{
[self processQuery:queryInfo withFailureCode:XMPPBlockingQueryTimeout];
[self removeQueryInfo:queryInfo withKey:uuid];
}
}
- (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPBlockingQueryInfo *)queryInfo
{
if (queryInfo.type == FetchBlockingList)
{
// Blocking List Query Response:
//
// <iq type='result' id='blocklist1'>
// <blocklist xmlns='urn:xmpp:blocking'>
// <item jid='romeo@montague.net'/>
// <item jid='iago@shakespeare.lit'/>
// </blocklist>
// </iq>
if ([[iq type] isEqualToString:@"result"])
{
NSXMLElement *blocklist = [iq elementForName:@"blocklist" xmlns:@"urn:xmpp:blocking"];
if (blocklist == nil) return;
NSArray *listItems = [blocklist elementsForName:@"item"];
for (NSXMLElement *listItem in listItems)
{
NSString *name = [listItem attributeStringValueForName:@"jid"];
if (name)
{
id value = blockingDict[name];
if (value == nil)
{
blockingDict[name] = [NSNull null];
}
}
}
[multicastDelegate xmppBlocking:self didReceivedBlockingList:[self blockingList]];
[self removeQueryInfo:queryInfo withKey:[iq elementID]];
}
else
{
[multicastDelegate xmppBlocking:self didNotReceivedBlockingListDueToError:iq];
}
}
else if (queryInfo.type == BlockUser)
{
// <iq type='result' id='block1'/>
if ([[iq type] isEqualToString:@"result"])
{
[self removeQueryInfo:queryInfo withKey:[iq elementID]];
[multicastDelegate xmppBlocking:self didBlockJID:queryInfo.blockingXMPPJID];
}
else
{
[blockingDict removeObjectForKey:[queryInfo.blockingXMPPJID full]];
[multicastDelegate xmppBlocking:self didNotBlockJID:queryInfo.blockingXMPPJID error:iq];
}
}
else if (queryInfo.type == UnblockUser)
{
// <iq type='result' id='unblock1'/>
if ([[iq type] isEqualToString:@"result"])
{
[self removeQueryInfo:queryInfo withKey:[iq elementID]];
[multicastDelegate xmppBlocking:self didUnblockJID:queryInfo.blockingXMPPJID];
}
else
{
XMPPBlockingQueryInfo *queryInfo = pendingQueries[[iq elementID]];
id value = blockingDict[[queryInfo.blockingXMPPJID full]];
if (value == nil)
{
blockingDict[[queryInfo.blockingXMPPJID full]] = [NSNull null];
}
[multicastDelegate xmppBlocking:self didNotBlockJID:queryInfo.blockingXMPPJID error:iq];
}
}
else if (queryInfo.type == UnblockAll)
{
// <iq type='result' id='unblock2'/>
if ([[iq type] isEqualToString:@"result"])
{
[self removeQueryInfo:queryInfo withKey:[iq elementID]];
[multicastDelegate xmppBlocking:self didUnblockAllWithError:nil];
}
else
{
XMPPBlockingQueryInfo *queryInfo = pendingQueries[[iq elementID]];
id value = blockingDict[[queryInfo.blockingXMPPJID full]];
if (value == nil)
{
blockingDict[queryInfo.blockingXMPPJID] = [NSNull null];
}
[multicastDelegate xmppBlocking:self didNotUnblockAllDueToError:iq];
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPStream Delegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
if (self.autoRetrieveBlockingListItems)
{
[self retrieveBlockingListItems];
}
}
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
NSString *type = [iq type];
if ([type isEqualToString:@"set"])
{
NSXMLElement *block = [iq elementForName:@"block" xmlns:@"urn:xmpp:blocking"];
NSXMLElement *unblock = [iq elementForName:@"unblock" xmlns:@"urn:xmpp:blocking"];
if (block || unblock)
{
NSXMLElement *list = [block elementForName:@"item"];
if (!list)
{
list = [unblock elementForName:@"item"];
}
NSString *itemName = [list attributeStringValueForName:@"jid"];
if (itemName == nil)
{
return NO;
}
[multicastDelegate xmppBlocking:self didReceivePushWithBlockingList:itemName];
XMPPIQ *iqResponse = [XMPPIQ iqWithType:@"result" to:[iq from] elementID:[iq elementID]];
[xmppStream sendElement:iqResponse];
if (self.autoRetrieveBlockingListItems)
{
[self retrieveBlockingListItems];
}
return YES;
}
}
else
{
// This may be a response to a query we sent
XMPPBlockingQueryInfo *queryInfo = pendingQueries[[iq elementID]];
if (queryInfo)
{
[self processQueryResponse:iq withInfo:queryInfo];
return YES;
}
}
return NO;
}
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
{
// If there are any pending queries,
// they just failed due to the disconnection.
for (NSString *uuid in pendingQueries)
{
XMPPBlockingQueryInfo *queryInfo = pendingQueries[uuid];
[self processQuery:queryInfo withFailureCode:XMPPBlockingDisconnect];
}
// Clear the list of pending queries
[pendingQueries removeAllObjects];
// Maybe clear all stored blocking info
if (self.autoClearBlockingListInfo)
{
[self clearBlockingListInfo];
}
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPBlockingQueryInfo
@synthesize type;
@synthesize blockingXMPPJID;
@synthesize blockingListItems;
@synthesize timer;
- (id)initWithType:(XMPPBlockingQueryInfoType)aType jid:(XMPPJID *)jid items:(NSArray *)items
{
if ((self = [super init]))
{
type = aType;
blockingXMPPJID = [jid copy];
blockingListItems = [items copy];
}
return self;
}
- (void)cancel
{
if (timer)
{
dispatch_source_cancel(timer);
#if !OS_OBJECT_USE_OBJC
dispatch_release(timer);
#endif
timer = NULL;
}
}
- (void)dealloc
{
[self cancel];
}
+ (XMPPBlockingQueryInfo *)queryInfoWithType:(XMPPBlockingQueryInfoType)type
{
return [self queryInfoWithType:type jid:nil items:nil];
}
+ (XMPPBlockingQueryInfo *)queryInfoWithType:(XMPPBlockingQueryInfoType)type jid:(XMPPJID *)jid
{
return [self queryInfoWithType:type jid:jid items:nil];
}
+ (XMPPBlockingQueryInfo *)queryInfoWithType:(XMPPBlockingQueryInfoType)type jid:(XMPPJID *)jid items:(NSArray *)items
{
return [[XMPPBlockingQueryInfo alloc] initWithType:type jid:jid items:items];
}
@end