#import "XMPP.h" #import "XMPPLogging.h" #import "XMPPPrivacy.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 XMPPPrivacyErrorDomain = @"XMPPPrivacyErrorDomain"; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef enum XMPPPrivacyQueryInfoType { FetchList, FetchRules, EditList, SetActiveList, SetDefaultList } XMPPPrivacyQueryInfoType; @interface XMPPPrivacyQueryInfo : NSObject { XMPPPrivacyQueryInfoType type; NSString *privacyListName; NSArray *privacyListItems; dispatch_source_t timer; } @property (nonatomic, readonly) XMPPPrivacyQueryInfoType type; @property (nonatomic, readonly) NSString *privacyListName; @property (nonatomic, readonly) NSArray *privacyListItems; @property (nonatomic, readwrite) dispatch_source_t timer; - (void)cancel; + (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type; + (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name; + (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name items:(NSArray *)items; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @interface XMPPPrivacy (/* Must be nameless for properties */) - (void)addQueryInfo:(XMPPPrivacyQueryInfo *)qi withKey:(NSString *)uuid; - (void)queryTimeout:(NSString *)uuid; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation XMPPPrivacy - (id)init { return [self initWithDispatchQueue:NULL]; } - (id)initWithDispatchQueue:(dispatch_queue_t)queue { if ((self = [super initWithDispatchQueue:queue])) { autoRetrievePrivacyListNames = YES; autoRetrievePrivacyListItems = YES; autoClearPrivacyListInfo = YES; privacyDict = [[NSMutableDictionary alloc] init]; activeListName = nil; defaultListName = nil; 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)autoRetrievePrivacyListNames { if (dispatch_get_specific(moduleQueueTag)) { return autoRetrievePrivacyListNames; } else { __block BOOL result; dispatch_sync(moduleQueue, ^{ result = autoRetrievePrivacyListNames; }); return result; } } - (void)setAutoRetrievePrivacyListNames:(BOOL)flag { dispatch_block_t block = ^{ autoRetrievePrivacyListNames = flag; }; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_async(moduleQueue, block); } - (BOOL)autoRetrievePrivacyListItems { if (dispatch_get_specific(moduleQueueTag)) { return autoRetrievePrivacyListItems; } else { __block BOOL result; dispatch_sync(moduleQueue, ^{ result = autoRetrievePrivacyListItems; }); return result; } } - (void)setAutoRetrievePrivacyListItems:(BOOL)flag { dispatch_block_t block = ^{ autoRetrievePrivacyListItems = flag; }; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_async(moduleQueue, block); } - (BOOL)autoClearPrivacyListInfo { if (dispatch_get_specific(moduleQueueTag)) { return autoClearPrivacyListInfo; } else { __block BOOL result; dispatch_sync(moduleQueue, ^{ result = autoClearPrivacyListInfo; }); return result; } } - (void)setAutoClearPrivacyListInfo:(BOOL)flag { dispatch_block_t block = ^{ autoClearPrivacyListInfo = flag; }; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_async(moduleQueue, block); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Public API //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)retrieveListNames { XMPPLogTrace(); // // // NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"]; NSString *uuid = [xmppStream generateUUID]; XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:uuid child:query]; [xmppStream sendElement:iq]; XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:FetchList]; [self addQueryInfo:qi withKey:uuid]; } - (void)retrieveListWithName:(NSString *)privacyListName { XMPPLogTrace(); if (privacyListName == nil) return; // // // // // NSXMLElement *list = [NSXMLElement elementWithName:@"list"]; [list addAttributeWithName:@"name" stringValue:privacyListName]; NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"]; [query addChild:list]; NSString *uuid = [xmppStream generateUUID]; XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:uuid child:query]; [xmppStream sendElement:iq]; XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:FetchRules name:privacyListName]; [self addQueryInfo:qi withKey:uuid]; } - (void)clearPrivacyListInfo { XMPPLogTrace(); if (dispatch_get_specific(moduleQueueTag)) { [privacyDict removeAllObjects]; } else { dispatch_async(moduleQueue, ^{ @autoreleasepool { [privacyDict removeAllObjects]; }}); } } - (NSArray *)listNames { if (dispatch_get_specific(moduleQueueTag)) { return [privacyDict allKeys]; } else { __block NSArray *result; dispatch_sync(moduleQueue, ^{ @autoreleasepool { result = [[privacyDict allKeys] copy]; }}); return result; } } - (NSArray *)listWithName:(NSString *)privacyListName { NSArray* (^block)() = ^ NSArray* () { id result = privacyDict[privacyListName]; if (result == [NSNull null]) // Not fetched yet return nil; else return (NSArray *)result; }; // ExecuteVoidBlock(moduleQueue, block); // ExecuteNonVoidBlock(moduleQueue, block, NSArray*) if (dispatch_get_specific(moduleQueueTag)) { return block(); } else { __block NSArray *result; dispatch_sync(moduleQueue, ^{ @autoreleasepool { result = block(); }}); return result; } } - (NSString *)activeListName { return activeListName; } - (NSArray *)activeList { return [self listWithName:activeListName]; } - (void)setActiveListName:(NSString *)privacyListName { // Setting active list: // // // // // // // // Decline the use of active lists: // // // // // // NSXMLElement *active = [NSXMLElement elementWithName:@"active"]; if (privacyListName) { [active addAttributeWithName:@"name" stringValue:privacyListName]; } NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"]; [query addChild:active]; NSString *uuid = [xmppStream generateUUID]; XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query]; [xmppStream sendElement:iq]; XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:SetActiveList name:privacyListName]; [self addQueryInfo:qi withKey:uuid]; } - (NSString *)defaultListName { return defaultListName; } - (NSArray *)defaultList { return [self listWithName:defaultListName]; } - (void)setDefaultListName:(NSString *)privacyListName { // Setting default list: // // // // // // // // Decline the use of default list: // // // // // // NSXMLElement *dfault = [NSXMLElement elementWithName:@"default"]; if (privacyListName) { [dfault addAttributeWithName:@"name" stringValue:privacyListName]; } NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"]; [query addChild:dfault]; NSString *uuid = [xmppStream generateUUID]; XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query]; [xmppStream sendElement:iq]; XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:SetDefaultList name:privacyListName]; [self addQueryInfo:qi withKey:uuid]; } - (void)setListWithName:(NSString *)privacyListName items:(NSArray *)items { // Edit a privacy list: // // // // // // // // // // // // // Remove a privacy list: // // // // // // NSXMLElement *list = [NSXMLElement elementWithName:@"list"]; [list addAttributeWithName:@"name" stringValue:privacyListName]; if (items && ([items count] > 0)) { [list setChildren:items]; } NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"]; [query addChild:list]; NSString *uuid = [xmppStream generateUUID]; XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query]; [xmppStream sendElement:iq]; XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:EditList name:privacyListName items:items]; [self addQueryInfo:qi withKey:uuid]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Query Processing //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)addQueryInfo:(XMPPPrivacyQueryInfo *)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:(XMPPPrivacyQueryInfo *)queryInfo withKey:(NSString *)uuid { // Invalidate timer [queryInfo cancel]; // Remove from dictionary [pendingQueries removeObjectForKey:uuid]; } - (void)processQuery:(XMPPPrivacyQueryInfo *)queryInfo withFailureCode:(XMPPPrivacyErrorCode)errorCode { NSError *error = [NSError errorWithDomain:XMPPPrivacyErrorDomain code:errorCode userInfo:nil]; if (queryInfo.type == FetchList) { [multicastDelegate xmppPrivacy:self didNotReceiveListNamesDueToError:error]; } else if (queryInfo.type == FetchRules) { [multicastDelegate xmppPrivacy:self didNotReceiveListWithName:queryInfo.privacyListName error:error]; } else if (queryInfo.type == EditList) { [multicastDelegate xmppPrivacy:self didNotSetListWithName:queryInfo.privacyListName error:error]; } else if (queryInfo.type == SetActiveList) { [multicastDelegate xmppPrivacy:self didNotSetActiveListName:queryInfo.privacyListName error:error]; } else if (queryInfo.type == SetDefaultList) { [multicastDelegate xmppPrivacy:self didNotSetDefaultListName:queryInfo.privacyListName error:error]; } } - (void)queryTimeout:(NSString *)uuid { XMPPPrivacyQueryInfo *queryInfo = privacyDict[uuid]; if (queryInfo) { [self processQuery:queryInfo withFailureCode:XMPPPrivacyQueryTimeout]; [self removeQueryInfo:queryInfo withKey:uuid]; } } NSInteger sortItems(id itemOne, id itemTwo, void *context) { NSXMLElement *item1 = (NSXMLElement *)itemOne; NSXMLElement *item2 = (NSXMLElement *)itemTwo; NSString *orderStr1 = [item1 attributeStringValueForName:@"order"]; NSString *orderStr2 = [item2 attributeStringValueForName:@"order"]; NSUInteger order1; BOOL parse1 = [NSNumber xmpp_parseString:orderStr1 intoNSUInteger:&order1]; NSUInteger order2; BOOL parse2 = [NSNumber xmpp_parseString:orderStr2 intoNSUInteger:&order2]; if (parse1) { if (parse2) { // item1 = valid // item2 = valid if (order1 < order2) return NSOrderedAscending; if (order1 > order2) return NSOrderedDescending; return NSOrderedSame; } else { // item1 = valid // item2 = invalid return NSOrderedAscending; } } else if (parse2) { // item1 = invalid // item2 = valid return NSOrderedDescending; } else { // item1 = invalid // item2 = invalid return NSOrderedSame; } } - (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPPrivacyQueryInfo *)queryInfo { if (queryInfo.type == FetchList) { // Privacy List Names Query Response: // // // // // // // // // // if ([[iq type] isEqualToString:@"result"]) { NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"]; if (query == nil) return; NSXMLElement *active = [query elementForName:@"active"]; activeListName = [[active attributeStringValueForName:@"name"] copy]; NSXMLElement *dfault = [query elementForName:@"default"]; defaultListName = [[dfault attributeStringValueForName:@"name"] copy]; NSArray *listNames = [query elementsForName:@"list"]; for (NSXMLElement *listName in listNames) { NSString *name = [listName attributeStringValueForName:@"name"]; if (name) { id value = privacyDict[name]; if (value == nil) { privacyDict[name] = [NSNull null]; } } } [multicastDelegate xmppPrivacy:self didReceiveListNames:[self listNames]]; } else { [multicastDelegate xmppPrivacy:self didNotReceiveListNamesDueToError:iq]; } } else if (queryInfo.type == FetchRules) { // Privacy List Rules Query Response (success): // // // // // // // // // // // // Privacy List Rules Query Response (error): // // // // // // // // // if ([[iq type] isEqualToString:@"result"]) { NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"]; if (query == nil) return; NSXMLElement *list = [query elementForName:@"list"]; if (list == nil) return; NSArray *items = [[list elementsForName:@"item"] sortedArrayUsingFunction:sortItems context:NULL]; if (items == nil) { items = [NSArray array]; } privacyDict[queryInfo.privacyListName] = items; [multicastDelegate xmppPrivacy:self didReceiveListWithName:queryInfo.privacyListName items:items]; } else { [multicastDelegate xmppPrivacy:self didNotReceiveListWithName:queryInfo.privacyListName error:iq]; } } else if (queryInfo.type == EditList) { // Privacy List Add/Edit/Remove Response (success): // // // // Note: The result iq does NOT have a query child. if ([[iq type] isEqualToString:@"result"]) { NSArray *items = [[queryInfo privacyListItems] sortedArrayUsingFunction:sortItems context:NULL]; if (items == nil) { items = [NSArray array]; } privacyDict[queryInfo.privacyListName] = items; [multicastDelegate xmppPrivacy:self didSetListWithName:queryInfo.privacyListName]; } else { [multicastDelegate xmppPrivacy:self didNotSetListWithName:queryInfo.privacyListName error:iq]; } } else if (queryInfo.type == SetActiveList) { // Change of active list (success): // // // // // Change of active list (error): // // // // // // // // // // // // Note: The result iq does NOT have a query child. // Note: The success could also mean we declined the use of an active list. if ([[iq type] isEqualToString:@"result"]) { activeListName = [[queryInfo privacyListName] copy]; [multicastDelegate xmppPrivacy:self didSetActiveListName:queryInfo.privacyListName]; } else { [multicastDelegate xmppPrivacy:self didNotSetActiveListName:queryInfo.privacyListName error:iq]; } } else if (queryInfo.type == SetDefaultList) { // Change of default list (success): // // // // // Change of default list (error): // // // // // // // // // // // // Note: The result iq does NOT have a query child. // Note: The success could also mean we declined the use of a default list. if ([[iq type] isEqualToString:@"result"]) { defaultListName = [[queryInfo privacyListName] copy]; [multicastDelegate xmppPrivacy:self didSetDefaultListName:queryInfo.privacyListName]; } else { [multicastDelegate xmppPrivacy:self didNotSetDefaultListName:queryInfo.privacyListName error:iq]; } } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark XMPPStream Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender { if (self.autoRetrievePrivacyListNames) { [self retrieveListNames]; } } - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { NSString *type = [iq type]; if ([type isEqualToString:@"set"]) { NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"]; if (query) { // Privacy List Push: // // // // // // // // // Push response: // // NSXMLElement *list = [query elementForName:@"list"]; NSString *listName = [list attributeStringValueForName:@"name"]; if (listName == nil) { return NO; } [multicastDelegate xmppPrivacy:self didReceivePushWithListName:listName]; XMPPIQ *iqResponse = [XMPPIQ iqWithType:@"result" to:[iq from] elementID:[iq elementID]]; [xmppStream sendElement:iqResponse]; if (self.autoRetrievePrivacyListItems) { [self retrieveListWithName:listName]; } return YES; } } else { // This may be a response to a query we sent XMPPPrivacyQueryInfo *queryInfo = pendingQueries[[iq elementID]]; if (queryInfo) { [self processQueryResponse:iq withInfo:queryInfo]; if (queryInfo.type == FetchList && self.autoRetrievePrivacyListItems) { for (NSString *privacyListName in privacyDict) { id privacyListItems = privacyDict[privacyListName]; if (privacyListItems == [NSNull null]) { [self retrieveListWithName:privacyListName]; } } } 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) { XMPPPrivacyQueryInfo *queryInfo = privacyDict[uuid]; [self processQuery:queryInfo withFailureCode:XMPPPrivacyDisconnect]; } // Clear the list of pending queries [pendingQueries removeAllObjects]; // Maybe clear all stored privacy info if (self.autoClearPrivacyListInfo) { [self clearPrivacyListInfo]; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Privacy Items //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + (NSXMLElement *)privacyItemWithAction:(NSString *)action order:(NSUInteger)orderValue { return [self privacyItemWithType:nil value:nil action:action order:orderValue]; } + (NSXMLElement *)privacyItemWithType:(NSString *)type value:(NSString *)value action:(NSString *)action order:(NSUInteger)orderValue { NSString *order = [[NSString alloc] initWithFormat:@"%lu", (unsigned long)orderValue]; // // [] // [] // [] // [] // NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; if (type) [item addAttributeWithName:@"type" stringValue:type]; if (value) [item addAttributeWithName:@"value" stringValue:value]; [item addAttributeWithName:@"action" stringValue:action]; [item addAttributeWithName:@"order" stringValue:order]; return item; } + (void)blockIQs:(NSXMLElement *)privacyItem { [privacyItem addChild:[NSXMLElement elementWithName:@"iq"]]; } + (void)blockMessages:(NSXMLElement *)privacyItem { [privacyItem addChild:[NSXMLElement elementWithName:@"message"]]; } + (void)blockPresenceIn:(NSXMLElement *)privacyItem { [privacyItem addChild:[NSXMLElement elementWithName:@"presence-in"]]; } + (void)blockPresenceOut:(NSXMLElement *)privacyItem { [privacyItem addChild:[NSXMLElement elementWithName:@"presence-out"]]; } @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation XMPPPrivacyQueryInfo @synthesize type; @synthesize privacyListName; @synthesize privacyListItems; @synthesize timer; - (id)initWithType:(XMPPPrivacyQueryInfoType)aType name:(NSString *)name items:(NSArray *)items { if ((self = [super init])) { type = aType; privacyListName = [name copy]; privacyListItems = [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]; } + (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type { return [self queryInfoWithType:type name:nil items:nil]; } + (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name { return [self queryInfoWithType:type name:name items:nil]; } + (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name items:(NSArray *)items { return [[XMPPPrivacyQueryInfo alloc] initWithType:type name:name items:items]; } @end