#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(); // // // 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]; } // // // // // 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]]; } // // // // // 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(); // // // 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: // // // // // // // 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) { // 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) { // 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) { // 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