diff --git a/ChangeLog b/ChangeLog index 418cbc45d..563da4719 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,11 @@ 2006-07-04 Richard Frith-Macdonald * Source/NSTask.m: On mingw32 create subtask with CREATE_NO_WINDOW + * Source/GSURLPrivate.h: Generalise GSHTTPAuthentication + * Source/GSHTTPAuthentication.m: Generalise to support Basic auth + * Source/NSURLProtectionSpace.m: Optimise a little. + * Source/GSHTTPURLHandle.m: Changes for GSHTTPAuthentication update. + Don't send basic authentication info unless challenged by server. 2006-07-02 Richard Frith-Macdonald diff --git a/Source/GSHTTPAuthentication.m b/Source/GSHTTPAuthentication.m index 33bef211b..cb8a56972 100644 --- a/Source/GSHTTPAuthentication.m +++ b/Source/GSHTTPAuthentication.m @@ -86,17 +86,19 @@ static GSMimeParser *mimeParser = nil; } } -+ (GSHTTPAuthentication *) digestWithCredential: (NSURLCredential*)credential - inProtectionSpace: (NSURLProtectionSpace*)space ++ (GSHTTPAuthentication *) authenticationWithCredential: + (NSURLCredential*)credential + inProtectionSpace: (NSURLProtectionSpace*)space { - NSMutableDictionary *cDict; - NSURLProtectionSpace *known; - GSHTTPAuthentication *digest = nil; + NSMutableDictionary *cDict = nil; + NSURLProtectionSpace *known = nil; + GSHTTPAuthentication *authentication = nil; [storeLock lock]; + /* * Keep track of known protection spaces so we don't make lots of - * duplicate copies, but share one copy between digest objects. + * duplicate copies, but share one copy between authentication objects. */ known = [spaces member: space]; if (known == nil) @@ -112,19 +114,21 @@ static GSMimeParser *mimeParser = nil; [store setObject: cDict forKey: space]; RELEASE(cDict); } - digest = [cDict objectForKey: credential]; - if (digest == nil) + authentication = [cDict objectForKey: credential]; + + if (authentication == nil) { - digest = [[GSHTTPAuthentication alloc] initWithCredential: credential - inProtectionSpace: space]; - [cDict setObject: digest forKey: [digest credential]]; + authentication = [[GSHTTPAuthentication alloc] + initWithCredential: credential + inProtectionSpace: space]; + [cDict setObject: authentication forKey: [authentication credential]]; } else { - RETAIN(digest); + RETAIN(authentication); } [storeLock unlock]; - return AUTORELEASE(digest); + return AUTORELEASE(authentication); } + (NSURLProtectionSpace*) protectionSpaceForAuthentication: (NSString*)auth @@ -187,7 +191,7 @@ static GSMimeParser *mimeParser = nil; * found for the URL, assume that it is unchanged. */ if ([[space realm] isEqualToString: realm] - && [[space authenticationMethod] isEqualToString: method]) + && [space authenticationMethod] == method) { return space; } @@ -337,155 +341,194 @@ static GSMimeParser *mimeParser = nil; method: (NSString*)method path: (NSString*)path { - NSString *realm = nil; - NSString *qop = nil; - NSString *nonce = nil; - NSString *opaque = nil; - NSString *stale = @"FALSE"; - NSString *algorithm = @"MD5"; - NSString *cnonce; - NSString *HA1; - NSString *HA2; - NSString *response; NSMutableString *authorisation; - int nc; - if (authentication != nil) + if ([self->_space authenticationMethod] + == NSURLAuthenticationMethodHTTPDigest) { - NSScanner *sc; - NSString *key; - NSString *val; + NSString *realm = nil; + NSString *qop = nil; + NSString *nonce = nil; + NSString *opaque = nil; + NSString *stale = @"FALSE"; + NSString *algorithm = @"MD5"; + NSString *cnonce; + NSString *HA1; + NSString *HA2; + NSString *response; + int nc; - sc = [NSScanner scannerWithString: authentication]; - if ([sc scanString: @"Digest" intoString: 0] == NO) + if (authentication != nil) { - NSDebugMLog(@"Bad format HTTP digest in '%@'", authentication); - return nil; // Not a digest authentication + NSScanner *sc; + NSString *key; + NSString *val; + + sc = [NSScanner scannerWithString: authentication]; + if ([sc scanString: @"Digest" intoString: 0] == NO) + { + NSDebugMLog(@"Bad format HTTP digest in '%@'", authentication); + return nil; // Not a digest authentication + } + while ((key = [mimeParser scanName: sc]) != nil) + { + if ([sc scanString: @"=" intoString: 0] == NO) + { + NSDebugMLog(@"Missing '=' in HTTP digest '%@'", + authentication); + return nil; // Bad name=value specification + } + if ((val = [mimeParser scanToken: sc]) == nil) + { + NSDebugMLog(@"Missing value in HTTP digest '%@'", + authentication); + return nil; // Bad name=value specification + } + if ([key caseInsensitiveCompare: @"realm"] == NSOrderedSame) + { + realm = val; + } + if ([key caseInsensitiveCompare: @"qop"] == NSOrderedSame) + { + qop = val; + } + if ([key caseInsensitiveCompare: @"nonce"] == NSOrderedSame) + { + nonce = val; + } + if ([key caseInsensitiveCompare: @"opaque"] == NSOrderedSame) + { + opaque = val; + } + if ([key caseInsensitiveCompare: @"stale"] == NSOrderedSame) + { + stale = val; + } + if ([key caseInsensitiveCompare: @"algorithm"] == NSOrderedSame) + { + algorithm = val; + } + if ([sc scanString: @"," intoString: 0] == NO) + { + break; // No more in list. + } + } + + if (realm == nil) + { + NSDebugMLog(@"Missing HTTP digest realm in '%@'", authentication); + return nil; + } + if ([realm isEqualToString: [self->_space realm]] == NO) + { + NSDebugMLog(@"Bad HTTP digest realm in '%@'", authentication); + return nil; + } + if (nonce == nil) + { + NSDebugMLog(@"Missing HTTP digest nonce in '%@'", authentication); + return nil; + } + + if ([algorithm isEqualToString: @"MD5"] == NO) + { + NSDebugMLog(@"Unsupported HTTP digest algorithm in '%@'", + authentication); + return nil; + } + if (![[qop componentsSeparatedByString: @","] + containsObject: @"auth"]) + { + NSDebugMLog(@"Unsupported/missing HTTP digest qop in '%@'", + authentication); + return nil; + } + + [self->_lock lock]; + if ([stale boolValue] == YES + || [nonce isEqualToString: _nonce] == NO) + { + _nc = 1; + } + ASSIGN(_nonce, nonce); + ASSIGN(_qop, qop); + ASSIGN(_opaque, opaque); } - while ((key = [mimeParser scanName: sc]) != nil) + else { - if ([sc scanString: @"=" intoString: 0] == NO) - { - NSDebugMLog(@"Missing '=' in HTTP digest '%@'", authentication); - return nil; // Bad name=value specification - } - if ((val = [mimeParser scanToken: sc]) == nil) - { - NSDebugMLog(@"Missing value in HTTP digest '%@'", authentication); - return nil; // Bad name=value specification - } - if ([key caseInsensitiveCompare: @"realm"] == NSOrderedSame) - { - realm = val; - } - if ([key caseInsensitiveCompare: @"qop"] == NSOrderedSame) - { - qop = val; - } - if ([key caseInsensitiveCompare: @"nonce"] == NSOrderedSame) - { - nonce = val; - } - if ([key caseInsensitiveCompare: @"opaque"] == NSOrderedSame) - { - opaque = val; - } - if ([key caseInsensitiveCompare: @"stale"] == NSOrderedSame) - { - stale = val; - } - if ([key caseInsensitiveCompare: @"algorithm"] == NSOrderedSame) - { - algorithm = val; - } - if ([sc scanString: @"," intoString: 0] == NO) - { - break; // No more in list. - } + [self->_lock lock]; + nonce = _nonce; + opaque = _opaque; + qop = _qop; + realm = [self->_space realm]; } - if (realm == nil) + nc = _nc++; + + qop = @"auth"; + + cnonce = [[[[[NSProcessInfo processInfo] globallyUniqueString] + dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; + + HA1 = [[[[NSString stringWithFormat: @"%@:%@:%@", + [self->_credential user], realm, [self->_credential password]] + dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; + + HA2 = [[[[NSString stringWithFormat: @"%@:%@", method, path] + dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; + + response = [[[[NSString stringWithFormat: @"%@:%@:%08x:%@:%@:%@", + HA1, nonce, nc, cnonce, qop, HA2] + dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; + + authorisation = [NSMutableString stringWithCapacity: 512]; + [authorisation appendFormat: @"Digest realm=\"%@\"", realm]; + [authorisation appendFormat: @",username=\"%@\"", + [self->_credential user]]; + [authorisation appendFormat: @",nonce=\"%@\"", nonce]; + [authorisation appendFormat: @",uri=\"%@\"", path]; + [authorisation appendFormat: @",response=\"%@\"", response]; + [authorisation appendFormat: @",qop=\"%@\"", qop]; + [authorisation appendFormat: @",nc=%08x", nc]; + [authorisation appendFormat: @",cnonce=\"%@\"", cnonce]; + if (opaque != nil) { - NSDebugMLog(@"Missing HTTP digest realm in '%@'", authentication); - return nil; - } - if ([realm isEqualToString: [self->_space realm]] == NO) - { - NSDebugMLog(@"Bad HTTP digest realm in '%@'", authentication); - return nil; - } - if (nonce == nil) - { - NSDebugMLog(@"Missing HTTP digest nonce in '%@'", authentication); - return nil; + [authorisation appendFormat: @",opaque=\"%@\"", opaque]; } - if ([algorithm isEqualToString: @"MD5"] == NO) - { - NSDebugMLog(@"Unsupported HTTP digest algorithm in '%@'", - authentication); - return nil; - } - if (![[qop componentsSeparatedByString: @","] containsObject: @"auth"]) - { - NSDebugMLog(@"Unsupported/missing HTTP digest qop in '%@'", - authentication); - return nil; - } - - [self->_lock lock]; - if ([stale boolValue] == YES - || [nonce isEqualToString: _nonce] == NO) - { - _nc = 1; - } - ASSIGN(_nonce, nonce); - ASSIGN(_qop, qop); - ASSIGN(_opaque, opaque); + [self->_lock unlock]; } else { - [self->_lock lock]; - nonce = _nonce; - opaque = _opaque; - qop = _qop; - realm = [self->_space realm]; + NSString *toEncode; + +// FIXME ... should support other methods + if (authentication != nil) + { + NSScanner *sc; + + sc = [NSScanner scannerWithString: authentication]; + if ([sc scanString: @"Basic" intoString: 0] == NO) + { + NSDebugMLog(@"Bad format HTTP basic in '%@'", authentication); + return nil; // Not a basic authentication + } + } + + authorisation = [NSMutableString stringWithCapacity: 64]; + if ([[self->_credential password] length] > 0) + { + toEncode = [NSString stringWithFormat: @"%@:%@", + [self->_credential user], [self->_credential password]]; + } + else + { + toEncode = [NSString stringWithFormat: @"%@", + [self->_credential user]]; + } + [authorisation appendFormat: @"Basic %@", + [GSMimeDocument encodeBase64String: toEncode]]; } - - nc = _nc++; - - qop = @"auth"; - - cnonce = [[[[[NSProcessInfo processInfo] globallyUniqueString] - dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; - - HA1 = [[[[NSString stringWithFormat: @"%@:%@:%@", - [self->_credential user], realm, [self->_credential password]] - dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; - - HA2 = [[[[NSString stringWithFormat: @"%@:%@", method, path] - dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; - - response = [[[[NSString stringWithFormat: @"%@:%@:%08x:%@:%@:%@", - HA1, nonce, nc, cnonce, qop, HA2] - dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; - - authorisation = [NSMutableString stringWithCapacity: 512]; - [authorisation appendFormat: @"Digest realm=\"%@\"", realm]; - [authorisation appendFormat: @",username=\"%@\"", [self->_credential user]]; - [authorisation appendFormat: @",nonce=\"%@\"", nonce]; - [authorisation appendFormat: @",uri=\"%@\"", path]; - [authorisation appendFormat: @",response=\"%@\"", response]; - [authorisation appendFormat: @",qop=\"%@\"", qop]; - [authorisation appendFormat: @",nc=%08x", nc]; - [authorisation appendFormat: @",cnonce=\"%@\"", cnonce]; - if (opaque != nil) - { - [authorisation appendFormat: @",opaque=\"%@\"", opaque]; - } - - [self->_lock unlock]; - return authorisation; } diff --git a/Source/GSHTTPURLHandle.m b/Source/GSHTTPURLHandle.m index 8276a0061..558f7cb8b 100644 --- a/Source/GSHTTPURLHandle.m +++ b/Source/GSHTTPURLHandle.m @@ -418,79 +418,53 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data) } if ((id)NSMapGet(wProperties, (void*)@"Authorization") == nil) { - if ([u user] != nil) + NSURLProtectionSpace *space; + + /* + * If we have username/password stored in the URL, and there is a + * known protection space for that URL, we generate an authentication + * header. + */ + if ([u user] != nil + && (space = [GSHTTPAuthentication protectionSpaceForURL: u]) != nil) { - NSString *auth = nil; - NSURLProtectionSpace *space; + NSString *auth; + GSHTTPAuthentication *authentication; + NSURLCredential *cred; + NSString *method; /* - * If the URL we are loading is in a digest authentication space - * we try to create an authorization header using any existing - * cached information so that we can avoid the wasteful - * challenge/response dialogue. + * Create credential from user and password + * stored in the URL. */ - space = [GSHTTPAuthentication protectionSpaceForURL: u]; - if (space != nil && [[space authenticationMethod] isEqual: - NSURLAuthenticationMethodHTTPDigest] == YES) + cred = [[NSURLCredential alloc] + initWithUser: [u user] + password: [u password] + persistence: NSURLCredentialPersistenceForSession]; + + authentication = [GSHTTPAuthentication + authenticationWithCredential: cred + inProtectionSpace: space]; + + RELEASE(cred); + + method = [request objectForKey: GSHTTPPropertyMethodKey]; + if (method == nil) { - NSURLCredential *cred; - GSHTTPAuthentication *digest; - NSString *method; - - /* - * Create credential from user and password - * stored in the URL. - */ - cred = [[NSURLCredential alloc] - initWithUser: [u user] - password: [u password] - persistence: NSURLCredentialPersistenceForSession]; - - /* - * Get the digest object and ask it for a header - * to use for authorisation. - */ - digest = [GSHTTPAuthentication - digestWithCredential: cred - inProtectionSpace: space]; - RELEASE(cred); - - method = [request objectForKey: GSHTTPPropertyMethodKey]; - if (method == nil) + if ([wData length] > 0) { - if ([wData length] > 0) - { - method = @"POST"; - } - else - { - method = @"GET"; - } - } - auth = [digest authorizationForAuthentication: nil - method: method - path: [u path]]; - } - - if (auth == nil) - { - /* - * Not able to do a digest authentication, - * so do a basic authentication in case the - * server accepts it. - */ - if ([[u password] length] > 0) - { - auth = [NSString stringWithFormat: @"%@:%@", - [u user], [u password]]; + method = @"POST"; } else { - auth = [NSString stringWithFormat: @"%@", [u user]]; + method = @"GET"; } - auth = [NSString stringWithFormat: @"Basic %@", - [GSMimeDocument encodeBase64String: auth]]; } + + auth = [authentication authorizationForAuthentication: nil + method: method + path: [u path]]; + NSMapInsert(wProperties, (void*)@"Authorization", (void*)auth); } } @@ -632,65 +606,62 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data) val = [info objectForKey: NSHTTPPropertyStatusCodeKey]; if ([val intValue] == 401 && self->challenged < 2) { - NSString *a; GSMimeHeader *ah; self->challenged++; // Prevent repeated challenge/auth - a = (id)NSMapGet(wProperties, (void*)@"Authorization"); if ((ah = [document headerNamed: @"WWW-Authenticate"]) != nil) { NSURLProtectionSpace *space; NSString *ac; + NSURLCredential *cred; + GSHTTPAuthentication *authentication; + NSString *method; + NSString *a; ac = [ah value]; space = [GSHTTPAuthentication protectionSpaceForAuthentication: ac requestURL: url]; - if (space != nil) + + /* + * Create credential from user and password + * stored in the URL. + */ + cred = [[NSURLCredential alloc] + initWithUser: [url user] + password: [url password] + persistence: NSURLCredentialPersistenceForSession]; + + /* + * Get the digest object and ask it for a header + * to use for authorisation. + */ + authentication = [GSHTTPAuthentication + authenticationWithCredential: cred + inProtectionSpace: space]; + + RELEASE(cred); + + method = [request objectForKey: GSHTTPPropertyMethodKey]; + if (method == nil) { - NSURLCredential *cred; - GSHTTPAuthentication *digest; - NSString *method; - NSString *a; - - /* - * Create credential from user and password - * stored in the URL. - */ - cred = [[NSURLCredential alloc] - initWithUser: [url user] - password: [url password] - persistence: NSURLCredentialPersistenceForSession]; - - /* - * Get the digest object and ask it for a header - * to use for authorisation. - */ - digest = [GSHTTPAuthentication - digestWithCredential: cred - inProtectionSpace: space]; - RELEASE(cred); - - method = [request objectForKey: GSHTTPPropertyMethodKey]; - if (method == nil) + if ([wData length] > 0) { - if ([wData length] > 0) - { - method = @"POST"; - } - else - { - method = @"GET"; - } + method = @"POST"; } - a = [digest authorizationForAuthentication: ac + else + { + method = @"GET"; + } + } + + a = [authentication authorizationForAuthentication: ac method: method path: [url path]]; - if (a != nil) - { - [self writeProperty: a forKey: @"Authorization"]; - [self _tryLoadInBackground: u]; - return; // Retrying. - } + if (a != nil) + { + [self writeProperty: a forKey: @"Authorization"]; + [self _tryLoadInBackground: u]; + return; // Retrying. } } } diff --git a/Source/GSURLPrivate.h b/Source/GSURLPrivate.h index 67eb0102f..ff2f22e43 100644 --- a/Source/GSURLPrivate.h +++ b/Source/GSURLPrivate.h @@ -82,8 +82,10 @@ /* * Return the object for the specified credential/protection space. */ -+ (GSHTTPAuthentication *) digestWithCredential: (NSURLCredential*)credential - inProtectionSpace: (NSURLProtectionSpace*)space; ++ (GSHTTPAuthentication *) authenticationWithCredential: + (NSURLCredential*)credential + inProtectionSpace: (NSURLProtectionSpace*)space; + /* * Create/return the protection space involved in the specified authentication * header returned in a response to a request sent to the URL. @@ -109,7 +111,7 @@ path: (NSString*)path; - (NSURLCredential *) credential; - (id) initWithCredential: (NSURLCredential*)credential - inProtectionSpace: (NSURLProtectionSpace*)space; + inProtectionSpace: (NSURLProtectionSpace*)space; - (NSURLProtectionSpace *) space; @end diff --git a/Source/NSURLProtectionSpace.m b/Source/NSURLProtectionSpace.m index f7da958ce..262e6a083 100644 --- a/Source/NSURLProtectionSpace.m +++ b/Source/NSURLProtectionSpace.m @@ -43,8 +43,8 @@ typedef struct { int port; NSString *protocol; NSString *realm; - NSString *proxyType; - NSString *authenticationMethod; + NSString *proxyType; // Not retained + NSString *authenticationMethod; // Not retained BOOL isProxy; } Internal; @@ -90,7 +90,7 @@ typedef struct { if (o != nil) { inst->isProxy = this->isProxy; - ASSIGN(inst->proxyType, this->proxyType); + inst->proxyType = this->proxyType; } return o; } @@ -102,9 +102,7 @@ typedef struct { { RELEASE(this->host); RELEASE(this->protocol); - RELEASE(this->proxyType); RELEASE(this->realm); - RELEASE(this->authenticationMethod); NSZoneFree([self zone], this); } [super dealloc]; @@ -114,7 +112,7 @@ typedef struct { { return [[self host] hash] + [self port] + [[self realm] hash] + [[self protocol] hash] - + [[self proxyType] hash] + [[self authenticationMethod] hash]; + + (uintptr_t)this->proxyType + (uintptr_t)this->authenticationMethod; } - (NSString *) host @@ -133,7 +131,25 @@ authenticationMethod: (NSString *)authenticationMethod this->host = [host copy]; this->protocol = [protocol copy]; this->realm = [realm copy]; - this->authenticationMethod = [authenticationMethod copy]; + if ([authenticationMethod isEqualToString: + NSURLAuthenticationMethodHTMLForm] == YES) + { + this->authenticationMethod = NSURLAuthenticationMethodHTMLForm; + } + else if ([authenticationMethod isEqualToString: + NSURLAuthenticationMethodHTTPBasic] == YES) + { + this->authenticationMethod = NSURLAuthenticationMethodHTTPBasic; + } + else if ([authenticationMethod isEqualToString: + NSURLAuthenticationMethodHTTPDigest] == YES) + { + this->authenticationMethod = NSURLAuthenticationMethodHTTPDigest; + } + else + { + this->authenticationMethod = NSURLAuthenticationMethodDefault; + } this->port = port; this->proxyType = nil; this->isProxy = NO; @@ -155,9 +171,28 @@ authenticationMethod: (NSString *)authenticationMethod if (self != nil) { this->isProxy = YES; - ASSIGNCOPY(this->proxyType, type); + if ([type isEqualToString: NSURLProtectionSpaceFTPProxy] == YES) + { + this->proxyType = NSURLProtectionSpaceFTPProxy; + } + else if ([type isEqualToString: NSURLProtectionSpaceHTTPProxy] == YES) + { + this->proxyType = NSURLProtectionSpaceHTTPProxy; + } + else if ([type isEqualToString: NSURLProtectionSpaceHTTPSProxy] == YES) + { + this->proxyType = NSURLProtectionSpaceHTTPSProxy; + } + else if ([type isEqualToString: NSURLProtectionSpaceSOCKSProxy] == YES) + { + this->proxyType = NSURLProtectionSpaceSOCKSProxy; + } + else + { + DESTROY(self); // Bad proxy type. + } } - return NO; + return self; } - (BOOL) isEqual: (id)other @@ -237,13 +272,13 @@ authenticationMethod: (NSString *)authenticationMethod - (BOOL) receivesCredentialSecurely { - if ([this->authenticationMethod isEqual: NSURLAuthenticationMethodHTTPDigest]) + if (this->authenticationMethod == NSURLAuthenticationMethodHTTPDigest) { return YES; } if (this->isProxy) { - if ([this->proxyType isEqual: NSURLProtectionSpaceHTTPSProxy] == YES) + if (this->proxyType == NSURLProtectionSpaceHTTPSProxy) { return YES; }