diff --git a/ChangeLog b/ChangeLog index 9a7b37bd8..8d7d8db99 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,7 @@ * Source/NSURLProtectionSpace.m: Implement ([-hash]) and ([-isEqual:]) * Headers/Foundation/NSURLAuthenticationChallenge.h: Improve comments. Add GSHTTPDigest class to handle HTTP digest authentication. + * Source/GSHTTPURLHandle.m: Perform rudimentary digest authentication. 2006-06-17 Richard Frith-Macdonald diff --git a/Source/GSHTTPDigest.m b/Source/GSHTTPDigest.m index 0ab6fef4b..238366a5a 100644 --- a/Source/GSHTTPDigest.m +++ b/Source/GSHTTPDigest.m @@ -32,6 +32,7 @@ static NSMutableDictionary *store = nil; static GSLazyLock *storeLock = nil; +static GSMimeParser *mimeParser = nil; @interface NSData(GSHTTPDigest) - (NSString*) digestHex; @@ -73,6 +74,7 @@ static GSLazyLock *storeLock = nil; { if (store == nil) { + mimeParser = [GSMimeParser new]; store = [NSMutableDictionary new]; storeLock = [GSLazyLock new]; } @@ -107,6 +109,38 @@ static GSLazyLock *storeLock = nil; return AUTORELEASE(digest); } ++ (NSString*) digestRealmForAuthentication: (NSString*)authentication +{ + if (authentication != nil) + { + NSScanner *sc; + NSString *key; + NSString *val; + + sc = [NSScanner scannerWithString: authentication]; + if ([sc scanString: @"Digest" intoString: 0] == NO) + { + return nil; // Not a digest authentication + } + while ((key = [mimeParser scanName: sc]) != nil) + { + if ([sc scanString: @"=" intoString: 0] == NO) + { + return nil; // Bad name=value specification + } + if ((val = [mimeParser scanToken: sc]) == nil) + { + return nil; // Bad name=value specification + } + if ([key caseInsensitiveCompare: @"realm"] == NSOrderedSame) + { + return val; + } + } + } + return nil; +} + - (NSString*) authorizationForAuthentication: (NSString*)authentication method: (NSString*)method path: (NSString*)path @@ -121,34 +155,29 @@ static GSLazyLock *storeLock = nil; NSString *HA1; NSString *HA2; NSString *response; - NSString *authorisation; + NSMutableString *authorisation; int nc; if (authentication != nil) { - static GSMimeParser *p = nil; NSScanner *sc; NSString *key; NSString *val; - if (p == nil) - { - p = [GSMimeParser new]; - } 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 = [p scanName: sc]) != nil) + 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 = [p scanToken: sc]) == nil) + if ((val = [mimeParser scanToken: sc]) == nil) { NSDebugMLog(@"Missing value in HTTP digest '%@'", authentication); return nil; // Bad name=value specification @@ -198,11 +227,6 @@ static GSLazyLock *storeLock = nil; NSDebugMLog(@"Missing HTTP digest nonce in '%@'", authentication); return nil; } - if (opaque == nil) - { - NSDebugMLog(@"Missing HTTP digest opaque in '%@'", authentication); - return nil; - } if ([algorithm isEqual: @"MD5"] == NO) { @@ -253,11 +277,19 @@ static GSLazyLock *storeLock = nil; HA1, nonce, nc, cnonce, qop, HA2] dataUsingEncoding: NSUTF8StringEncoding] md5Digest] digestHex]; - authorisation = [NSString stringWithFormat: @"Digest username=\"%@\"," - @"realm=\"%@\",nonce=\"%@\",uri=\"%@\",qop=\"%@\",nc=%08x,cnonce=\"%@\"," - @"response=\"%@\",opaque=\"%@\"", - [self->_credential user], - realm, nonce, path, qop, nc, cnonce, response, opaque]; + 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]; diff --git a/Source/GSHTTPURLHandle.m b/Source/GSHTTPURLHandle.m index 5fecdc505..6f9553783 100644 --- a/Source/GSHTTPURLHandle.m +++ b/Source/GSHTTPURLHandle.m @@ -44,6 +44,7 @@ #include "GNUstepBase/GSMime.h" #include "GNUstepBase/GSLock.h" #include "NSCallBacks.h" +#include "GSURLPrivate.h" #include #ifdef HAVE_UNISTD_H @@ -569,17 +570,94 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data) * Retrieve essential keys from document */ info = [document headerNamed: @"http"]; + val = [info objectForKey: NSHTTPPropertyStatusCodeKey]; + if ([val intValue] == 401) + { + NSString *a; + GSMimeHeader *ah; + + a = (id)NSMapGet(wProperties, (void*)@"Authorization"); + if ([a hasPrefix: @"Basic"] == YES + && (ah = [document headerNamed: @"WWW-Authenticate"]) != nil) + { + NSString *realm; + NSString *ac; + + ac = [ah value]; + realm = [GSHTTPDigest digestRealmForAuthentication: ac]; + if (realm != nil) + { + NSURLProtectionSpace *space; + NSURLCredential *cred; + GSHTTPDigest *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]; + + /* + * Create protection space from the information in + * the URL and the realm of the authentication + * challenge. + */ + space = [[NSURLProtectionSpace alloc] + initWithHost: [url host] + port: [[url port] intValue] + protocol: [url scheme] + realm: realm + authenticationMethod: + NSURLAuthenticationMethodHTTPDigest]; + + /* + * Get the digest object and ask it for a header + * to use for authorisation. + */ + digest = [GSHTTPDigest digestWithCredential: cred + inProtectionSpace: space]; + RELEASE(cred); + RELEASE(space); + + method = [request objectForKey: GSHTTPPropertyMethodKey]; + if (method == nil) + { + if ([wData length] > 0) + { + method = @"POST"; + } + else + { + method = @"GET"; + } + } + a = [digest authorizationForAuthentication: ac + method: method + path: [url path]]; + if (a != nil) + { + [self writeProperty: a forKey: @"Authorization"]; + [self _tryLoadInBackground: u]; + return; // Retrying. + } + } + } + } + if (val != nil) + { + [pageInfo setObject: val forKey: NSHTTPPropertyStatusCodeKey]; + } val = [info objectForKey: NSHTTPPropertyServerHTTPVersionKey]; if (val != nil) { [pageInfo setObject: val forKey: NSHTTPPropertyServerHTTPVersionKey]; } - val = [info objectForKey: NSHTTPPropertyStatusCodeKey]; - if (val != nil) - { - [pageInfo setObject: val forKey: NSHTTPPropertyStatusCodeKey]; - } val = [info objectForKey: NSHTTPPropertyStatusReasonKey]; if (val != nil) { diff --git a/Source/GSURLPrivate.h b/Source/GSURLPrivate.h index 189e611ba..7ea6f7aac 100644 --- a/Source/GSURLPrivate.h +++ b/Source/GSURLPrivate.h @@ -84,6 +84,10 @@ */ + (GSHTTPDigest *) digestWithCredential: (NSURLCredential*)credential inProtectionSpace: (NSURLProtectionSpace*)space; +/* + * Look for a digest realm in a header + */ ++ (NSString*) digestRealmForAuthentication: (NSString*)authentication; /* * Generate next authorisation header for the specified authentication * header, method, and path.