This commit is contained in:
giuseppenuc committed 2016-02-24 16:56:39 +01:00
1 parent d281d765ea
commit 5b074a5176
1261 files changed
+199158 -7303

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
//
// Core classes
//
#import "XMPPJID.h"
#import "XMPPStream.h"
#import "XMPPElement.h"
#import "XMPPIQ.h"
#import "XMPPMessage.h"
#import "XMPPPresence.h"
#import "XMPPModule.h"
//
// Authentication
//
#import "XMPPSASLAuthentication.h"
#import "XMPPCustomBinding.h"
#import "XMPPDigestMD5Authentication.h"
#import "XMPPSCRAMSHA1Authentication.h"
#import "XMPPPlainAuthentication.h"
#import "XMPPXFacebookPlatformAuthentication.h"
#import "XMPPAnonymousAuthentication.h"
#import "XMPPDeprecatedPlainAuthentication.h"
#import "XMPPDeprecatedDigestAuthentication.h"
//
// Categories
//
#import "NSXMLElement+XMPP.h"
+16
View File
@@ -0,0 +1,16 @@
#import <Foundation/Foundation.h>
/**
* This class is provided to house various namespaces that are reused throughout
* the project. Feel free to add to the constants as you see necessary. If a
* particular namespace is only applicable to a particular extension, then it
* should be inside that extension rather than here.
*/
extern NSString *const XMPPSINamespace;
extern NSString *const XMPPSIProfileFileTransferNamespace;
extern NSString *const XMPPFeatureNegNamespace;
extern NSString *const XMPPBytestreamsNamespace;
extern NSString *const XMPPIBBNamespace;
extern NSString *const XMPPDiscoItemsNamespace;
extern NSString *const XMPPDiscoInfoNamespace;
+10
View File
@@ -0,0 +1,10 @@
#import "XMPPConstants.h"
NSString *const XMPPSINamespace = @"http://jabber.org/protocol/si";
NSString *const XMPPSIProfileFileTransferNamespace =
@"http://jabber.org/protocol/si/profile/file-transfer";
NSString *const XMPPFeatureNegNamespace = @"http://jabber.org/protocol/feature-neg";
NSString *const XMPPBytestreamsNamespace = @"http://jabber.org/protocol/bytestreams";
NSString *const XMPPIBBNamespace = @"http://jabber.org/protocol/ibb";
NSString *const XMPPDiscoItemsNamespace = @"http://jabber.org/protocol/disco#items";
NSString *const XMPPDiscoInfoNamespace = @"http://jabber.org/protocol/disco#info";
+44
View File
@@ -0,0 +1,44 @@
#import <Foundation/Foundation.h>
#import "XMPPJID.h"
#if TARGET_OS_IPHONE
#import "DDXML.h"
#endif
/**
* The XMPPElement provides the base class for XMPPIQ, XMPPMessage & XMPPPresence.
*
* This class extends NSXMLElement.
* The NSXML classes (NSXMLElement & NSXMLNode) provide a full-featured library for working with XML elements.
*
* On the iPhone, the KissXML library provides a drop-in replacement for Apple's NSXML classes.
**/
@interface XMPPElement : NSXMLElement <NSSecureCoding, NSCopying>
#pragma mark Common Jabber Methods
- (NSString *)elementID;
- (XMPPJID *)to;
- (XMPPJID *)from;
- (NSString *)toStr;
- (NSString *)fromStr;
#pragma mark To and From Methods
- (BOOL)isTo:(XMPPJID *)to;
- (BOOL)isTo:(XMPPJID *)to options:(XMPPJIDCompareOptions)mask;
- (BOOL)isFrom:(XMPPJID *)from;
- (BOOL)isFrom:(XMPPJID *)from options:(XMPPJIDCompareOptions)mask;
- (BOOL)isToOrFrom:(XMPPJID *)toOrFrom;
- (BOOL)isToOrFrom:(XMPPJID *)toOrFrom options:(XMPPJIDCompareOptions)mask;
- (BOOL)isTo:(XMPPJID *)to from:(XMPPJID *)from;
- (BOOL)isTo:(XMPPJID *)to from:(XMPPJID *)from options:(XMPPJIDCompareOptions)mask;
@end
+192
View File
@@ -0,0 +1,192 @@
#import "XMPPElement.h"
#import "XMPPJID.h"
#import "NSXMLElement+XMPP.h"
#import <objc/runtime.h>
@implementation XMPPElement
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Encoding, Decoding
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if ! TARGET_OS_IPHONE
- (id)replacementObjectForPortCoder:(NSPortCoder *)encoder
{
if([encoder isBycopy])
return self;
else
return [super replacementObjectForPortCoder:encoder];
// return [NSDistantObject proxyWithLocal:self connection:[encoder connection]];
}
#endif
- (id)initWithCoder:(NSCoder *)coder
{
NSString *xmlString;
if([coder allowsKeyedCoding])
{
if([coder respondsToSelector:@selector(requiresSecureCoding)] &&
[coder requiresSecureCoding])
{
xmlString = [coder decodeObjectOfClass:[NSString class] forKey:@"xmlString"];
}
else
{
xmlString = [coder decodeObjectForKey:@"xmlString"];
}
}
else
{
xmlString = [coder decodeObject];
}
// The method [super initWithXMLString:error:] may return a different self.
// In other words, it may [self release], and alloc/init/return a new self.
//
// So to maintain the proper class (XMPPIQ, XMPPMessage, XMPPPresence, etc)
// we need to get a reference to the class before invoking super.
Class selfClass = [self class];
if ((self = [super initWithXMLString:xmlString error:nil]))
{
object_setClass(self, selfClass);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
NSString *xmlString = [self compactXMLString];
if([coder allowsKeyedCoding])
{
[coder encodeObject:xmlString forKey:@"xmlString"];
}
else
{
[coder encodeObject:xmlString];
}
}
+ (BOOL) supportsSecureCoding
{
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Copying
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)copyWithZone:(NSZone *)zone
{
NSXMLElement *elementCopy = [super copyWithZone:zone];
object_setClass(elementCopy, [self class]);
return elementCopy;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Common Jabber Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSString *)elementID
{
return [[self attributeForName:@"id"] stringValue];
}
- (NSString *)toStr
{
return [[self attributeForName:@"to"] stringValue];
}
- (NSString *)fromStr
{
return [[self attributeForName:@"from"] stringValue];
}
- (XMPPJID *)to
{
return [XMPPJID jidWithString:[self toStr]];
}
- (XMPPJID *)from
{
return [XMPPJID jidWithString:[self fromStr]];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark To and From Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)isTo:(XMPPJID *)to
{
return [self.to isEqualToJID:to];
}
- (BOOL)isTo:(XMPPJID *)to options:(XMPPJIDCompareOptions)mask
{
return [self.to isEqualToJID:to options:mask];
}
- (BOOL)isFrom:(XMPPJID *)from
{
return [self.from isEqualToJID:from];
}
- (BOOL)isFrom:(XMPPJID *)from options:(XMPPJIDCompareOptions)mask
{
return [self.from isEqualToJID:from options:mask];
}
- (BOOL)isToOrFrom:(XMPPJID *)toOrFrom
{
if([self isTo:toOrFrom] || [self isFrom:toOrFrom])
{
return YES;
}
else
{
return NO;
}
}
- (BOOL)isToOrFrom:(XMPPJID *)toOrFrom options:(XMPPJIDCompareOptions)mask
{
if([self isTo:toOrFrom options:mask] || [self isFrom:toOrFrom options:mask])
{
return YES;
}
else
{
return NO;
}
}
- (BOOL)isTo:(XMPPJID *)to from:(XMPPJID *)from
{
if([self isTo:to] && [self isFrom:from])
{
return YES;
}
else
{
return NO;
}
}
- (BOOL)isTo:(XMPPJID *)to from:(XMPPJID *)from options:(XMPPJIDCompareOptions)mask
{
if([self isTo:to options:mask] && [self isFrom:from options:mask])
{
return YES;
}
else
{
return NO;
}
}
@end
+83
View File
@@ -0,0 +1,83 @@
#import <Foundation/Foundation.h>
#import "XMPPElement.h"
/**
* The XMPPIQ class represents an <iq> element.
* It extends XMPPElement, which in turn extends NSXMLElement.
* All <iq> elements that go in and out of the
* xmpp stream will automatically be converted to XMPPIQ objects.
*
* This class exists to provide developers an easy way to add functionality to IQ processing.
* Simply add your own category to XMPPIQ to extend it with your own custom methods.
**/
@interface XMPPIQ : XMPPElement
/**
* Converts an NSXMLElement to an XMPPIQ element in place (no memory allocations or copying)
**/
+ (XMPPIQ *)iqFromElement:(NSXMLElement *)element;
/**
* Creates and returns a new autoreleased XMPPIQ element.
* If the type or elementID parameters are nil, those attributes will not be added.
**/
+ (XMPPIQ *)iq;
+ (XMPPIQ *)iqWithType:(NSString *)type;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid;
+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement;
/**
* Creates and returns a new XMPPIQ element.
* If the type or elementID parameters are nil, those attributes will not be added.
**/
- (id)init;
- (id)initWithType:(NSString *)type;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement;
/**
* Returns the type attribute of the IQ.
* According to the XMPP protocol, the type should be one of 'get', 'set', 'result' or 'error'.
*
* This method converts the attribute to lowercase so
* case-sensitive string comparisons are safe (regardless of server treatment).
**/
- (NSString *)type;
/**
* Convenience methods for determining the IQ type.
**/
- (BOOL)isGetIQ;
- (BOOL)isSetIQ;
- (BOOL)isResultIQ;
- (BOOL)isErrorIQ;
/**
* Convenience method for determining if the IQ is of type 'get' or 'set'.
**/
- (BOOL)requiresResponse;
/**
* The XMPP RFC has various rules for the number of child elements an IQ is allowed to have:
*
* - An IQ stanza of type "get" or "set" MUST contain one and only one child element.
* - An IQ stanza of type "result" MUST include zero or one child elements.
* - An IQ stanza of type "error" SHOULD include the child element contained in the
* associated "get" or "set" and MUST include an <error/> child.
*
* The childElement returns the single non-error element, if one exists, or nil.
* The childErrorElement returns the error element, if one exists, or nil.
**/
- (NSXMLElement *)childElement;
- (NSXMLElement *)childErrorElement;
@end
+222
View File
@@ -0,0 +1,222 @@
#import "XMPPIQ.h"
#import "XMPPJID.h"
#import "NSXMLElement+XMPP.h"
#import <objc/runtime.h>
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@implementation XMPPIQ
#if DEBUG
+ (void)initialize
{
// We use the object_setClass method below to dynamically change the class from a standard NSXMLElement.
// The size of the two classes is expected to be the same.
//
// If a developer adds instance methods to this class, bad things happen at runtime that are very hard to debug.
// This check is here to aid future developers who may make this mistake.
//
// For Fearless And Experienced Objective-C Developers:
// It may be possible to support adding instance variables to this class if you seriously need it.
// To do so, try realloc'ing self after altering the class, and then initialize your variables.
size_t superSize = class_getInstanceSize([NSXMLElement class]);
size_t ourSize = class_getInstanceSize([XMPPIQ class]);
if (superSize != ourSize)
{
NSLog(@"Adding instance variables to XMPPIQ is not currently supported!");
exit(15);
}
}
#endif
+ (XMPPIQ *)iqFromElement:(NSXMLElement *)element
{
object_setClass(element, [XMPPIQ class]);
return (XMPPIQ *)element;
}
+ (XMPPIQ *)iq
{
return [[XMPPIQ alloc] initWithType:nil to:nil elementID:nil child:nil];
}
+ (XMPPIQ *)iqWithType:(NSString *)type
{
return [[XMPPIQ alloc] initWithType:type to:nil elementID:nil child:nil];
}
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid
{
return [[XMPPIQ alloc] initWithType:type to:jid elementID:nil child:nil];
}
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid
{
return [[XMPPIQ alloc] initWithType:type to:jid elementID:eid child:nil];
}
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
return [[XMPPIQ alloc] initWithType:type to:jid elementID:eid child:childElement];
}
+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid
{
return [[XMPPIQ alloc] initWithType:type to:nil elementID:eid child:nil];
}
+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
return [[XMPPIQ alloc] initWithType:type to:nil elementID:eid child:childElement];
}
+ (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement
{
return [[XMPPIQ alloc] initWithType:type to:nil elementID:nil child:childElement];
}
- (id)init
{
return [self initWithType:nil to:nil elementID:nil child:nil];
}
- (id)initWithType:(NSString *)type
{
return [self initWithType:type to:nil elementID:nil child:nil];
}
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid
{
return [self initWithType:type to:jid elementID:nil child:nil];
}
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid
{
return [self initWithType:type to:jid elementID:eid child:nil];
}
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
if ((self = [super initWithName:@"iq"]))
{
if (type)
[self addAttributeWithName:@"type" stringValue:type];
if (jid)
[self addAttributeWithName:@"to" stringValue:[jid full]];
if (eid)
[self addAttributeWithName:@"id" stringValue:eid];
if (childElement)
[self addChild:childElement];
}
return self;
}
- (id)initWithType:(NSString *)type elementID:(NSString *)eid
{
return [self initWithType:type to:nil elementID:eid child:nil];
}
- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
return [self initWithType:type to:nil elementID:eid child:childElement];
}
- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement
{
return [self initWithType:type to:nil elementID:nil child:childElement];
}
- (id)initWithXMLString:(NSString *)string error:(NSError *__autoreleasing *)error
{
if((self = [super initWithXMLString:string error:error])){
self = [XMPPIQ iqFromElement:self];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
NSXMLElement *element = [super copyWithZone:zone];
return [XMPPIQ iqFromElement:element];
}
- (NSString *)type
{
return [[self attributeStringValueForName:@"type"] lowercaseString];
}
- (BOOL)isGetIQ
{
return [[self type] isEqualToString:@"get"];
}
- (BOOL)isSetIQ
{
return [[self type] isEqualToString:@"set"];
}
- (BOOL)isResultIQ
{
return [[self type] isEqualToString:@"result"];
}
- (BOOL)isErrorIQ
{
return [[self type] isEqualToString:@"error"];
}
- (BOOL)requiresResponse
{
// An entity that receives an IQ request of type "get" or "set" MUST reply with an IQ response
// of type "result" or "error" (the response MUST preserve the 'id' attribute of the request).
return [self isGetIQ] || [self isSetIQ];
}
- (NSXMLElement *)childElement
{
NSArray *children = [self children];
for (NSXMLElement *child in children)
{
// Careful: NSOrderedSame == 0
NSString *childName = [child name];
if (childName && ([childName caseInsensitiveCompare:@"error"] != NSOrderedSame))
{
return child;
}
}
return nil;
}
- (NSXMLElement *)childErrorElement
{
NSArray *children = [self children];
for (NSXMLElement *child in children)
{
// Careful: NSOrderedSame == 0
NSString *childName = [child name];
if (childName && ([childName caseInsensitiveCompare:@"error"] == NSOrderedSame))
{
return child;
}
}
return nil;
}
@end
+118
View File
@@ -0,0 +1,118 @@
//
// This file is for XMPPStream and various internal components.
//
#import "XMPPStream.h"
#import "XMPPModule.h"
// Define the various states we'll use to track our progress
typedef NS_ENUM(NSInteger, XMPPStreamState) {
STATE_XMPP_DISCONNECTED,
STATE_XMPP_RESOLVING_SRV,
STATE_XMPP_CONNECTING,
STATE_XMPP_OPENING,
STATE_XMPP_NEGOTIATING,
STATE_XMPP_STARTTLS_1,
STATE_XMPP_STARTTLS_2,
STATE_XMPP_POST_NEGOTIATION,
STATE_XMPP_REGISTERING,
STATE_XMPP_AUTH,
STATE_XMPP_BINDING,
STATE_XMPP_START_SESSION,
STATE_XMPP_CONNECTED,
};
/**
* It is recommended that storage classes cache a stream's myJID.
* This prevents them from constantly querying the property from the xmppStream instance,
* as doing so goes through xmppStream's dispatch queue.
* Caching the stream's myJID frees the dispatch queue to handle xmpp processing tasks.
*
* The object of the notification will be the XMPPStream instance.
*
* Note: We're not using the typical MulticastDelegate paradigm for this task as
* storage classes are not typically added as a delegate of the xmppStream.
**/
extern NSString *const XMPPStreamDidChangeMyJIDNotification;
@interface XMPPStream (/* Internal */)
/**
* XMPPStream maintains thread safety by dispatching through the internal serial xmppQueue.
* Subclasses of XMPPStream MUST follow the same technique:
*
* dispatch_block_t block = ^{
* // Code goes here
* };
*
* if (dispatch_get_specific(xmppQueueTag))
* block();
* else
* dispatch_sync(xmppQueue, block);
*
* Category methods may or may not need to dispatch through the xmppQueue.
* It depends entirely on what properties of xmppStream the category method needs to access.
* For example, if a category only accesses a single property, such as the rootElement,
* then it can simply fetch the atomic property, inspect it, and complete its job.
* However, if the category needs to fetch multiple properties, then it likely needs to fetch all such
* properties in an atomic fashion. In this case, the category should likely go through the xmppQueue,
* to ensure that it gets an atomic state of the xmppStream in order to complete its job.
**/
@property (nonatomic, readonly) dispatch_queue_t xmppQueue;
@property (nonatomic, readonly) void *xmppQueueTag;
/**
* Returns the current state of the xmppStream.
**/
@property (atomic, readonly) XMPPStreamState state;
/**
* This method is for use by xmpp authentication mechanism classes.
* They should send elements using this method instead of the public sendElement methods,
* as those methods don't send the elements while authentication is in progress.
*
* @see XMPPSASLAuthentication
**/
- (void)sendAuthElement:(NSXMLElement *)element;
/**
* This method is for use by xmpp custom binding classes.
* They should send elements using this method instead of the public sendElement methods,
* as those methods don't send the elements while authentication/binding is in progress.
*
* @see XMPPCustomBinding
**/
- (void)sendBindElement:(NSXMLElement *)element;
/**
* This method allows you to inject an element into the stream as if it was received on the socket.
* This is an advanced technique, but makes for some interesting possibilities.
**/
- (void)injectElement:(NSXMLElement *)element;
/**
* The XMPP standard only supports <iq>, <message> and <presence> stanzas (excluding session setup stuff).
* But some extensions use non-standard element types.
* The standard example is XEP-0198, which uses <r> & <a> elements.
*
* XMPPStream will assume that any non-standard element types are errors, unless you register them.
* Once registered the stream can recognize them, and will use the following delegate methods:
*
* xmppStream:didSendCustomElement:
* xmppStream:didReceiveCustomElement:
**/
- (void)registerCustomElementNames:(NSSet *)names;
- (void)unregisterCustomElementNames:(NSSet *)names;
@end
@interface XMPPModule (/* Internal */)
/**
* Used internally by methods like XMPPStream's unregisterModule:.
* Normally removing a delegate is a synchronous operation, but due to multiple dispatch_sync operations,
* it must occasionally be done asynchronously to avoid deadlock.
**/
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue synchronously:(BOOL)synchronously;
@end
+74
View File
@@ -0,0 +1,74 @@
#import <Foundation/Foundation.h>
enum XMPPJIDCompareOptions
{
XMPPJIDCompareUser = 1, // 001
XMPPJIDCompareDomain = 2, // 010
XMPPJIDCompareResource = 4, // 100
XMPPJIDCompareBare = 3, // 011
XMPPJIDCompareFull = 7, // 111
};
typedef enum XMPPJIDCompareOptions XMPPJIDCompareOptions;
@interface XMPPJID : NSObject <NSSecureCoding, NSCopying>
{
__strong NSString *user;
__strong NSString *domain;
__strong NSString *resource;
}
+ (XMPPJID *)jidWithString:(NSString *)jidStr;
+ (XMPPJID *)jidWithString:(NSString *)jidStr resource:(NSString *)resource;
+ (XMPPJID *)jidWithUser:(NSString *)user domain:(NSString *)domain resource:(NSString *)resource;
@property (strong, readonly) NSString *user;
@property (strong, readonly) NSString *domain;
@property (strong, readonly) NSString *resource;
/**
* Terminology (from RFC 6120):
*
* The term "bare JID" refers to an XMPP address of the form <localpart@domainpart> (for an account at a server)
* or of the form <domainpart> (for a server).
*
* The term "full JID" refers to an XMPP address of the form
* <localpart@domainpart/resourcepart> (for a particular authorized client or device associated with an account)
* or of the form <domainpart/resourcepart> (for a particular resource or script associated with a server).
*
* Thus a bareJID is one that does not have a resource.
* And a fullJID is one that does have a resource.
*
* For convenience, there are also methods that that check for a user component as well.
**/
- (XMPPJID *)bareJID;
- (XMPPJID *)domainJID;
- (NSString *)bare;
- (NSString *)full;
- (BOOL)isBare;
- (BOOL)isBareWithUser;
- (BOOL)isFull;
- (BOOL)isFullWithUser;
/**
* A server JID does not have a user component.
**/
- (BOOL)isServer;
/**
* Returns a new jid with the given resource.
**/
- (XMPPJID *)jidWithNewResource:(NSString *)resource;
/**
* When you know both objects are JIDs, this method is a faster way to check equality than isEqual:.
**/
- (BOOL)isEqualToJID:(XMPPJID *)aJID;
- (BOOL)isEqualToJID:(XMPPJID *)aJID options:(XMPPJIDCompareOptions)mask;
@end
+603
View File
@@ -0,0 +1,603 @@
#import "XMPPJID.h"
#import "XMPPStringPrep.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@implementation XMPPJID
+ (BOOL)validateDomain:(NSString *)domain
{
// Domain is the only required part of a JID
if ((domain == nil) || ([domain length] == 0))
return NO;
// If there's an @ symbol in the domain it probably means user put @ in their username
NSRange invalidAtRange = [domain rangeOfString:@"@"];
if (invalidAtRange.location != NSNotFound)
return NO;
return YES;
}
+ (BOOL)validateResource:(NSString *)resource
{
// Can't use an empty string resource name
if ((resource != nil) && ([resource length] == 0))
return NO;
return YES;
}
+ (BOOL)validateUser:(NSString *)user domain:(NSString *)domain resource:(NSString *)resource
{
if (![self validateDomain:domain])
return NO;
if (![self validateResource:resource])
return NO;
return YES;
}
+ (BOOL)parse:(NSString *)jidStr
outUser:(NSString **)user
outDomain:(NSString **)domain
outResource:(NSString **)resource
{
if(user) *user = nil;
if(domain) *domain = nil;
if(resource) *resource = nil;
if(jidStr == nil) return NO;
NSString *rawUser = nil;
NSString *rawDomain = nil;
NSString *rawResource = nil;
NSRange atRange = [jidStr rangeOfString:@"@"];
if (atRange.location != NSNotFound)
{
rawUser = [jidStr substringToIndex:atRange.location];
NSString *minusUser = [jidStr substringFromIndex:atRange.location+1];
NSRange slashRange = [minusUser rangeOfString:@"/"];
if (slashRange.location != NSNotFound)
{
rawDomain = [minusUser substringToIndex:slashRange.location];
rawResource = [minusUser substringFromIndex:slashRange.location+1];
}
else
{
rawDomain = minusUser;
}
}
else
{
NSRange slashRange = [jidStr rangeOfString:@"/"];
if (slashRange.location != NSNotFound)
{
rawDomain = [jidStr substringToIndex:slashRange.location];
rawResource = [jidStr substringFromIndex:slashRange.location+1];
}
else
{
rawDomain = jidStr;
}
}
NSString *prepUser = [XMPPStringPrep prepNode:rawUser];
NSString *prepDomain = [XMPPStringPrep prepDomain:rawDomain];
NSString *prepResource = [XMPPStringPrep prepResource:rawResource];
if ([XMPPJID validateUser:prepUser domain:prepDomain resource:prepResource])
{
if(user) *user = prepUser;
if(domain) *domain = prepDomain;
if(resource) *resource = prepResource;
return YES;
}
return NO;
}
+ (XMPPJID *)jidWithString:(NSString *)jidStr
{
NSString *user;
NSString *domain;
NSString *resource;
if ([XMPPJID parse:jidStr outUser:&user outDomain:&domain outResource:&resource])
{
XMPPJID *jid = [[XMPPJID alloc] init];
jid->user = [user copy];
jid->domain = [domain copy];
jid->resource = [resource copy];
return jid;
}
return nil;
}
+ (XMPPJID *)jidWithString:(NSString *)jidStr resource:(NSString *)resource
{
NSString *prepResource = [XMPPStringPrep prepResource:resource];
if (![self validateResource:prepResource]) return nil;
NSString *user;
NSString *domain;
if ([XMPPJID parse:jidStr outUser:&user outDomain:&domain outResource:nil])
{
XMPPJID *jid = [[XMPPJID alloc] init];
jid->user = [user copy];
jid->domain = [domain copy];
jid->resource = [prepResource copy];
return jid;
}
return nil;
}
+ (XMPPJID *)jidWithUser:(NSString *)user domain:(NSString *)domain resource:(NSString *)resource
{
NSString *prepUser = [XMPPStringPrep prepNode:user];
NSString *prepDomain = [XMPPStringPrep prepDomain:domain];
NSString *prepResource = [XMPPStringPrep prepResource:resource];
if ([XMPPJID validateUser:prepUser domain:prepDomain resource:prepResource])
{
XMPPJID *jid = [[XMPPJID alloc] init];
jid->user = [prepUser copy];
jid->domain = [prepDomain copy];
jid->resource = [prepResource copy];
return jid;
}
return nil;
}
+ (XMPPJID *)jidWithPrevalidatedUser:(NSString *)user
prevalidatedDomain:(NSString *)domain
prevalidatedResource:(NSString *)resource
{
XMPPJID *jid = [[XMPPJID alloc] init];
jid->user = [user copy];
jid->domain = [domain copy];
jid->resource = [resource copy];
return jid;
}
+ (XMPPJID *)jidWithPrevalidatedUser:(NSString *)user
prevalidatedDomain:(NSString *)domain
resource:(NSString *)resource
{
NSString *prepResource = [XMPPStringPrep prepResource:resource];
if (![self validateResource:prepResource]) return nil;
XMPPJID *jid = [[XMPPJID alloc] init];
jid->user = [user copy];
jid->domain = [domain copy];
jid->resource = [prepResource copy];
return jid;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Encoding, Decoding:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if ! TARGET_OS_IPHONE
- (id)replacementObjectForPortCoder:(NSPortCoder *)encoder
{
if([encoder isBycopy])
return self;
else
return [super replacementObjectForPortCoder:encoder];
// return [NSDistantObject proxyWithLocal:self connection:[encoder connection]];
}
#endif
- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super init]))
{
if ([coder allowsKeyedCoding])
{
if([coder respondsToSelector:@selector(requiresSecureCoding)] &&
[coder requiresSecureCoding])
{
user = [[coder decodeObjectOfClass:[NSString class] forKey:@"user"] copy];
domain = [[coder decodeObjectOfClass:[NSString class] forKey:@"domain"] copy];
resource = [[coder decodeObjectOfClass:[NSString class] forKey:@"resource"] copy];
}
else
{
user = [[coder decodeObjectForKey:@"user"] copy];
domain = [[coder decodeObjectForKey:@"domain"] copy];
resource = [[coder decodeObjectForKey:@"resource"] copy];
}
}
else
{
user = [[coder decodeObject] copy];
domain = [[coder decodeObject] copy];
resource = [[coder decodeObject] copy];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
if ([coder allowsKeyedCoding])
{
[coder encodeObject:user forKey:@"user"];
[coder encodeObject:domain forKey:@"domain"];
[coder encodeObject:resource forKey:@"resource"];
}
else
{
[coder encodeObject:user];
[coder encodeObject:domain];
[coder encodeObject:resource];
}
}
+ (BOOL) supportsSecureCoding
{
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Copying:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)copyWithZone:(NSZone *)zone
{
// This class is immutable
return self;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Normal Methods:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Why didn't we just synthesize these properties?
//
// Since these variables are readonly within the class,
// we want the synthesized methods to work like a nonatomic property.
// In order to do this, we have to mark the properties as nonatomic in the header.
// However we don't like marking the property as nonatomic in the header because
// then people might think it's not thread-safe when in fact it is.
- (NSString *)user
{
return user; // Why didn't we just synthesize this? See comment above.
}
- (NSString *)domain
{
return domain; // Why didn't we just synthesize this? See comment above.
}
- (NSString *)resource
{
return resource; // Why didn't we just synthesize this? See comment above.
}
- (XMPPJID *)bareJID
{
if (resource == nil)
{
return self;
}
else
{
return [XMPPJID jidWithPrevalidatedUser:user prevalidatedDomain:domain prevalidatedResource:nil];
}
}
- (XMPPJID *)domainJID
{
if (user == nil && resource == nil)
{
return self;
}
else
{
return [XMPPJID jidWithPrevalidatedUser:nil prevalidatedDomain:domain prevalidatedResource:nil];
}
}
- (NSString *)bare
{
if (user)
return [NSString stringWithFormat:@"%@@%@", user, domain];
else
return domain;
}
- (NSString *)full
{
if (user)
{
if (resource)
return [NSString stringWithFormat:@"%@@%@/%@", user, domain, resource];
else
return [NSString stringWithFormat:@"%@@%@", user, domain];
}
else
{
if (resource)
return [NSString stringWithFormat:@"%@/%@", domain, resource];
else
return domain;
}
}
- (BOOL)isBare
{
// From RFC 6120 Terminology:
//
// The term "bare JID" refers to an XMPP address of the form <localpart@domainpart> (for an account at a server)
// or of the form <domainpart> (for a server).
return (resource == nil);
}
- (BOOL)isBareWithUser
{
return (user != nil && resource == nil);
}
- (BOOL)isFull
{
// From RFC 6120 Terminology:
//
// The term "full JID" refers to an XMPP address of the form
// <localpart@domainpart/resourcepart> (for a particular authorized client or device associated with an account)
// or of the form <domainpart/resourcepart> (for a particular resource or script associated with a server).
return (resource != nil);
}
- (BOOL)isFullWithUser
{
return (user != nil && resource != nil);
}
- (BOOL)isServer
{
return (user == nil);
}
- (XMPPJID *)jidWithNewResource:(NSString *)newResource
{
return [XMPPJID jidWithPrevalidatedUser:user prevalidatedDomain:domain resource:newResource];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark NSObject Methods:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSUInteger)hash
{
// We used to do this:
// return [[self full] hash];
//
// It was functional but less than optimal because it required the creation of a new NSString everytime.
// Now the hashing of a string itself is extremely fast,
// so combining 3 hashes is much faster than creating a new string.
// To accomplish this we use the murmur hashing algorithm.
//
// MurmurHash2 was written by Austin Appleby, and is placed in the public domain.
// http://code.google.com/p/smhasher
NSUInteger uhash = [user hash];
NSUInteger dhash = [domain hash];
NSUInteger rhash = [resource hash];
if (NSUIntegerMax == UINT32_MAX) // Should be optimized out via compiler since these are constants
{
// MurmurHash2 (32-bit)
//
// uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed )
//
// Normally one would pass a chunk of data ('key') and associated data chunk length ('len').
// Instead we're going to use our 3 hashes.
// And we're going to randomly make up a 'seed'.
const uint32_t seed = 0xa2f1b6f; // Some random value I made up
const uint32_t len = 12; // 3 hashes, each 4 bytes = 12 bytes
// 'm' and 'r' are mixing constants generated offline.
// They're not really 'magic', they just happen to work well.
const uint32_t m = 0x5bd1e995;
const int r = 24;
// Initialize the hash to a 'random' value
uint32_t h = seed ^ len;
uint32_t k;
// Mix uhash
k = uhash;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
// Mix dhash
k = dhash;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
// Mix rhash
k = rhash;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return (NSUInteger)h;
}
else
{
// MurmurHash2 (64-bit)
//
// uint64_t MurmurHash64A ( const void * key, int len, uint64_t seed )
//
// Normally one would pass a chunk of data ('key') and associated data chunk length ('len').
// Instead we're going to use our 3 hashes.
// And we're going to randomly make up a 'seed'.
const uint32_t seed = 0xa2f1b6f; // Some random value I made up
const uint32_t len = 24; // 3 hashes, each 8 bytes = 24 bytes
// 'm' and 'r' are mixing constants generated offline.
// They're not really 'magic', they just happen to work well.
const uint64_t m = 0xc6a4a7935bd1e995LLU;
const int r = 47;
// Initialize the hash to a 'random' value
uint64_t h = seed ^ (len * m);
uint64_t k;
// Mix uhash
k = uhash;
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
// Mix dhash
k = dhash;
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
// Mix rhash
k = rhash;
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> r;
h *= m;
h ^= h >> r;
return (NSUInteger)h;
}
}
- (BOOL)isEqual:(id)anObject
{
if ([anObject isMemberOfClass:[self class]])
{
return [self isEqualToJID:(XMPPJID *)anObject options:XMPPJIDCompareFull];
}
return NO;
}
- (BOOL)isEqualToJID:(XMPPJID *)aJID
{
return [self isEqualToJID:aJID options:XMPPJIDCompareFull];
}
- (BOOL)isEqualToJID:(XMPPJID *)aJID options:(XMPPJIDCompareOptions)mask
{
if (aJID == nil) return NO;
if (mask & XMPPJIDCompareUser)
{
if (user) {
if (![user isEqualToString:aJID->user]) return NO;
}
else {
if (aJID->user) return NO;
}
}
if (mask & XMPPJIDCompareDomain)
{
if (domain) {
if (![domain isEqualToString:aJID->domain]) return NO;
}
else {
if (aJID->domain) return NO;
}
}
if (mask & XMPPJIDCompareResource)
{
if (resource) {
if (![resource isEqualToString:aJID->resource]) return NO;
}
else {
if (aJID->resource) return NO;
}
}
return YES;
}
- (NSString *)description
{
return [self full];
}
@end
+191
View File
@@ -0,0 +1,191 @@
/**
* In order to provide fast and flexible logging, this project uses Cocoa Lumberjack.
*
* The GitHub project page has a wealth of documentation if you have any questions.
* https://github.com/robbiehanson/CocoaLumberjack
*
* Here's what you need to know concerning how logging is setup for XMPPFramework:
*
* There are 4 log levels:
* - Error
* - Warning
* - Info
* - Verbose
*
* In addition to this, there is a Trace flag that can be enabled.
* When tracing is enabled, it spits out the methods that are being called.
*
* Please note that tracing is separate from the log levels.
* For example, one could set the log level to warning, and enable tracing.
*
* All logging is asynchronous, except errors.
* To use logging within your own custom files, follow the steps below.
*
* Step 1:
* Import this header in your implementation file:
*
* #import "XMPPLogging.h"
*
* Step 2:
* Define your logging level in your implementation file:
*
* // Log levels: off, error, warn, info, verbose
* static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE;
*
* If you wish to enable tracing, you could do something like this:
*
* // Log levels: off, error, warn, info, verbose
* static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_TRACE;
*
* Step 3:
* Replace your NSLog statements with XMPPLog statements according to the severity of the message.
*
* NSLog(@"Fatal error, no dohickey found!"); -> XMPPLogError(@"Fatal error, no dohickey found!");
*
* XMPPLog has the same syntax as NSLog.
* This means you can pass it multiple variables just like NSLog.
*
* You may optionally choose to define different log levels for debug and release builds.
* You can do so like this:
*
* // Log levels: off, error, warn, info, verbose
* #if DEBUG
* static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE;
* #else
* static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
* #endif
*
* Xcode projects created with Xcode 4 automatically define DEBUG via the project's preprocessor macros.
* If you created your project with a previous version of Xcode, you may need to add the DEBUG macro manually.
**/
#import "CocoaLumberJack/DDLog.h"
// Global flag to enable/disable logging throughout the entire xmpp framework.
#ifndef XMPP_LOGGING_ENABLED
#define XMPP_LOGGING_ENABLED 1
#endif
// Define logging context for every log message coming from the XMPP framework.
// The logging context can be extracted from the DDLogMessage from within the logging framework.
// This gives loggers, formatters, and filters the ability to optionally process them differently.
#define XMPP_LOG_CONTEXT 5222
// Configure log levels.
#define XMPP_LOG_FLAG_ERROR (1 << 0) // 0...00001
#define XMPP_LOG_FLAG_WARN (1 << 1) // 0...00010
#define XMPP_LOG_FLAG_INFO (1 << 2) // 0...00100
#define XMPP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000
#define XMPP_LOG_LEVEL_OFF 0 // 0...00000
#define XMPP_LOG_LEVEL_ERROR (XMPP_LOG_LEVEL_OFF | XMPP_LOG_FLAG_ERROR) // 0...00001
#define XMPP_LOG_LEVEL_WARN (XMPP_LOG_LEVEL_ERROR | XMPP_LOG_FLAG_WARN) // 0...00011
#define XMPP_LOG_LEVEL_INFO (XMPP_LOG_LEVEL_WARN | XMPP_LOG_FLAG_INFO) // 0...00111
#define XMPP_LOG_LEVEL_VERBOSE (XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_VERBOSE) // 0...01111
// Setup fine grained logging.
// The first 4 bits are being used by the standard log levels (0 - 3)
//
// We're going to add tracing, but NOT as a log level.
// Tracing can be turned on and off independently of log level.
#define XMPP_LOG_FLAG_TRACE (1 << 4) // 0...10000
// Setup the usual boolean macros.
#define XMPP_LOG_ERROR (xmppLogLevel & XMPP_LOG_FLAG_ERROR)
#define XMPP_LOG_WARN (xmppLogLevel & XMPP_LOG_FLAG_WARN)
#define XMPP_LOG_INFO (xmppLogLevel & XMPP_LOG_FLAG_INFO)
#define XMPP_LOG_VERBOSE (xmppLogLevel & XMPP_LOG_FLAG_VERBOSE)
#define XMPP_LOG_TRACE (xmppLogLevel & XMPP_LOG_FLAG_TRACE)
// Configure asynchronous logging.
// We follow the default configuration,
// but we reserve a special macro to easily disable asynchronous logging for debugging purposes.
#if DEBUG
#define XMPP_LOG_ASYNC_ENABLED NO
#else
#define XMPP_LOG_ASYNC_ENABLED YES
#endif
#define XMPP_LOG_ASYNC_ERROR ( NO && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_WARN (YES && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_INFO (YES && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_VERBOSE (YES && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_TRACE (YES && XMPP_LOG_ASYNC_ENABLED)
// Define logging primitives.
// These are primarily wrappers around the macros defined in Lumberjack's DDLog.h header file.
#define XMPP_LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \
do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__); } while(0)
#define XMPP_LOG_C_MAYBE(async, lvl, flg, ctx, frmt, ...) \
do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__); } while(0)
#define XMPPLogError(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_ERROR, xmppLogLevel, XMPP_LOG_FLAG_ERROR, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogWarn(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_WARN, xmppLogLevel, XMPP_LOG_FLAG_WARN, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogInfo(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_INFO, xmppLogLevel, XMPP_LOG_FLAG_INFO, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogVerbose(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_VERBOSE, xmppLogLevel, XMPP_LOG_FLAG_VERBOSE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogTrace() XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, @"%@: %@", THIS_FILE, THIS_METHOD)
#define XMPPLogTrace2(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogCError(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_ERROR, xmppLogLevel, XMPP_LOG_FLAG_ERROR, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogCWarn(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_WARN, xmppLogLevel, XMPP_LOG_FLAG_WARN, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogCInfo(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_INFO, xmppLogLevel, XMPP_LOG_FLAG_INFO, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogCVerbose(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_VERBOSE, xmppLogLevel, XMPP_LOG_FLAG_VERBOSE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
#define XMPPLogCTrace() XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, @"%@: %s", THIS_FILE, __FUNCTION__)
#define XMPPLogCTrace2(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
// Setup logging for XMPPStream (and subclasses such as XMPPStreamFacebook)
#define XMPP_LOG_FLAG_SEND (1 << 5)
#define XMPP_LOG_FLAG_RECV_PRE (1 << 6) // Prints data before it goes to the parser
#define XMPP_LOG_FLAG_RECV_POST (1 << 7) // Prints data as it comes out of the parser
#define XMPP_LOG_FLAG_SEND_RECV (XMPP_LOG_FLAG_SEND | XMPP_LOG_FLAG_RECV_POST)
#define XMPP_LOG_SEND (xmppLogLevel & XMPP_LOG_FLAG_SEND)
#define XMPP_LOG_RECV_PRE (xmppLogLevel & XMPP_LOG_FLAG_RECV_PRE)
#define XMPP_LOG_RECV_POST (xmppLogLevel & XMPP_LOG_FLAG_RECV_POST)
#define XMPP_LOG_ASYNC_SEND (YES && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_RECV_PRE (YES && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_RECV_POST (YES && XMPP_LOG_ASYNC_ENABLED)
#define XMPPLogSend(format, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_SEND, xmppLogLevel, \
XMPP_LOG_FLAG_SEND, XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
#define XMPPLogRecvPre(format, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_RECV_PRE, xmppLogLevel, \
XMPP_LOG_FLAG_RECV_PRE, XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
#define XMPPLogRecvPost(format, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_RECV_POST, xmppLogLevel, \
XMPP_LOG_FLAG_RECV_POST, XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
+55
View File
@@ -0,0 +1,55 @@
#import <Foundation/Foundation.h>
#import "XMPPElement.h"
/**
* The XMPPMessage class represents a <message> element.
* It extends XMPPElement, which in turn extends NSXMLElement.
* All <message> elements that go in and out of the
* xmpp stream will automatically be converted to XMPPMessage objects.
*
* This class exists to provide developers an easy way to add functionality to message processing.
* Simply add your own category to XMPPMessage to extend it with your own custom methods.
**/
@interface XMPPMessage : XMPPElement
// Converts an NSXMLElement to an XMPPMessage element in place (no memory allocations or copying)
+ (XMPPMessage *)messageFromElement:(NSXMLElement *)element;
+ (XMPPMessage *)message;
+ (XMPPMessage *)messageWithType:(NSString *)type;
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)to;
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid;
+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPMessage *)messageWithType:(NSString *)type child:(NSXMLElement *)childElement;
- (id)init;
- (id)initWithType:(NSString *)type;
- (id)initWithType:(NSString *)type to:(XMPPJID *)to;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement;
- (NSString *)type;
- (NSString *)subject;
- (NSString *)body;
- (NSString *)bodyForLanguage:(NSString *)language;
- (NSString *)thread;
- (void)addSubject:(NSString *)subject;
- (void)addBody:(NSString *)body;
- (void)addBody:(NSString *)body withLanguage:(NSString *)language;
- (void)addThread:(NSString *)thread;
- (BOOL)isChatMessage;
- (BOOL)isChatMessageWithBody;
- (BOOL)isErrorMessage;
- (BOOL)isMessageWithBody;
- (NSError *)errorMessage;
@end
+267
View File
@@ -0,0 +1,267 @@
#import "XMPPMessage.h"
#import "XMPPJID.h"
#import "NSXMLElement+XMPP.h"
#import <objc/runtime.h>
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@implementation XMPPMessage
#if DEBUG
+ (void)initialize
{
// We use the object_setClass method below to dynamically change the class from a standard NSXMLElement.
// The size of the two classes is expected to be the same.
//
// If a developer adds instance methods to this class, bad things happen at runtime that are very hard to debug.
// This check is here to aid future developers who may make this mistake.
//
// For Fearless And Experienced Objective-C Developers:
// It may be possible to support adding instance variables to this class if you seriously need it.
// To do so, try realloc'ing self after altering the class, and then initialize your variables.
size_t superSize = class_getInstanceSize([NSXMLElement class]);
size_t ourSize = class_getInstanceSize([XMPPMessage class]);
if (superSize != ourSize)
{
NSLog(@"Adding instance variables to XMPPMessage is not currently supported!");
exit(15);
}
}
#endif
+ (XMPPMessage *)messageFromElement:(NSXMLElement *)element
{
object_setClass(element, [XMPPMessage class]);
return (XMPPMessage *)element;
}
+ (XMPPMessage *)message
{
return [[XMPPMessage alloc] init];
}
+ (XMPPMessage *)messageWithType:(NSString *)type
{
return [[XMPPMessage alloc] initWithType:type to:nil];
}
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)to
{
return [[XMPPMessage alloc] initWithType:type to:to];
}
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid
{
return [[XMPPMessage alloc] initWithType:type to:jid elementID:eid];
}
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
return [[XMPPMessage alloc] initWithType:type to:jid elementID:eid child:childElement];
}
+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid
{
return [[XMPPMessage alloc] initWithType:type elementID:eid];
}
+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
return [[XMPPMessage alloc] initWithType:type elementID:eid child:childElement];
}
+ (XMPPMessage *)messageWithType:(NSString *)type child:(NSXMLElement *)childElement
{
return [[XMPPMessage alloc] initWithType:type child:childElement];
}
- (id)init
{
return [self initWithType:nil to:nil elementID:nil child:nil];
}
- (id)initWithType:(NSString *)type
{
return [self initWithType:type to:nil elementID:nil child:nil];
}
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid
{
return [self initWithType:type to:jid elementID:nil child:nil];
}
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid
{
return [self initWithType:type to:jid elementID:eid child:nil];
}
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
if ((self = [super initWithName:@"message"]))
{
if (type)
[self addAttributeWithName:@"type" stringValue:type];
if (jid)
[self addAttributeWithName:@"to" stringValue:[jid full]];
if (eid)
[self addAttributeWithName:@"id" stringValue:eid];
if (childElement)
[self addChild:childElement];
}
return self;
}
- (id)initWithType:(NSString *)type elementID:(NSString *)eid
{
return [self initWithType:type to:nil elementID:eid child:nil];
}
- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
{
return [self initWithType:type to:nil elementID:eid child:childElement];
}
- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement
{
return [self initWithType:type to:nil elementID:nil child:childElement];
}
- (id)initWithXMLString:(NSString *)string error:(NSError *__autoreleasing *)error
{
if((self = [super initWithXMLString:string error:error])){
self = [XMPPMessage messageFromElement:self];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
NSXMLElement *element = [super copyWithZone:zone];
return [XMPPMessage messageFromElement:element];
}
- (NSString *)type
{
return [[self attributeForName:@"type"] stringValue];
}
- (NSString *)subject
{
return [[self elementForName:@"subject"] stringValue];
}
- (NSString *)body
{
return [[self elementForName:@"body"] stringValue];
}
- (NSString *)bodyForLanguage:(NSString *)language
{
NSString *bodyForLanguage = nil;
for (NSXMLElement *bodyElement in [self elementsForName:@"body"])
{
NSString *lang = [[bodyElement attributeForName:@"xml:lang"] stringValue];
// Openfire strips off the xml prefix
if (lang == nil)
{
lang = [[bodyElement attributeForName:@"lang"] stringValue];
}
if ([language isEqualToString:lang] || ([language length] == 0 && [lang length] == 0))
{
bodyForLanguage = [bodyElement stringValue];
break;
}
}
return bodyForLanguage;
}
- (NSString *)thread
{
return [[self elementForName:@"thread"] stringValue];
}
- (void)addSubject:(NSString *)subject
{
NSXMLElement *subjectElement = [NSXMLElement elementWithName:@"subject" stringValue:subject];
[self addChild:subjectElement];
}
- (void)addBody:(NSString *)body
{
NSXMLElement *bodyElement = [NSXMLElement elementWithName:@"body" stringValue:body];
[self addChild:bodyElement];
}
- (void)addBody:(NSString *)body withLanguage:(NSString *)language
{
NSXMLElement *bodyElement = [NSXMLElement elementWithName:@"body" stringValue:body];
if ([language length])
{
[bodyElement addAttributeWithName:@"xml:lang" stringValue:language];
}
[self addChild:bodyElement];
}
- (void)addThread:(NSString *)thread
{
NSXMLElement *threadElement = [NSXMLElement elementWithName:@"thread" stringValue:thread];
[self addChild:threadElement];
}
- (BOOL)isChatMessage
{
return [[[self attributeForName:@"type"] stringValue] isEqualToString:@"chat"];
}
- (BOOL)isChatMessageWithBody
{
if ([self isChatMessage])
{
return [self isMessageWithBody];
}
return NO;
}
- (BOOL)isErrorMessage
{
return [[[self attributeForName:@"type"] stringValue] isEqualToString:@"error"];
}
- (NSError *)errorMessage
{
if (![self isErrorMessage]) {
return nil;
}
NSXMLElement *error = [self elementForName:@"error"];
return [NSError errorWithDomain:@"urn:ietf:params:xml:ns:xmpp-stanzas"
code:[error attributeIntValueForName:@"code"]
userInfo:@{NSLocalizedDescriptionKey : [error compactXMLString]}];
}
- (BOOL)isMessageWithBody
{
return ([self elementForName:@"body"] != nil);
}
@end
+43
View File
@@ -0,0 +1,43 @@
#import <Foundation/Foundation.h>
#import "GCDMulticastDelegate.h"
@class XMPPStream;
/**
* XMPPModule is the base class that all extensions/modules inherit.
* They automatically get:
*
* - A dispatch queue.
* - A multicast delegate that automatically invokes added delegates.
*
* The module also automatically registers/unregisters itself with the
* xmpp stream during the activate/deactive methods.
**/
@interface XMPPModule : NSObject
{
XMPPStream *xmppStream;
dispatch_queue_t moduleQueue;
void *moduleQueueTag;
id multicastDelegate;
}
@property (readonly) dispatch_queue_t moduleQueue;
@property (readonly) void *moduleQueueTag;
@property (strong, readonly) XMPPStream *xmppStream;
- (id)init;
- (id)initWithDispatchQueue:(dispatch_queue_t)queue;
- (BOOL)activate:(XMPPStream *)aXmppStream;
- (void)deactivate;
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
- (void)removeDelegate:(id)delegate;
- (NSString *)moduleName;
@end
+224
View File
@@ -0,0 +1,224 @@
#import "XMPPModule.h"
#import "XMPPStream.h"
#import "XMPPLogging.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@implementation XMPPModule
/**
* Standard init method.
**/
- (id)init
{
return [self initWithDispatchQueue:NULL];
}
/**
* Designated initializer.
**/
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
if ((self = [super init]))
{
if (queue)
{
moduleQueue = queue;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(moduleQueue);
#endif
}
else
{
const char *moduleQueueName = [[self moduleName] UTF8String];
moduleQueue = dispatch_queue_create(moduleQueueName, NULL);
}
moduleQueueTag = &moduleQueueTag;
dispatch_queue_set_specific(moduleQueue, moduleQueueTag, moduleQueueTag, NULL);
multicastDelegate = [[GCDMulticastDelegate alloc] init];
}
return self;
}
- (void)dealloc
{
#if !OS_OBJECT_USE_OBJC
dispatch_release(moduleQueue);
#endif
}
/**
* The activate method is the point at which the module gets plugged into the xmpp stream.
*
* It is recommended that subclasses override didActivate, instead of this method,
* to perform any custom actions upon activation.
**/
- (BOOL)activate:(XMPPStream *)aXmppStream
{
__block BOOL result = YES;
dispatch_block_t block = ^{
if (xmppStream != nil)
{
result = NO;
}
else
{
xmppStream = aXmppStream;
[xmppStream addDelegate:self delegateQueue:moduleQueue];
[xmppStream registerModule:self];
[self didActivate];
}
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
/**
* It is recommended that subclasses override this method (instead of activate:)
* to perform tasks after the module has been activated.
*
* This method is only invoked if the module is successfully activated.
* This method is always invoked on the moduleQueue.
**/
- (void)didActivate
{
// Override me to do custom work after the module is activated
}
/**
* The deactivate method unplugs a module from the xmpp stream.
* When this method returns, no further delegate methods on this module will be dispatched.
* However, there may be delegate methods that have already been dispatched.
* If this is the case, the module will be properly retained until the delegate methods have completed.
* If your custom module requires that delegate methods are not run after the deactivate method has been run,
* then simply check the xmppStream variable in your delegate methods.
*
* It is recommended that subclasses override didDeactivate, instead of this method,
* to perform any custom actions upon deactivation.
**/
- (void)deactivate
{
dispatch_block_t block = ^{
if (xmppStream)
{
[self willDeactivate];
[xmppStream removeDelegate:self delegateQueue:moduleQueue];
[xmppStream unregisterModule:self];
xmppStream = nil;
}
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
}
/**
* It is recommended that subclasses override this method (instead of deactivate:)
* to perform tasks after the module has been deactivated.
*
* This method is only invoked if the module is transitioning from activated to deactivated.
* This method is always invoked on the moduleQueue.
**/
- (void)willDeactivate
{
// Override me to do custom work after the module is deactivated
}
- (dispatch_queue_t)moduleQueue
{
return moduleQueue;
}
- (void *)moduleQueueTag
{
return moduleQueueTag;
}
- (XMPPStream *)xmppStream
{
if (dispatch_get_specific(moduleQueueTag))
{
return xmppStream;
}
else
{
__block XMPPStream *result;
dispatch_sync(moduleQueue, ^{
result = xmppStream;
});
return result;
}
}
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
// Asynchronous operation (if outside xmppQueue)
dispatch_block_t block = ^{
[multicastDelegate addDelegate:delegate delegateQueue:delegateQueue];
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
[multicastDelegate removeDelegate:delegate delegateQueue:delegateQueue];
};
if (dispatch_get_specific(moduleQueueTag))
block();
else if (synchronously)
dispatch_sync(moduleQueue, block);
else
dispatch_async(moduleQueue, block);
}
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
// Synchronous operation (common-case default)
[self removeDelegate:delegate delegateQueue:delegateQueue synchronously:YES];
}
- (void)removeDelegate:(id)delegate
{
// Synchronous operation (common-case default)
[self removeDelegate:delegate delegateQueue:NULL synchronously:YES];
}
- (NSString *)moduleName
{
// Override me (if needed) to provide a customized module name.
// This name is used as the name of the dispatch_queue which could aid in debugging.
return NSStringFromClass([self class]);
}
@end
+40
View File
@@ -0,0 +1,40 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import "DDXML.h"
#endif
@interface XMPPParser : NSObject
- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)dq;
- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)dq parserQueue:(dispatch_queue_t)pq;
- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
/**
* Asynchronously parses the given data.
* The delegate methods will be dispatch_async'd as events occur.
**/
- (void)parseData:(NSData *)data;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@protocol XMPPParserDelegate
@optional
- (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root;
- (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element;
- (void)xmppParserDidEnd:(XMPPParser *)sender;
- (void)xmppParser:(XMPPParser *)sender didFail:(NSError *)error;
- (void)xmppParserDidParseData:(XMPPParser *)sender;
@end
+850
View File
@@ -0,0 +1,850 @@
#import "XMPPParser.h"
#import "XMPPLogging.h"
#import <libxml/parser.h>
#import <libxml/parserInternals.h>
#if TARGET_OS_IPHONE
#import "DDXMLPrivate.h"
#endif
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
#define CHECK_FOR_NULL(value) \
do { \
if (value == NULL) { \
xmpp_xmlAbortDueToMemoryShortage(ctxt); \
return; \
} \
} while(false)
#if !TARGET_OS_IPHONE
static void xmpp_recursiveAddChild(NSXMLElement *parent, xmlNodePtr childNode);
#endif
@implementation XMPPParser
{
#if __has_feature(objc_arc_weak)
__weak id delegate;
#else
__unsafe_unretained id delegate;
#endif
dispatch_queue_t delegateQueue;
dispatch_queue_t parserQueue;
void *xmppParserQueueTag;
BOOL hasReportedRoot;
unsigned depth;
xmlParserCtxt *parserCtxt;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark iPhone
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_OS_IPHONE
static void xmpp_onDidReadRoot(XMPPParser *parser, xmlNodePtr root)
{
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
{
// We first copy the root node.
// We do this to allow the delegate to retain and make changes to the reported root
// without affecting the underlying xmpp parser.
// xmlCopyNode(const xmlNodePtr node, int extended)
//
// node:
// the node to copy
// extended:
// if 1 do a recursive copy (properties, namespaces and children when applicable)
// if 2 copy properties and namespaces (when applicable)
xmlNodePtr rootCopy = xmlCopyNode(root, 2);
DDXMLElement *rootCopyWrapper = [DDXMLElement nodeWithElementPrimitive:rootCopy owner:nil];
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadRoot:rootCopyWrapper];
}});
// Note: DDXMLElement will properly free the rootCopy when it's deallocated.
}
}
static void xmpp_onDidReadElement(XMPPParser *parser, xmlNodePtr child)
{
// Detach the child from the xml tree.
//
// clean: Nullify next, prev, parent and doc pointers of child.
// fixNamespaces: Recurse through subtree, and ensure no namespaces are pointing to xmlNs nodes outside the tree.
// E.G. in a parent node that will no longer be available after the child is detached.
//
// We don't need to fix namespaces since we used xmpp_xmlSearchNs() to ensure we never created any
// namespaces outside the subtree of the child in the first place.
[DDXMLNode detachChild:child andClean:YES andFixNamespaces:NO];
DDXMLElement *childWrapper = [DDXMLElement nodeWithElementPrimitive:child owner:nil];
// Note: We want to detach the child from the root even if the delegate method isn't setup.
// This prevents the doc from growing infinitely large.
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
{
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadElement:childWrapper];
}});
}
// Note: DDXMLElement will properly free the child when it's deallocated.
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Mac
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#else
static void xmpp_setName(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
if (node->name == NULL)
{
[element setName:@""];
return;
}
if ((node->ns != NULL) && (node->ns->prefix != NULL))
{
// E.g: <deusty:element xmlns:deusty="deusty.com"/>
NSString *prefix = [[NSString alloc] initWithUTF8String:(const char *)node->ns->prefix];
NSString *name = [[NSString alloc] initWithUTF8String:(const char *)node->name];
NSString *elementName = [[NSString alloc] initWithFormat:@"%@:%@", prefix, name];
[element setName:elementName];
}
else
{
NSString *elementName = [[NSString alloc] initWithUTF8String:(const char *)node->name];
[element setName:elementName];
}
}
static void xmpp_addNamespaces(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlNsPtr nsNode = node->nsDef;
while (nsNode != NULL)
{
if (nsNode->href == NULL)
{
// Namespace doesn't have a value!
}
else
{
NSXMLNode *ns = [[NSXMLNode alloc] initWithKind:NSXMLNamespaceKind];
if (nsNode->prefix != NULL)
{
NSString *nsName = [[NSString alloc] initWithUTF8String:(const char *)nsNode->prefix];
[ns setName:nsName];
}
else
{
// Default namespace.
// E.g: xmlns="deusty.com"
[ns setName:@""];
}
NSString *nsValue = [[NSString alloc] initWithUTF8String:(const char *)nsNode->href];
[ns setStringValue:nsValue];
[element addNamespace:ns];
}
nsNode = nsNode->next;
}
}
static void xmpp_addChildren(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlNodePtr childNode = node->children;
while (childNode != NULL)
{
if (childNode->type == XML_ELEMENT_NODE)
{
xmpp_recursiveAddChild(element, childNode);
}
else if (childNode->type == XML_TEXT_NODE)
{
if (childNode->content != NULL)
{
NSString *value = [[NSString alloc] initWithUTF8String:(const char *)childNode->content];
[element setStringValue:value];
}
}
childNode = childNode->next;
}
}
static void xmpp_addAttributes(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlAttrPtr attrNode = node->properties;
while (attrNode != NULL)
{
if (attrNode->name == NULL)
{
// Attribute doesn't have a name!
}
else if (attrNode->children == NULL)
{
// Attribute doesn't have a value node!
}
else if (attrNode->children->content == NULL)
{
// Attribute doesn't have a value!
}
else
{
NSXMLNode *attr = [[NSXMLNode alloc] initWithKind:NSXMLAttributeKind];
if ((attrNode->ns != NULL) && (attrNode->ns->prefix != NULL))
{
// E.g: <element xmlns:deusty="deusty.com" deusty:attr="value"/>
NSString *prefix = [[NSString alloc] initWithUTF8String:(const char *)attrNode->ns->prefix];
NSString *name = [[NSString alloc] initWithUTF8String:(const char *)attrNode->name];
NSString *attrName = [[NSString alloc] initWithFormat:@"%@:%@", prefix, name];
[attr setName:attrName];
}
else
{
NSString *attrName = [[NSString alloc] initWithUTF8String:(const char *)attrNode->name];
[attr setName:attrName];
}
NSString *attrValue = [[NSString alloc] initWithUTF8String:(const char *)attrNode->children->content];
[attr setStringValue:attrValue];
[element addAttribute:attr];
}
attrNode = attrNode->next;
}
}
/**
* Recursively adds all the child elements to the given parent.
*
* Note: This method is almost the same as xmpp_nsxmlFromLibxml, with one important difference.
* It doen't add any objects to the autorelease pool (xmpp_nsxmlFromLibXml has return value).
**/
static void xmpp_recursiveAddChild(NSXMLElement *parent, xmlNodePtr childNode)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
NSXMLElement *child = [[NSXMLElement alloc] initWithKind:NSXMLElementKind];
xmpp_setName(child, childNode);
xmpp_addNamespaces(child, childNode);
xmpp_addChildren(child, childNode);
xmpp_addAttributes(child, childNode);
[parent addChild:child];
}
/**
* Creates and returns an NSXMLElement from the given node.
* Use this method after finding the root element, or root.child element.
**/
static NSXMLElement* xmpp_nsxmlFromLibxml(xmlNodePtr rootNode)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
NSXMLElement *root = [[NSXMLElement alloc] initWithKind:NSXMLElementKind];
xmpp_setName(root, rootNode);
xmpp_addNamespaces(root, rootNode);
xmpp_addChildren(root, rootNode);
xmpp_addAttributes(root, rootNode);
return root;
}
static void xmpp_onDidReadRoot(XMPPParser *parser, xmlNodePtr root)
{
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
{
NSXMLElement *nsRoot = xmpp_nsxmlFromLibxml(root);
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadRoot:nsRoot];
}});
}
}
static void xmpp_onDidReadElement(XMPPParser *parser, xmlNodePtr child)
{
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
{
NSXMLElement *nsChild = xmpp_nsxmlFromLibxml(child);
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didReadElement:nsChild];
}});
}
// Note: We want to detach the child from the root even if the delegate method isn't setup.
// This prevents the doc from growing infinitely large.
// Detach and free child to keep memory footprint small
xmlUnlinkNode(child);
xmlFreeNode(child);
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Common
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method is called at the end of the xmlStartElement method.
* This allows us to inspect the parser and xml tree, and determine if we need to invoke any delegate methods.
**/
static void xmpp_postStartElement(xmlParserCtxt *ctxt)
{
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
parser->depth++;
if (!(parser->hasReportedRoot) && (parser->depth == 1))
{
// We've received the full root - report it to the delegate
if (ctxt->myDoc)
{
xmlNodePtr root = xmlDocGetRootElement(ctxt->myDoc);
if (root)
{
xmpp_onDidReadRoot(parser, root);
parser->hasReportedRoot = YES;
}
}
}
}
/**
* This method is called at the end of the xmlEndElement method.
* This allows us to inspect the parser and xml tree, and determine if we need to invoke any delegate methods.
**/
static void xmpp_postEndElement(xmlParserCtxt *ctxt)
{
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
parser->depth--;
if (parser->depth == 1)
{
// End of full xmpp element.
// That is, a child of the root element.
// Extract the child, and pass it to the delegate.
xmlDocPtr doc = ctxt->myDoc;
xmlNodePtr root = xmlDocGetRootElement(doc);
xmlNodePtr child = root->children;
while (child != NULL)
{
if (child->type == XML_ELEMENT_NODE)
{
xmpp_onDidReadElement(parser, child);
// Exit while loop
break;
}
child = child->next;
}
}
else if (parser->depth == 0)
{
// End of the root element
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParserDidEnd:)])
{
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParserDidEnd:parser];
}});
}
}
}
/**
* We're screwed...
**/
static void xmpp_xmlAbortDueToMemoryShortage(xmlParserCtxt *ctxt)
{
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
xmlStopParser(ctxt);
if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didFail:)])
{
NSString *errMsg = @"Unable to allocate memory in xmpp parser";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
NSError *error = [NSError errorWithDomain:@"libxmlErrorDomain" code:1001 userInfo:info];
__strong id theDelegate = parser->delegate;
dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:parser didFail:error];
}});
}
}
/**
* (Similar to the libxml "xmlSearchNs" method, with one very important difference.)
*
* This method searches for an existing xmlNsPtr in the given node,
* recursing on the parents but stopping before it reaches the root node of the document.
*
* Why do we skip the root node?
* Because all nodes are going to be detached from the root node.
* So it makes no sense to allow them to reference namespaces stored in the root node,
* since the detach algorithm will be forced to copy the namespaces later anyway.
**/
static xmlNsPtr xmpp_xmlSearchNs(xmlDocPtr doc, xmlNodePtr node, const xmlChar *nameSpace)
{
xmlNodePtr rootNode = xmlDocGetRootElement(doc);
xmlNodePtr currentNode = node;
while (currentNode && currentNode != rootNode)
{
xmlNsPtr currentNs = currentNode->nsDef;
while (currentNs)
{
if (currentNs->href != NULL)
{
if ((currentNs->prefix == NULL) && (nameSpace == NULL))
{
return currentNs;
}
if ((currentNs->prefix != NULL) && (nameSpace != NULL))
{
if (xmlStrEqual(currentNs->prefix, nameSpace))
return currentNs;
}
}
currentNs = currentNs->next;
}
currentNode = currentNode->parent;
}
return NULL;
}
/**
* SAX parser C-style callback.
* Invoked when a new node element is started.
**/
static void xmpp_xmlStartElement(void *ctx, const xmlChar *nodeName,
const xmlChar *nodePrefix,
const xmlChar *nodeUri,
int nb_namespaces,
const xmlChar **namespaces,
int nb_attributes,
int nb_defaulted,
const xmlChar **attributes)
{
int i, j;
xmlNsPtr lastAddedNs = NULL;
xmlParserCtxt *ctxt = (xmlParserCtxt *)ctx;
// We store the parent node in the context's node pointer.
// We keep this updated by "pushing" the node in the startElement method,
// and "popping" the node in the endElement method.
xmlNodePtr parent = ctxt->node;
// Create the node
xmlNodePtr newNode = xmlNewDocNode(ctxt->myDoc, NULL, nodeName, NULL);
CHECK_FOR_NULL(newNode);
// Add the node to the tree
if (parent == NULL)
{
// Root node
xmlAddChild((xmlNodePtr)ctxt->myDoc, newNode);
}
else
{
xmlAddChild(parent, newNode);
}
// Process the namespaces
for (i = 0, j = 0; j < nb_namespaces; j++)
{
// Extract namespace prefix and uri
const xmlChar *nsPrefix = namespaces[i++];
const xmlChar *nsUri = namespaces[i++];
// Create the namespace
xmlNsPtr newNs = xmlNewNs(NULL, nsUri, nsPrefix);
CHECK_FOR_NULL(newNs);
// Add namespace to node.
// Each node has a linked list of nodes (in the nsDef variable).
// The linked list is forward only.
// In other words, each ns has a next, but not a prev pointer.
if (newNode->nsDef == NULL)
{
newNode->nsDef = newNs;
lastAddedNs = newNs;
}
else
{
if(lastAddedNs != NULL)
{
lastAddedNs->next = newNs;
}
lastAddedNs = newNs;
}
// Is this the namespace for the node?
if (nodeUri && (nodePrefix == nsPrefix))
{
// Ex 1: node == <stream:stream xmlns:stream="url"> && newNs == stream:url
// Ex 2: node == <starttls xmlns="url"> && newNs == null:url
newNode->ns = newNs;
}
}
// Search for the node's namespace if it wasn't already found
if ((nodeUri) && (newNode->ns == NULL))
{
newNode->ns = xmpp_xmlSearchNs(ctxt->myDoc, newNode, nodePrefix);
if (newNode->ns == NULL)
{
// We use href==NULL in the case of an element creation where the namespace was not defined.
//
// We do NOT use xmlNewNs(newNode, nodeUri, nodePrefix) because that method doesn't properly add
// the namespace to BOTH nsDef and ns.
xmlNsPtr newNs = xmlNewNs(NULL, nodeUri, nodePrefix);
CHECK_FOR_NULL(newNs);
if (newNode->nsDef == NULL)
{
newNode->nsDef = newNs;
}
else if(lastAddedNs != NULL)
{
lastAddedNs->next = newNs;
}
newNode->ns = newNs;
}
}
// Process all the attributes
for (i = 0, j = 0; j < nb_attributes; j++)
{
const xmlChar *attrName = attributes[i++];
const xmlChar *attrPrefix = attributes[i++];
const xmlChar *attrUri = attributes[i++];
const xmlChar *valueBegin = attributes[i++];
const xmlChar *valueEnd = attributes[i++];
// The attribute value might contain character references which need to be decoded.
//
// "Franks &#38; Beans" -> "Franks & Beans"
xmlChar *value = xmlStringLenDecodeEntities(ctxt, // the parser context
valueBegin, // the input string
(int)(valueEnd - valueBegin), // the input string length
(XML_SUBSTITUTE_REF), // what to substitue
0, 0, 0); // end markers, 0 if none
CHECK_FOR_NULL(value);
if ((attrPrefix == NULL) && (attrUri == NULL))
{
// Normal attribute - no associated namespace
xmlAttrPtr newAttr = xmlNewProp(newNode, attrName, value);
CHECK_FOR_NULL(newAttr);
}
else
{
// Find the namespace for the attribute
xmlNsPtr attrNs = xmpp_xmlSearchNs(ctxt->myDoc, newNode, attrPrefix);
if (attrNs != NULL)
{
xmlAttrPtr newAttr = xmlNewNsProp(newNode, attrNs, attrName, value);
CHECK_FOR_NULL(newAttr);
}
else
{
attrNs = xmlNewNs(NULL, NULL, nodePrefix);
CHECK_FOR_NULL(attrNs);
xmlAttrPtr newAttr = xmlNewNsProp(newNode, attrNs, attrName, value);
CHECK_FOR_NULL(newAttr);
}
}
xmlFree(value);
}
// Update our parent node pointer
ctxt->node = newNode;
// Invoke delegate methods if needed
xmpp_postStartElement(ctxt);
}
/**
* SAX parser C-style callback.
* Invoked when characters are found within a node.
**/
static void xmpp_xmlCharacters(void *ctx, const xmlChar *ch, int len)
{
xmlParserCtxt *ctxt = (xmlParserCtxt *)ctx;
if (ctxt->node != NULL)
{
xmlNodePtr textNode = xmlNewTextLen(ch, len);
// xmlAddChild(xmlNodePtr parent, xmlNodePtr cur)
//
// Add a new node to @parent, at the end of the child list
// merging adjacent TEXT nodes (in which case @cur is freed).
xmlAddChild(ctxt->node, textNode);
}
}
/**
* SAX parser C-style callback.
* Invoked when a new node element is ended.
**/
static void xmpp_xmlEndElement(void *ctx, const xmlChar *localname,
const xmlChar *prefix,
const xmlChar *URI)
{
xmlParserCtxt *ctxt = (xmlParserCtxt *)ctx;
// Update our parent node pointer
if (ctxt->node != NULL)
ctxt->node = ctxt->node->parent;
// Invoke delegate methods if needed
xmpp_postEndElement(ctxt);
}
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
{
return [self initWithDelegate:aDelegate delegateQueue:dq parserQueue:NULL];
}
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq parserQueue:(dispatch_queue_t)pq
{
if ((self = [super init]))
{
delegate = aDelegate;
delegateQueue = dq;
#if !OS_OBJECT_USE_OBJC
if (delegateQueue)
dispatch_retain(delegateQueue);
#endif
if (pq) {
parserQueue = pq;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(parserQueue);
#endif
}
else {
parserQueue = dispatch_queue_create("xmpp.parser", NULL);
}
xmppParserQueueTag = &xmppParserQueueTag;
dispatch_queue_set_specific(parserQueue, xmppParserQueueTag, xmppParserQueueTag, NULL);
hasReportedRoot = NO;
depth = 0;
// Create SAX handler
xmlSAXHandler saxHandler;
memset(&saxHandler, 0, sizeof(xmlSAXHandler));
saxHandler.initialized = XML_SAX2_MAGIC;
saxHandler.startElementNs = xmpp_xmlStartElement;
saxHandler.characters = xmpp_xmlCharacters;
saxHandler.endElementNs = xmpp_xmlEndElement;
// Create the push parser context
parserCtxt = xmlCreatePushParserCtxt(&saxHandler, NULL, NULL, 0, NULL);
// Note: This method copies the saxHandler, so we don't have to keep it around.
// Create the document to hold the parsed elements
parserCtxt->myDoc = xmlNewDoc(parserCtxt->version);
// Store reference to ourself
parserCtxt->_private = (__bridge void *)(self);
// Note: The parserCtxt also has a userData variable, but it is used by the DOM building functions.
// If we put a value there, it actually causes a crash!
// We need to be sure to use the _private variable which libxml won't touch.
}
return self;
}
- (void)dealloc
{
if (parserCtxt)
{
// The xmlFreeParserCtxt method will not free the created document in parserCtxt->myDoc.
if (parserCtxt->myDoc)
{
// Free the created xmlDoc
xmlFreeDoc(parserCtxt->myDoc);
}
xmlFreeParserCtxt(parserCtxt);
}
#if !OS_OBJECT_USE_OBJC
if (delegateQueue)
dispatch_release(delegateQueue);
if (parserQueue)
dispatch_release(parserQueue);
#endif
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
#if !OS_OBJECT_USE_OBJC
if (newDelegateQueue)
dispatch_retain(newDelegateQueue);
#endif
dispatch_block_t block = ^{
delegate = newDelegate;
#if !OS_OBJECT_USE_OBJC
if (delegateQueue)
dispatch_release(delegateQueue);
#endif
delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(xmppParserQueueTag))
block();
else
dispatch_async(parserQueue, block);
}
- (void)parseData:(NSData *)data
{
dispatch_block_t block = ^{ @autoreleasepool {
int result = xmlParseChunk(parserCtxt, (const char *)[data bytes], (int)[data length], 0);
if (result == 0)
{
if (delegateQueue && [delegate respondsToSelector:@selector(xmppParserDidParseData:)])
{
__strong id theDelegate = delegate;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParserDidParseData:self];
}});
}
}
else
{
if (delegateQueue && [delegate respondsToSelector:@selector(xmppParser:didFail:)])
{
NSError *error;
xmlError *xmlErr = xmlCtxtGetLastError(parserCtxt);
if (xmlErr->message)
{
NSString *errMsg = [NSString stringWithFormat:@"%s", xmlErr->message];
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:info];
}
else
{
error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:nil];
}
__strong id theDelegate = delegate;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate xmppParser:self didFail:error];
}});
}
}
}};
if (dispatch_get_specific(xmppParserQueueTag))
block();
else
dispatch_async(parserQueue, block);
}
@end
+38
View File
@@ -0,0 +1,38 @@
#import <Foundation/Foundation.h>
#import "XMPPElement.h"
/**
* The XMPPPresence class represents a <presence> element.
* It extends XMPPElement, which in turn extends NSXMLElement.
* All <presence> elements that go in and out of the
* xmpp stream will automatically be converted to XMPPPresence objects.
*
* This class exists to provide developers an easy way to add functionality to presence processing.
* Simply add your own category to XMPPPresence to extend it with your own custom methods.
**/
@interface XMPPPresence : XMPPElement
// Converts an NSXMLElement to an XMPPPresence element in place (no memory allocations or copying)
+ (XMPPPresence *)presenceFromElement:(NSXMLElement *)element;
+ (XMPPPresence *)presence;
+ (XMPPPresence *)presenceWithType:(NSString *)type;
+ (XMPPPresence *)presenceWithType:(NSString *)type to:(XMPPJID *)to;
- (id)init;
- (id)initWithType:(NSString *)type;
- (id)initWithType:(NSString *)type to:(XMPPJID *)to;
- (NSString *)type;
- (NSString *)show;
- (NSString *)status;
- (int)priority;
- (int)intShow;
- (BOOL)isErrorPresence;
@end
+145
View File
@@ -0,0 +1,145 @@
#import "XMPPPresence.h"
#import "XMPPJID.h"
#import "NSXMLElement+XMPP.h"
#import <objc/runtime.h>
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@implementation XMPPPresence
#if DEBUG
+ (void)initialize
{
// We use the object_setClass method below to dynamically change the class from a standard NSXMLElement.
// The size of the two classes is expected to be the same.
//
// If a developer adds instance methods to this class, bad things happen at runtime that are very hard to debug.
// This check is here to aid future developers who may make this mistake.
//
// For Fearless And Experienced Objective-C Developers:
// It may be possible to support adding instance variables to this class if you seriously need it.
// To do so, try realloc'ing self after altering the class, and then initialize your variables.
size_t superSize = class_getInstanceSize([NSXMLElement class]);
size_t ourSize = class_getInstanceSize([XMPPPresence class]);
if (superSize != ourSize)
{
NSLog(@"Adding instance variables to XMPPPresence is not currently supported!");
exit(15);
}
}
#endif
+ (XMPPPresence *)presenceFromElement:(NSXMLElement *)element
{
object_setClass(element, [XMPPPresence class]);
return (XMPPPresence *)element;
}
+ (XMPPPresence *)presence
{
return [[XMPPPresence alloc] init];
}
+ (XMPPPresence *)presenceWithType:(NSString *)type
{
return [[XMPPPresence alloc] initWithType:type to:nil];
}
+ (XMPPPresence *)presenceWithType:(NSString *)type to:(XMPPJID *)to
{
return [[XMPPPresence alloc] initWithType:type to:to];
}
- (id)init
{
self = [super initWithName:@"presence"];
return self;
}
- (id)initWithType:(NSString *)type
{
return [self initWithType:type to:nil];
}
- (id)initWithType:(NSString *)type to:(XMPPJID *)to
{
if ((self = [super initWithName:@"presence"]))
{
if (type)
[self addAttributeWithName:@"type" stringValue:type];
if (to)
[self addAttributeWithName:@"to" stringValue:[to full]];
}
return self;
}
- (id)initWithXMLString:(NSString *)string error:(NSError *__autoreleasing *)error
{
if((self = [super initWithXMLString:string error:error])){
self = [XMPPPresence presenceFromElement:self];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
NSXMLElement *element = [super copyWithZone:zone];
return [XMPPPresence presenceFromElement:element];
}
- (NSString *)type
{
NSString *type = [self attributeStringValueForName:@"type"];
if(type)
return [type lowercaseString];
else
return @"available";
}
- (NSString *)show
{
return [[self elementForName:@"show"] stringValue];
}
- (NSString *)status
{
return [[self elementForName:@"status"] stringValue];
}
- (int)priority
{
return [[[self elementForName:@"priority"] stringValue] intValue];
}
- (int)intShow
{
NSString *show = [self show];
if([show isEqualToString:@"dnd"])
return 0;
if([show isEqualToString:@"xa"])
return 1;
if([show isEqualToString:@"away"])
return 2;
if([show isEqualToString:@"chat"])
return 4;
return 3;
}
- (BOOL)isErrorPresence
{
return [[self type] isEqualToString:@"error"];
}
@end
+1111
View File
File diff suppressed because it is too large. Load diff
+5105
View File
File diff suppressed because it is too large. Load diff