Skip to content
This repository
Browse code

Adding XEP-0016 - privacy lists

  • Loading branch information...
commit 28c3e77a07f097cf15187e0bd808dd6711b4dc35 1 parent aa2e3d1
Robbie Hanson authored

Showing 2 changed files with 1,238 additions and 0 deletions. Show diff stats Hide diff stats

  1. +229 0 Extensions/XEP-0016/XMPPPrivacy.h
  2. +1,009 0 Extensions/XEP-0016/XMPPPrivacy.m
229 Extensions/XEP-0016/XMPPPrivacy.h
... ... @@ -0,0 +1,229 @@
  1 +#import <Foundation/Foundation.h>
  2 +#import "XMPPModule.h"
  3 +
  4 +#if TARGET_OS_IPHONE
  5 + #import "DDXML.h"
  6 +#endif
  7 +
  8 +@class XMPPIQ;
  9 +
  10 +extern NSString *const XMPPPrivacyErrorDomain;
  11 +
  12 +typedef enum XMPPPrivacyErrorCode
  13 +{
  14 + XMPPPrivacyQueryTimeout, // No response from server
  15 + XMPPPrivacyDisconnect, // XMPP disconnection
  16 +
  17 +} XMPPPrivacyErrorCode;
  18 +
  19 +
  20 +
  21 +@interface XMPPPrivacy : XMPPModule
  22 +{
  23 + BOOL autoRetrievePrivacyListNames;
  24 + BOOL autoRetrievePrivacyListItems;
  25 + BOOL autoClearPrivacyListInfo;
  26 +
  27 + NSMutableDictionary *privacyDict;
  28 + NSString *activeListName;
  29 + NSString *defaultListName;
  30 +
  31 + NSMutableDictionary *pendingQueries;
  32 +}
  33 +
  34 +/**
  35 + * Whether or not the module should automatically retrieve the privacy list names.
  36 + * If this property is enabled, then the list is automatically fetched when the client signs in.
  37 + *
  38 + * The default value is YES.
  39 +**/
  40 +@property (readwrite, assign) BOOL autoRetrievePrivacyListNames;
  41 +
  42 +/**
  43 + * Whether or not the module should automatically retrieve the privacy list rules.
  44 + * If this property is enabled, then the rules for each privacy list are automatically fetched.
  45 + *
  46 + * In other words, if the privacy list names are fetched (either automatically, or via retrieveListNames),
  47 + * then the module will automatically fetch the associated rules.
  48 + * It will also update the set of rules if we receive a "privacy list push"
  49 + * from the server that another resource has changed one of the privacy lists.
  50 + *
  51 + * The default value is YES.
  52 +**/
  53 +@property (readwrite, assign) BOOL autoRetrievePrivacyListItems;
  54 +
  55 +/**
  56 + * Whether the module should automatically clear the privacy list info when the client disconnects.
  57 + *
  58 + * As per the XEP, if there are multiple resources signed in for the user,
  59 + * and one resource makes changes to a privacy list, all other resources are "pushed" a notification.
  60 + * However, if our client is disconnected when another resource makes the changes,
  61 + * then the only way we can find out about the changes are to redownload the privacy lists.
  62 + *
  63 + * It is recommended to clear the privacy list to assure we have the correct info.
  64 + * However, there may be specific situations in which an xmpp client can be sure the privacy list won't change.
  65 + *
  66 + * The default value is YES.
  67 +**/
  68 +@property (readwrite, assign) BOOL autoClearPrivacyListInfo;
  69 +
  70 +/**
  71 + * Manual fetch of list names and rules, and manual control over when to clear stored info.
  72 +**/
  73 +- (void)retrieveListNames;
  74 +- (void)retrieveListWithName:(NSString *)privacyListName;
  75 +- (void)clearPrivacyListInfo;
  76 +
  77 +/**
  78 + * Returns the privacy list names.
  79 + * This is an array of strings.
  80 +**/
  81 +- (NSArray *)listNames;
  82 +
  83 +/**
  84 + * Returns the privacy list rules/items for the given list name.
  85 + *
  86 + * The result is an array or privacy items (NSXMLElement's).
  87 + * The array is sorted according to order, where the item with the smallest 'order' is first in the array.
  88 +**/
  89 +- (NSArray *)listWithName:(NSString *)privacyListName;
  90 +
  91 +/**
  92 + * Returns information about the active list.
  93 + * If there is no active list, the methods return nil.
  94 + *
  95 + * The activeList method is a convenience method for [modPriv listWithName:[modPriv activeListName]]
  96 +**/
  97 +- (NSString *)activeListName;
  98 +- (NSArray *)activeList;
  99 +
  100 +/**
  101 + * Returns information about the default list.
  102 + * If there is no default list, the methods return nil.
  103 + *
  104 + * The defaultList method is a convenience method for [modPriv listWithName:[modPriv defaultListName]]
  105 +**/
  106 +- (NSString *)defaultListName;
  107 +- (NSArray *)defaultList;
  108 +
  109 +/**
  110 + * Changes the client's active privacy list to the list with the given name.
  111 + * The privacy list name must match the name of an existing privacy list.
  112 + *
  113 + * To decline the use of an active list simply pass nil to this method.
  114 + *
  115 + * Once the server has acknowledged the change,
  116 + * the delegate method xmppPrivacy:didSetActiveListName: will be invoked.
  117 + * If the server is unable to process the change (e.g. invalid list name),
  118 + * the delegate method xmppPrivacy:didNotSetActiveListName:error: will be invoked.
  119 + *
  120 + * The methods activeListName and activeList will update after the server acknowledges the change.
  121 +**/
  122 +- (void)setActiveListName:(NSString *)privacyListName;
  123 +
  124 +/**
  125 + * Changes the client's default privacy list to the list with the given name.
  126 + * The privacy list name must match the name of an existing privacy list.
  127 + *
  128 + * To decline the use of a default list simply pass nil to this method.
  129 + *
  130 + * Once the server has acknowledged the change,
  131 + * the delegate method xmppPrivacy:didSetDefaultListName: will be invoked.
  132 + * If the server is unable to process the change (e.g. invalid list name, in use by another resource),
  133 + * the delegate method xmppPrivacy:didNotSetDefaultListName:error: will be invoked.
  134 + *
  135 + * The methods defaultListName and defaultList will update after the server acknowledges the change.
  136 +**/
  137 +- (void)setDefaultListName:(NSString *)privacyListName;
  138 +
  139 +/**
  140 + * Adds/Edits/Removes a privacy list with the given name.
  141 + * The given array should contain only privacy items (NSXMLElement's).
  142 + *
  143 + * To remove a privacy list, invoke this method will a nil or empty items parameter.
  144 +**/
  145 +- (void)setListWithName:(NSString *)privacyListName items:(NSArray *)items;
  146 +
  147 +/**
  148 + * The following are convenience methods to create privacy item rules.
  149 + * A quick refresher from the XEP-0016 documentation is provided below.
  150 + *
  151 + * The 'type' attribute is OPTIONAL, and must be one of: jid|group|subscription
  152 + *
  153 + * If the 'type' is 'jid', then the 'value' must contain a valid JID.
  154 + * JIDs are to be matched in the following order:
  155 + *
  156 + * - <user@domain/resource> (only that resource matches)
  157 + * - <user@domain> (any resource matches)
  158 + * - <domain/resource> (only that resource matches)
  159 + * - <domain> (the domain itself matches, as does any user@domain or domain/resource)
  160 + *
  161 + * If the 'type' is 'group', then the 'value' should contain the name of a group in the user's roster.
  162 + *
  163 + * If the 'type' is 'subscription', then the 'value' must be one of: both|to|from|none
  164 + *
  165 + * The 'action' attribute is MANDATORY and must be one of: allow|deny
  166 + *
  167 + * The 'order' attribute is MANDATORY and must be a non-negative integer that is unique among all items in the list.
  168 + * List items are processed by the server according to the 'order' attribute in ascending order. (0 before 1, etc)
  169 + * Once the server matches a privacy item in the list, it obeys the item and ceases processing.
  170 + *
  171 + * The privacy item may contain one or more child elements that specify more granular blocking control:
  172 + *
  173 + * - <message/> (blocks incoming message stanzas)
  174 + * - <iq/> (blocks incoming IQ stanzas)
  175 + * - <presence-in/> (blocks incoming presence notifications)
  176 + * - <presence-out/> (blocks outgoing presence notifications)
  177 +**/
  178 ++ (NSXMLElement *)privacyItemWithAction:(NSString *)action order:(NSUInteger)order;
  179 ++ (NSXMLElement *)privacyItemWithType:(NSString *)type
  180 + value:(NSString *)value
  181 + action:(NSString *)action
  182 + order:(NSUInteger)order;
  183 +
  184 ++ (void)blockIQs:(NSXMLElement *)privacyItem;
  185 ++ (void)blockMessages:(NSXMLElement *)privacyItem;
  186 ++ (void)blockPresenceIn:(NSXMLElement *)privacyItem;
  187 ++ (void)blockPresenceOut:(NSXMLElement *)privacyItem;
  188 +
  189 +@end
  190 +
  191 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  192 +#pragma mark -
  193 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  194 +
  195 +@protocol XMPPPrivacyDelegate
  196 +@optional
  197 +
  198 +/**
  199 + * The following delegate methods correspond almost exactly with the action methods of the class.
  200 + * There are a few possible ways in which an action could fail:
  201 + *
  202 + * 1. We receive an error response from the server.
  203 + * 2. We receive no response from the server, and the query times out.
  204 + * 3. We get disconnected before we receive the response.
  205 + *
  206 + * In case number 1, the error will be an XMPPIQ of type='error'.
  207 + *
  208 + * In case number 2 or 3, the error will be an NSError
  209 + * with domain=XMPPPrivacyErrorDomain and code from the XMPPPrivacyErrorCode enumeration.
  210 +**/
  211 +
  212 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didReceiveListNames:(NSArray *)listNames;
  213 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotReceiveListNamesDueToError:(id)error;
  214 +
  215 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didReceiveListWithName:(NSString *)name items:(NSArray *)items;
  216 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotReceiveListWithName:(NSString *)name error:(id)error;
  217 +
  218 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didReceivePushWithListName:(NSString *)name;
  219 +
  220 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didSetActiveListName:(NSString *)name;
  221 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotSetActiveListName:(NSString *)name error:(id)error;
  222 +
  223 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didSetDefaultListName:(NSString *)name;
  224 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotSetDefaultListName:(NSString *)name error:(id)error;
  225 +
  226 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didSetListWithName:(NSString *)name;
  227 +- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotSetListWithName:(NSString *)name error:(id)error;
  228 +
  229 +@end
1,009 Extensions/XEP-0016/XMPPPrivacy.m
... ... @@ -0,0 +1,1009 @@
  1 +#import "XMPP.h"
  2 +#import "XMPPLogging.h"
  3 +#import "XMPPPrivacy.h"
  4 +
  5 +// Log levels: off, error, warn, info, verbose
  6 +// Log flags: trace
  7 +#ifdef DEBUG
  8 + static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE; // | XMPP_LOG_FLAG_TRACE;
  9 +#else
  10 + static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
  11 +#endif
  12 +
  13 +#define QUERY_TIMEOUT 30.0 // NSTimeInterval (double) = seconds
  14 +
  15 +NSString *const XMPPPrivacyErrorDomain = @"XMPPPrivacyErrorDomain";
  16 +
  17 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  18 +#pragma mark -
  19 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  20 +
  21 +typedef enum XMPPPrivacyQueryInfoType {
  22 + FetchList,
  23 + FetchRules,
  24 + EditList,
  25 + SetActiveList,
  26 + SetDefaultList
  27 +
  28 +} XMPPPrivacyQueryInfoType;
  29 +
  30 +@interface XMPPPrivacyQueryInfo : NSObject
  31 +{
  32 + XMPPPrivacyQueryInfoType type;
  33 + NSString *privacyListName;
  34 + NSArray *privacyListItems;
  35 +
  36 + dispatch_source_t timer;
  37 +}
  38 +
  39 +@property (nonatomic, readonly) XMPPPrivacyQueryInfoType type;
  40 +@property (nonatomic, readonly) NSString *privacyListName;
  41 +@property (nonatomic, readonly) NSArray *privacyListItems;
  42 +
  43 +@property (nonatomic, readwrite, retain) dispatch_source_t timer;
  44 +
  45 ++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type;
  46 ++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name;
  47 ++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name items:(NSArray *)items;
  48 +
  49 +@end
  50 +
  51 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  52 +#pragma mark -
  53 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  54 +
  55 +@interface XMPPPrivacy (/* Must be nameless for properties */)
  56 +
  57 +- (void)addQueryInfo:(XMPPPrivacyQueryInfo *)qi withKey:(NSString *)uuid;
  58 +
  59 +@end
  60 +
  61 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  62 +#pragma mark -
  63 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  64 +
  65 +@implementation XMPPPrivacy
  66 +
  67 +- (id)init
  68 +{
  69 + return [self initWithDispatchQueue:NULL];
  70 +}
  71 +
  72 +- (id)initWithDispatchQueue:(dispatch_queue_t)queue
  73 +{
  74 + if ((self = [super initWithDispatchQueue:queue]))
  75 + {
  76 + autoRetrievePrivacyListNames = YES;
  77 + autoRetrievePrivacyListItems = YES;
  78 + autoClearPrivacyListInfo = YES;
  79 +
  80 + privacyDict = [[NSMutableDictionary alloc] init];
  81 + activeListName = nil;
  82 + defaultListName = nil;
  83 +
  84 + pendingQueries = [[NSMutableDictionary alloc] init];
  85 + }
  86 + return self;
  87 +}
  88 +
  89 +- (BOOL)activate:(XMPPStream *)aXmppStream
  90 +{
  91 + if ([super activate:aXmppStream])
  92 + {
  93 + // Reserved for possible future use.
  94 +
  95 + return YES;
  96 + }
  97 +
  98 + return NO;
  99 +}
  100 +
  101 +- (void)deactivate
  102 +{
  103 + // Reserved for possible future use.
  104 +
  105 + [super deactivate];
  106 +}
  107 +
  108 +- (void)dealloc
  109 +{
  110 + [privacyDict release];
  111 + [activeListName release];
  112 + [defaultListName release];
  113 + [pendingQueries release];
  114 + [super dealloc];
  115 +}
  116 +
  117 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  118 +#pragma mark Properties
  119 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  120 +
  121 +- (BOOL)autoRetrievePrivacyListNames
  122 +{
  123 + if (dispatch_get_current_queue() == moduleQueue)
  124 + {
  125 + return autoRetrievePrivacyListNames;
  126 + }
  127 + else
  128 + {
  129 + __block BOOL result;
  130 +
  131 + dispatch_sync(moduleQueue, ^{
  132 + result = autoRetrievePrivacyListNames;
  133 + });
  134 +
  135 + return result;
  136 + }
  137 +}
  138 +
  139 +- (void)setAutoRetrievePrivacyListNames:(BOOL)flag
  140 +{
  141 + dispatch_block_t block = ^{
  142 +
  143 + autoRetrievePrivacyListNames = flag;
  144 + };
  145 +
  146 + if (dispatch_get_current_queue() == moduleQueue)
  147 + block();
  148 + else
  149 + dispatch_async(moduleQueue, block);
  150 +}
  151 +
  152 +- (BOOL)autoRetrievePrivacyListItems
  153 +{
  154 + if (dispatch_get_current_queue() == moduleQueue)
  155 + {
  156 + return autoRetrievePrivacyListItems;
  157 + }
  158 + else
  159 + {
  160 + __block BOOL result;
  161 +
  162 + dispatch_sync(moduleQueue, ^{
  163 + result = autoRetrievePrivacyListItems;
  164 + });
  165 +
  166 + return result;
  167 + }
  168 +}
  169 +
  170 +- (void)setAutoRetrievePrivacyListItems:(BOOL)flag
  171 +{
  172 + dispatch_block_t block = ^{
  173 +
  174 + autoRetrievePrivacyListItems = flag;
  175 + };
  176 +
  177 + if (dispatch_get_current_queue() == moduleQueue)
  178 + block();
  179 + else
  180 + dispatch_async(moduleQueue, block);
  181 +}
  182 +
  183 +- (BOOL)autoClearPrivacyListInfo
  184 +{
  185 + if (dispatch_get_current_queue() == moduleQueue)
  186 + {
  187 + return autoClearPrivacyListInfo;
  188 + }
  189 + else
  190 + {
  191 + __block BOOL result;
  192 +
  193 + dispatch_sync(moduleQueue, ^{
  194 + result = autoClearPrivacyListInfo;
  195 + });
  196 +
  197 + return result;
  198 + }
  199 +}
  200 +
  201 +- (void)setAutoClearPrivacyListInfo:(BOOL)flag
  202 +{
  203 + dispatch_block_t block = ^{
  204 +
  205 + autoClearPrivacyListInfo = flag;
  206 + };
  207 +
  208 + if (dispatch_get_current_queue() == moduleQueue)
  209 + block();
  210 + else
  211 + dispatch_async(moduleQueue, block);
  212 +}
  213 +
  214 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  215 +#pragma mark Public API
  216 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  217 +
  218 +- (void)retrieveListNames
  219 +{
  220 + XMPPLogTrace();
  221 +
  222 + // <iq type='get' id='abc123'>
  223 + // <query xmlns='jabber:iq:privacy'/>
  224 + // </iq>
  225 +
  226 + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
  227 +
  228 + NSString *uuid = [xmppStream generateUUID];
  229 + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:uuid child:query];
  230 +
  231 + [xmppStream sendElement:iq];
  232 +
  233 + XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:FetchList];
  234 + [self addQueryInfo:qi withKey:uuid];
  235 +}
  236 +
  237 +- (void)retrieveListWithName:(NSString *)privacyListName
  238 +{
  239 + XMPPLogTrace();
  240 +
  241 + if (privacyListName == nil) return;
  242 +
  243 + // <iq type='get' id='abc123'>
  244 + // <query xmlns='jabber:iq:privacy'>
  245 + // <list name='public'/>
  246 + // </query>
  247 + // </iq>
  248 +
  249 + NSXMLElement *list = [NSXMLElement elementWithName:@"list"];
  250 + [list addAttributeWithName:@"name" stringValue:privacyListName];
  251 +
  252 + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
  253 + [query addChild:list];
  254 +
  255 + NSString *uuid = [xmppStream generateUUID];
  256 + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:uuid child:query];
  257 +
  258 + [xmppStream sendElement:iq];
  259 +
  260 + XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:FetchRules name:privacyListName];
  261 + [self addQueryInfo:qi withKey:uuid];
  262 +}
  263 +
  264 +- (void)clearPrivacyListInfo
  265 +{
  266 + XMPPLogTrace();
  267 +
  268 + if (dispatch_get_current_queue() == moduleQueue)
  269 + {
  270 + [privacyDict removeAllObjects];
  271 + }
  272 + else
  273 + {
  274 + dispatch_async(moduleQueue, ^{
  275 + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  276 +
  277 + [privacyDict removeAllObjects];
  278 +
  279 + [pool drain];
  280 + });
  281 + }
  282 +}
  283 +
  284 +- (NSArray *)listNames
  285 +{
  286 + if (dispatch_get_current_queue() == moduleQueue)
  287 + {
  288 + return [privacyDict allKeys];
  289 + }
  290 + else
  291 + {
  292 + __block NSArray *result;
  293 +
  294 + dispatch_sync(moduleQueue, ^{
  295 + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  296 +
  297 + result = [[privacyDict allKeys] copy];
  298 +
  299 + [pool drain];
  300 + });
  301 +
  302 + return [result autorelease];
  303 + }
  304 +}
  305 +
  306 +- (NSArray *)listWithName:(NSString *)privacyListName
  307 +{
  308 + NSArray* (^block)() = ^ NSArray* () {
  309 +
  310 + id result = [privacyDict objectForKey:privacyListName];
  311 +
  312 + if (result == [NSNull null]) // Not fetched yet
  313 + return nil;
  314 + else
  315 + return (NSArray *)result;
  316 + };
  317 +
  318 + // ExecuteVoidBlock(moduleQueue, block);
  319 + // ExecuteNonVoidBlock(moduleQueue, block, NSArray*)
  320 +
  321 + int n = MIN(5, 6);
  322 +
  323 + if (dispatch_get_current_queue() == moduleQueue)
  324 + {
  325 + return block();
  326 + }
  327 + else
  328 + {
  329 + __block NSArray *result;
  330 +
  331 + dispatch_sync(moduleQueue, ^{
  332 + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  333 +
  334 + result = [block() retain];
  335 +
  336 + [pool drain];
  337 + });
  338 +
  339 + return [result autorelease];
  340 + }
  341 +}
  342 +
  343 +- (NSString *)activeListName
  344 +{
  345 + return activeListName;
  346 +}
  347 +
  348 +- (NSArray *)activeList
  349 +{
  350 + return [self listWithName:activeListName];
  351 +}
  352 +
  353 +- (void)setActiveListName:(NSString *)privacyListName
  354 +{
  355 + // Setting active list:
  356 + //
  357 + // <iq type='set' id='active1'>
  358 + // <query xmlns='jabber:iq:privacy'>
  359 + // <active name='special'/>
  360 + // </query>
  361 + // </iq>
  362 + //
  363 + // Decline the use of active lists:
  364 + //
  365 + // <iq type='set' id='active3'>
  366 + // <query xmlns='jabber:iq:privacy'>
  367 + // <active/>
  368 + // </query>
  369 + // </iq>
  370 +
  371 + NSXMLElement *active = [NSXMLElement elementWithName:@"active"];
  372 + if (privacyListName)
  373 + {
  374 + [active addAttributeWithName:@"name" stringValue:privacyListName];
  375 + }
  376 +
  377 + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
  378 + [query addChild:active];
  379 +
  380 + NSString *uuid = [xmppStream generateUUID];
  381 + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query];
  382 +
  383 + [xmppStream sendElement:iq];
  384 +
  385 + XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:SetActiveList name:privacyListName];
  386 + [self addQueryInfo:qi withKey:uuid];
  387 +}
  388 +
  389 +- (NSString *)defaultListName
  390 +{
  391 + return defaultListName;
  392 +}
  393 +
  394 +- (NSArray *)defaultList
  395 +{
  396 + return [self listWithName:defaultListName];
  397 +}
  398 +
  399 +- (void)setDefaultListName:(NSString *)privacyListName
  400 +{
  401 + // Setting default list:
  402 + //
  403 + // <iq type='set' id='default1'>
  404 + // <query xmlns='jabber:iq:privacy'>
  405 + // <default name='special'/>
  406 + // </query>
  407 + // </iq>
  408 + //
  409 + // Decline the use of default list:
  410 + //
  411 + // <iq type='set' id='default2'>
  412 + // <query xmlns='jabber:iq:privacy'>
  413 + // <default/>
  414 + // </query>
  415 + // </iq>
  416 +
  417 + NSXMLElement *dfault = [NSXMLElement elementWithName:@"default"];
  418 + if (privacyListName)
  419 + {
  420 + [dfault addAttributeWithName:@"name" stringValue:privacyListName];
  421 + }
  422 +
  423 + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
  424 + [query addChild:dfault];
  425 +
  426 + NSString *uuid = [xmppStream generateUUID];
  427 + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query];
  428 +
  429 + [xmppStream sendElement:iq];
  430 +
  431 + XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:SetDefaultList name:privacyListName];
  432 + [self addQueryInfo:qi withKey:uuid];
  433 +}
  434 +
  435 +- (void)setListWithName:(NSString *)privacyListName items:(NSArray *)items
  436 +{
  437 + // Edit a privacy list:
  438 + //
  439 + // <iq type='set' id='edit1'>
  440 + // <query xmlns='jabber:iq:privacy'>
  441 + // <list name='public'>
  442 + // <item type='jid' value='tybalt@example.com' action='deny' order='3'/>
  443 + // <item type='jid' value='paris@example.org' action='deny' order='5'/>
  444 + // <item action='allow' order='68'/>
  445 + // </list>
  446 + // </query>
  447 + // </iq>
  448 + //
  449 + //
  450 + // Remove a privacy list:
  451 + //
  452 + // <iq type='set' id='remove1'>
  453 + // <query xmlns='jabber:iq:privacy'>
  454 + // <list name='private'/>
  455 + // </query>
  456 + // </iq>
  457 +
  458 + NSXMLElement *list = [NSXMLElement elementWithName:@"list"];
  459 + [list addAttributeWithName:@"name" stringValue:privacyListName];
  460 +
  461 + if (items && ([items count] > 0))
  462 + {
  463 + [list setChildren:items];
  464 + }
  465 +
  466 + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
  467 + [query addChild:list];
  468 +
  469 + NSString *uuid = [xmppStream generateUUID];
  470 + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query];
  471 +
  472 + [xmppStream sendElement:iq];
  473 +
  474 + XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:EditList name:privacyListName items:items];
  475 + [self addQueryInfo:qi withKey:uuid];
  476 +}
  477 +
  478 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  479 +#pragma mark Query Processing
  480 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  481 +
  482 +- (void)addQueryInfo:(XMPPPrivacyQueryInfo *)queryInfo withKey:(NSString *)uuid
  483 +{
  484 + // Setup timer
  485 + NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:QUERY_TIMEOUT
  486 + target:self
  487 + selector:@selector(queryTimeout:)
  488 + userInfo:uuid
  489 + repeats:NO];
  490 + queryInfo.timer = timer;
  491 +
  492 + // Add to dictionary
  493 + [pendingQueries setObject:queryInfo forKey:uuid];
  494 +}
  495 +
  496 +- (void)removeQueryInfo:(XMPPPrivacyQueryInfo *)queryInfo withKey:(NSString *)uuid
  497 +{
  498 + // Invalidate timer
  499 + [queryInfo.timer invalidate];
  500 +
  501 + // Remove from dictionary
  502 + [pendingQueries removeObjectForKey:uuid];
  503 +}
  504 +
  505 +- (void)processQuery:(XMPPPrivacyQueryInfo *)queryInfo withFailureCode:(XMPPPrivacyErrorCode)errorCode
  506 +{
  507 + NSError *error = [NSError errorWithDomain:XMPPPrivacyErrorDomain code:errorCode userInfo:nil];
  508 +
  509 + if (queryInfo.type == FetchList)
  510 + {
  511 + [multicastDelegate xmppPrivacy:self didNotReceiveListNamesDueToError:error];
  512 + }
  513 + else if (queryInfo.type == FetchRules)
  514 + {
  515 + [multicastDelegate xmppPrivacy:self didNotReceiveListWithName:queryInfo.privacyListName error:error];
  516 + }
  517 + else if (queryInfo.type == EditList)
  518 + {
  519 + [multicastDelegate xmppPrivacy:self didNotSetListWithName:queryInfo.privacyListName error:error];
  520 + }
  521 + else if (queryInfo.type == SetActiveList)
  522 + {
  523 + [multicastDelegate xmppPrivacy:self didNotSetActiveListName:queryInfo.privacyListName error:error];
  524 + }
  525 + else if (queryInfo.type == SetDefaultList)
  526 + {
  527 + [multicastDelegate xmppPrivacy:self didNotSetDefaultListName:queryInfo.privacyListName error:error];
  528 + }
  529 +}
  530 +
  531 +- (void)queryTimeout:(NSTimer *)timer
  532 +{
  533 + NSString *uuid = [timer userInfo];
  534 +
  535 + XMPPPrivacyQueryInfo *queryInfo = [privacyDict objectForKey:uuid];
  536 + if (queryInfo)
  537 + {
  538 + [self processQuery:queryInfo withFailureCode:XMPPPrivacyQueryTimeout];
  539 + [self removeQueryInfo:queryInfo withKey:uuid];
  540 + }
  541 +}
  542 +
  543 +NSInteger sortItems(id itemOne, id itemTwo, void *context)
  544 +{
  545 + NSXMLElement *item1 = (NSXMLElement *)itemOne;
  546 + NSXMLElement *item2 = (NSXMLElement *)itemTwo;
  547 +
  548 + NSString *orderStr1 = [item1 attributeStringValueForName:@"order"];
  549 + NSString *orderStr2 = [item2 attributeStringValueForName:@"order"];
  550 +
  551 + NSUInteger order1;
  552 + BOOL parse1 = [NSNumber parseString:orderStr1 intoNSUInteger:&order1];
  553 +
  554 + NSUInteger order2;
  555 + BOOL parse2 = [NSNumber parseString:orderStr2 intoNSUInteger:&order2];
  556 +
  557 + if (parse1)
  558 + {
  559 + if (parse2)
  560 + {
  561 + // item1 = valid
  562 + // item2 = valid
  563 +
  564 + if (order1 < order2)
  565 + return NSOrderedAscending;
  566 + if (order1 > order2)
  567 + return NSOrderedDescending;
  568 +
  569 + return NSOrderedSame;
  570 + }
  571 + else
  572 + {
  573 + // item1 = valid
  574 + // item2 = invalid
  575 +
  576 + return NSOrderedAscending;
  577 + }
  578 + }
  579 + else if (parse2)
  580 + {
  581 + // item1 = invalid
  582 + // item2 = valid
  583 +
  584 + return NSOrderedDescending;
  585 + }
  586 + else
  587 + {
  588 + // item1 = invalid
  589 + // item2 = invalid
  590 +
  591 + return NSOrderedSame;
  592 + }
  593 +}
  594 +
  595 +- (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPPrivacyQueryInfo *)queryInfo
  596 +{
  597 + if (queryInfo.type == FetchList)
  598 + {
  599 + // Privacy List Names Query Response:
  600 + //
  601 + // <iq type='result' id='getlist1'>
  602 + // <query xmlns='jabber:iq:privacy'>
  603 + // <active name='private'/>
  604 + // <default name='public'/>
  605 + // <list name='public'/>
  606 + // <list name='private'/>
  607 + // <list name='special'/>
  608 + // </query>
  609 + // </iq>
  610 +
  611 + if ([[iq type] isEqualToString:@"result"])
  612 + {
  613 + [activeListName release];
  614 + [defaultListName release];
  615 +
  616 + NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"];
  617 + if (query == nil) return;
  618 +
  619 + NSXMLElement *active = [query elementForName:@"active"];
  620 + activeListName = [[active attributeStringValueForName:@"name"] copy];
  621 +
  622 + NSXMLElement *dfault = [query elementForName:@"default"];
  623 + defaultListName = [[dfault attributeStringValueForName:@"name"] copy];
  624 +
  625 + NSArray *listNames = [query elementsForName:@"list"];
  626 + for (NSXMLElement *listName in listNames)
  627 + {
  628 + NSString *name = [listName attributeStringValueForName:@"name"];
  629 + if (name)
  630 + {
  631 + id value = [privacyDict objectForKey:name];
  632 + if (value == nil)
  633 + {
  634 + [privacyDict setObject:[NSNull null] forKey:name];
  635 + }
  636 + }
  637 + }
  638 +
  639 + [multicastDelegate xmppPrivacy:self didReceiveListNames:[self listNames]];
  640 + }
  641 + else
  642 + {
  643 + [multicastDelegate xmppPrivacy:self didNotReceiveListNamesDueToError:iq];
  644 + }
  645 + }
  646 + else if (queryInfo.type == FetchRules)
  647 + {
  648 + // Privacy List Rules Query Response (success):
  649 + //
  650 + // <iq type='result' id='getlist2'>
  651 + // <query xmlns='jabber:iq:privacy'>
  652 + // <list name='public'>
  653 + // <item type='jid' value='tybalt@example.com' action='deny' order='1'/>
  654 + // <item action='allow' order='2'/>
  655 + // </list>
  656 + // </query>
  657 + // </iq>
  658 + //
  659 + //
  660 + // Privacy List Rules Query Response (error):
  661 + //
  662 + // <iq type='error' id='getlist5'>
  663 + // <query xmlns='jabber:iq:privacy'>
  664 + // <list name='The Empty Set'/>
  665 + // </query>
  666 + // <error type='cancel'>
  667 + // <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  668 + // </error>
  669 + // </iq>
  670 +
  671 + if ([[iq type] isEqualToString:@"result"])
  672 + {
  673 + NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"];
  674 + if (query == nil) return;
  675 +
  676 + NSXMLElement *list = [query elementForName:@"list"];
  677 + if (list == nil) return;
  678 +
  679 + NSArray *items = [[list elementsForName:@"item"] sortedArrayUsingFunction:sortItems context:NULL];
  680 +
  681 + if (items == nil)
  682 + {
  683 + items = [NSArray array];
  684 + }
  685 +
  686 + [privacyDict setObject:items forKey:queryInfo.privacyListName];
  687 +
  688 + [multicastDelegate xmppPrivacy:self didReceiveListWithName:queryInfo.privacyListName items:items];
  689 + }
  690 + else
  691 + {
  692 + [multicastDelegate xmppPrivacy:self didNotReceiveListWithName:queryInfo.privacyListName error:iq];
  693 + }
  694 + }
  695 + else if (queryInfo.type == EditList)
  696 + {
  697 + // Privacy List Add/Edit/Remove Response (success):
  698 + //
  699 + // <iq type='result' id='abc123' to='romeo@example.net/orchard'/>
  700 + //
  701 + // Note: The result iq does NOT have a query child.
  702 +
  703 + if ([[iq type] isEqualToString:@"result"])
  704 + {
  705 + NSArray *items = [[queryInfo privacyListItems] sortedArrayUsingFunction:sortItems context:NULL];
  706 +
  707 + if (items == nil)
  708 + {
  709 + items = [NSArray array];
  710 + }
  711 +
  712 + [privacyDict setObject:items forKey:queryInfo.privacyListName];
  713 +
  714 + [multicastDelegate xmppPrivacy:self didSetListWithName:queryInfo.privacyListName];
  715 + }
  716 + else
  717 + {
  718 + [multicastDelegate xmppPrivacy:self didNotSetListWithName:queryInfo.privacyListName error:iq];
  719 + }
  720 + }
  721 + else if (queryInfo.type == SetActiveList)
  722 + {
  723 + // Change of active list (success):
  724 + //
  725 + // <iq type='result' id='active1' to='romeo@example.net/orchard'/>
  726 + //
  727 + //
  728 + // Change of active list (error):
  729 + //
  730 + // <iq to='romeo@example.net/orchard' type='error' id='active2'>
  731 + // <query xmlns='jabber:iq:privacy'>
  732 + // <active name='Invalid List Name'/>
  733 + // </query>
  734 + // <error type='cancel'>
  735 + // <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  736 + // </error>
  737 + // </iq>
  738 + //
  739 + //
  740 + // Note: The result iq does NOT have a query child.
  741 + // Note: The success could also mean we declined the use of an active list.
  742 +
  743 + if ([[iq type] isEqualToString:@"result"])
  744 + {
  745 + [activeListName release];
  746 + activeListName = [[queryInfo privacyListName] copy];
  747 +
  748 + [multicastDelegate xmppPrivacy:self didSetActiveListName:queryInfo.privacyListName];
  749 + }
  750 + else
  751 + {
  752 + [multicastDelegate xmppPrivacy:self didNotSetActiveListName:queryInfo.privacyListName error:iq];
  753 + }
  754 + }
  755 + else if (queryInfo.type == SetDefaultList)
  756 + {
  757 + // Change of default list (success):
  758 + //
  759 + // <iq type='result' id='default1' to='romeo@example.net/orchard'/>
  760 + //
  761 + //
  762 + // Change of default list (error):
  763 + //
  764 + // <iq to='romeo@example.net/orchard' type='error' id='default1'>
  765 + // <query xmlns='jabber:iq:privacy'>
  766 + // <default name='special'/>
  767 + // </query>
  768 + // <error type='cancel'>
  769 + // <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  770 + // </error>
  771 + // </iq>
  772 + //
  773 + //
  774 + // Note: The result iq does NOT have a query child.
  775 + // Note: The success could also mean we declined the use of a default list.
  776 +
  777 + if ([[iq type] isEqualToString:@"result"])
  778 + {
  779 + [defaultListName release];
  780 + defaultListName = [[queryInfo privacyListName] copy];
  781 +
  782 + [multicastDelegate xmppPrivacy:self didSetDefaultListName:queryInfo.privacyListName];
  783 + }
  784 + else
  785 + {
  786 + [multicastDelegate xmppPrivacy:self didNotSetDefaultListName:queryInfo.privacyListName error:iq];
  787 + }
  788 + }
  789 +}
  790 +
  791 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  792 +#pragma mark XMPPStream Delegate
  793 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  794 +
  795 +- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
  796 +{
  797 + if (self.autoRetrievePrivacyListNames)
  798 + {
  799 + [self retrieveListNames];
  800 + }
  801 +}
  802 +
  803 +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
  804 +{
  805 + NSString *type = [iq type];
  806 +
  807 + if ([type isEqualToString:@"set"])
  808 + {
  809 + NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"];
  810 + if (query)
  811 + {
  812 + // Privacy List Push:
  813 + //
  814 + // <iq type='set' id='push1'>
  815 + // <query xmlns='jabber:iq:privacy'>
  816 + // <list name='public'/>
  817 + // </query>
  818 + // </iq>
  819 + //
  820 + //
  821 + // Push response:
  822 + //
  823 + // <iq type='result' id='push1'/>
  824 +
  825 + NSXMLElement *list = [query elementForName:@"list"];
  826 +
  827 + NSString *listName = [list attributeStringValueForName:@"name"];
  828 + if (listName == nil)
  829 + {
  830 + return NO;
  831 + }
  832 +
  833 + [multicastDelegate xmppPrivacy:self didReceivePushWithListName:listName];
  834 +
  835 + XMPPIQ *iqResponse = [XMPPIQ iqWithType:@"result" to:[iq from] elementID:[iq elementID]];
  836 + [xmppStream sendElement:iqResponse];
  837 +
  838 + if (self.autoRetrievePrivacyListItems)
  839 + {
  840 + [self retrieveListWithName:listName];
  841 + }
  842 +
  843 + return YES;
  844 + }
  845 + }
  846 + else
  847 + {
  848 + // This may be a response to a query we sent
  849 +
  850 + XMPPPrivacyQueryInfo *queryInfo = [pendingQueries objectForKey:[iq elementID]];
  851 + if (queryInfo)
  852 + {
  853 + [self processQueryResponse:iq withInfo:queryInfo];
  854 +
  855 + if (queryInfo.type == FetchList && self.autoRetrievePrivacyListItems)
  856 + {
  857 + for (NSString *privacyListName in privacyDict)
  858 + {
  859 + id privacyListItems = [privacyDict objectForKey:privacyListName];
  860 +
  861 + if (privacyListItems == [NSNull null])
  862 + {
  863 + [self retrieveListWithName:privacyListName];
  864 + }
  865 + }
  866 + }
  867 +
  868 + return YES;
  869 + }
  870 + }
  871 +
  872 + return NO;
  873 +}
  874 +
  875 +- (void)xmppStreamDidDisconnect:(XMPPStream *)sender
  876 +{
  877 + // If there are any pending queries,
  878 + // they just failed due to the disconnection.
  879 +
  880 + for (NSString *uuid in pendingQueries)
  881 + {
  882 + XMPPPrivacyQueryInfo *queryInfo = [privacyDict objectForKey:uuid];
  883 +
  884 + [self processQuery:queryInfo withFailureCode:XMPPPrivacyDisconnect];
  885 + }
  886 +
  887 + // Clear the list of pending queries
  888 +
  889 + [pendingQueries removeAllObjects];
  890 +
  891 + // Maybe clear all stored privacy info
  892 +
  893 + if (self.autoClearPrivacyListInfo)
  894 + {
  895 + [self clearPrivacyListInfo];
  896 + }
  897 +}
  898 +
  899 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  900 +#pragma mark Privacy Items
  901 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  902 +
  903 ++ (NSXMLElement *)privacyItemWithAction:(NSString *)action order:(NSUInteger)orderValue
  904 +{
  905 + return [self privacyItemWithType:nil value:nil action:action order:orderValue];
  906 +}
  907 +
  908 ++ (NSXMLElement *)privacyItemWithType:(NSString *)type
  909 + value:(NSString *)value
  910 + action:(NSString *)action
  911 + order:(NSUInteger)orderValue
  912 +{
  913 + NSString *order = [[NSString alloc] initWithFormat:@"%lu", (unsigned long)orderValue];
  914 +
  915 + // <item type='[jid|group|subscription]'
  916 + // value='bar'
  917 + // action='[allow|deny]'
  918 + // order='unsignedInt'>
  919 + // [<iq/>]
  920 + // [<message/>]
  921 + // [<presence-in/>]
  922 + // [<presence-out/>]
  923 + // </item>
  924 +
  925 + NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
  926 +
  927 + if (type)
  928 + [item addAttributeWithName:@"type" stringValue:type];
  929 +
  930 + if (value)
  931 + [item addAttributeWithName:@"value" stringValue:value];
  932 +