#import "HTTPAuthenticationRequest.h" #import "HTTPMessage.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif @interface HTTPAuthenticationRequest (PrivateAPI) - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; @end @implementation HTTPAuthenticationRequest - (id)initWithRequest:(HTTPMessage *)request { if ((self = [super init])) { NSString *authInfo = [request headerField:@"Authorization"]; isBasic = NO; if ([authInfo length] >= 6) { isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame; } isDigest = NO; if ([authInfo length] >= 7) { isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame; } if (isBasic) { NSMutableString *temp = [[authInfo substringFromIndex:6] mutableCopy]; CFStringTrimWhitespace((__bridge CFMutableStringRef)temp); base64Credentials = [temp copy]; } if (isDigest) { username = [self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo]; realm = [self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo]; nonce = [self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo]; uri = [self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo]; // It appears from RFC 2617 that the qop is to be given unquoted // Tests show that Firefox performs this way, but Safari does not // Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; if(qop && ([qop characterAtIndex:0] == '"')) { qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; } nc = [self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo]; cnonce = [self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo]; response = [self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo]; } } return self; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Accessors: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (BOOL)isBasic { return isBasic; } - (BOOL)isDigest { return isDigest; } - (NSString *)base64Credentials { return base64Credentials; } - (NSString *)username { return username; } - (NSString *)realm { return realm; } - (NSString *)nonce { return nonce; } - (NSString *)uri { return uri; } - (NSString *)qop { return qop; } - (NSString *)nc { return nc; } - (NSString *)cnonce { return cnonce; } - (NSString *)response { return response; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Private API: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Retrieves a "Sub Header Field Value" from a given header field value. * The sub header field is expected to be quoted. * * In the following header field: * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa". **/ - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header { NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]]; if(startRange.location == NSNotFound) { // The param was not found anywhere in the header return nil; } NSUInteger postStartRangeLocation = startRange.location + startRange.length; NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange]; if(endRange.location == NSNotFound) { // The ending double-quote was not found anywhere in the header return nil; } NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); return [header substringWithRange:subHeaderRange]; } /** * Retrieves a "Sub Header Field Value" from a given header field value. * The sub header field is expected to not be quoted. * * In the following header field: * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth". **/ - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header { NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]]; if(startRange.location == NSNotFound) { // The param was not found anywhere in the header return nil; } NSUInteger postStartRangeLocation = startRange.location + startRange.length; NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange]; if(endRange.location == NSNotFound) { // The ending comma was not found anywhere in the header // However, if the nonquoted param is at the end of the string, there would be no comma // This is only possible if there are no spaces anywhere NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange]; if(endRange2.location != NSNotFound) { return nil; } else { return [header substringWithRange:postStartRange]; } } else { NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); return [header substringWithRange:subHeaderRange]; } } @end