604 lines
14 KiB
Objective-C
604 lines
14 KiB
Objective-C
#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
|