#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 (for an account at a server) // or of the form (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 // (for a particular authorized client or device associated with an account) // or of the form (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