- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
+31
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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__)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 & 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
|
||||
@@ -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
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+5105
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user