// Copyright 1997-1999 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OWAuthorizationServer.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>

#import "OWAddress.h"
#import "OWAuthorizationRealm.h"
#import "OWHeaderDictionary.h"
#import "OWNetLocation.h"
#import "OWURL.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Processors.subproj/Protocols.subproj/HTTP.subproj/OWAuthorizationServer.m,v 1.6 1999/03/30 21:26:41 kc Exp $")

@interface OWAuthorizationServer (Private)
+ (OWAuthorizationServer *)serverForHostAddress:(NSString *)aHostAddress type:(NSString *)authorizationType create:(BOOL)shouldCreate;
- initWithHostAddress:(NSString *)aHostAddress type:(NSString *)anAuthorizationType;
- (OWAuthorizationRealm *)realmForPath:(NSString *)path;
- (OWAuthorizationRealm *)realmNamed:(NSString *)name atPath:(NSString *)path;
- (NSString *)cleanPath:(NSString *)path;
- (void)generateCredentialsForHeader:(NSString *)authenticateHeader challenge:(OWHeaderDictionary *)challenge path:(NSString *)path reprompt:(BOOL)shouldReprompt;
@end

@implementation OWAuthorizationServer

static NSMutableDictionary *addressDictionary;
static NSLock *addressDictionaryLock;
static NSCharacterSet *whitespaceAndNewlineCharacterSet;
static NSCharacterSet *endRealmSet;

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    if (initialized)
	return;
    initialized = YES;

    addressDictionary = [[NSMutableDictionary alloc] init];
    addressDictionaryLock = [[NSLock alloc] init];
    whitespaceAndNewlineCharacterSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
    endRealmSet = [[NSCharacterSet characterSetWithCharactersInString:@"\""] retain];
}

+ (OWAuthorizationServer *)serverForAddress:(OWAddress *)address;
{
    return [self serverForHostAddress:[[[address url] netLocation] lowercaseString] type:@"www" create:NO];
}

+ (OWAuthorizationServer *)newServerForAddress:(OWAddress *)address;
{
    return [self serverForHostAddress:[[[address url] netLocation] lowercaseString] type:@"www" create:YES];
}

+ (OWAuthorizationServer *)serverForProxy:(NSString *)netLocation
{
    return [self serverForHostAddress:[netLocation lowercaseString] type:@"proxy" create:NO];
}

+ (OWAuthorizationServer *)newServerForProxy:(NSString *)netLocation
{
    return [self serverForHostAddress:[netLocation lowercaseString] type:@"proxy" create:YES];
}

// Init and dealloc

- (void)dealloc;
{
    [hostAddress release];
    [authorizationType release];
    [realmsByName release];
    [realmsByPath release];
    [super dealloc];
}

//

- (NSArray *)credentialsForPath:(NSString *)path;
{
    NSArray *credentials;

    credentials = [[self realmForPath:path] credentials];
    if (credentials && [credentials count] && ![authorizationType isEqualToString:@"www"]) {
        NSString *prefix;
        NSMutableArray *newCredentials;
        NSEnumerator *credentialEnumerator;
        NSString *credential;

        newCredentials = [NSMutableArray arrayWithCapacity:[credentials count]];
        prefix = [[authorizationType capitalizedString] stringByAppendingString:@"-"];
        credentialEnumerator = [credentials objectEnumerator];
        while ((credential = [credentialEnumerator nextObject])) {
            [newCredentials addObject:[prefix stringByAppendingString:credential]];
        }
        credentials = newCredentials;
    }

    return credentials;
}

- (void)generateCredentialsForChallenge:(OWHeaderDictionary *)challenge path:(NSString *)path reprompt:(BOOL)shouldReprompt;
{
    NSArray *authenticateArray;
    NSEnumerator *authenticateEnumerator;
    NSString *authenticateHeader;
    NSException *firstException = nil;
    BOOL generatedCredentials = NO;

    authenticateArray = [challenge stringArrayForKey:[authorizationType stringByAppendingString:@"-authenticate"]];

    if (!authenticateArray) {
        // Some old servers use www-authorization rather than www-authenticate.
        authenticateArray = [challenge stringArrayForKey:[authorizationType stringByAppendingString:@"-authorization"]];
    }

    authenticateEnumerator = [authenticateArray objectEnumerator];
    while ((authenticateHeader = [authenticateEnumerator nextObject])) {
	NS_DURING {
	    [self generateCredentialsForHeader:authenticateHeader challenge:challenge path:path reprompt:shouldReprompt];
	    generatedCredentials = YES;
	} NS_HANDLER {
	    if (!firstException)
		firstException = localException;
	} NS_ENDHANDLER;
	if (generatedCredentials)
	    return;
    }
    if (firstException)
	[firstException raise];
    else
#warning Should make the authentication failure string localizable
        [NSException raise:@"Not Authorized" format:@"Authentication is required to view this document"];
}

// Debugging

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];

    if (hostAddress)
        [debugDictionary setObject:hostAddress forKey:@"hostAddress"];
    if (authorizationType)
        [debugDictionary setObject:authorizationType forKey:@"authorizationType"];
    if (realmsByName)
        [debugDictionary setObject:realmsByName forKey:@"realmsByName"];
    if (realmsByPath)
        [debugDictionary setObject:realmsByPath forKey:@"realmsByPath"];

    return debugDictionary;
}

@end

@implementation OWAuthorizationServer (Private)

+ (OWAuthorizationServer *)serverForHostAddress:(NSString *)aHostAddress type:(NSString *)anAuthorizationType create:(BOOL)shouldCreate;
{
    OWAuthorizationServer *server;
    NSMutableString *key;

    key = [anAuthorizationType mutableCopy];
    [key appendString:@" "];
    [key appendString:aHostAddress];

    [addressDictionaryLock lock];
    server = [addressDictionary objectForKey:key];
    if (!server && shouldCreate) {
        server = [[self alloc] initWithHostAddress:aHostAddress type:anAuthorizationType];
        [addressDictionary setObject:server forKey:key];
        [server release];
    }
    [addressDictionaryLock unlock];

    [key release];
    return server;
}

- initWithHostAddress:(NSString *)aHostAddress type:(NSString *)anAuthorizationType;
{
    if (![super init])
	return nil;

    hostAddress = [aHostAddress retain];
    authorizationType = [anAuthorizationType retain];
    realmsByName = [[NSMutableDictionary alloc] initWithCapacity:0];
    realmsByPath = [[NSMutableDictionary alloc] initWithCapacity:0];

    return self;
}

- (OWAuthorizationRealm *)realmForPath:(NSString *)path;
{
    path = [self cleanPath:path];
    for (; ;) {
	OWAuthorizationRealm *realm;

	realm = [realmsByPath objectForKey:path];
	if (realm)
	    return realm;
	if ([path length] == 0)
	    return nil;
        path = [OWURL stringByDeletingLastPathComponentFromPath:path];
    }

    return nil;
}

- (OWAuthorizationRealm *)realmNamed:(NSString *)name atPath:(NSString *)path;
{
    OWAuthorizationRealm *realm;

    path = [self cleanPath:path];
    realm = [realmsByName objectForKey:name];
    if (!realm) {
	realm = [OWAuthorizationRealm realmWithName:name forHostAddress:hostAddress];
	[realmsByName setObject:realm forKey:name];
    }
    [realmsByPath setObject:realm forKey:path];
   
    return realm;
}

- (NSString *)cleanPath:(NSString *)path;
{
    if (!path)
	return @"";
    if ([path hasSuffix:@"/"])
	return [path substringToIndex:[path length]];
    return [OWURL stringByDeletingLastPathComponentFromPath:path];
}

- (void)generateCredentialsForHeader:(NSString *)authenticateHeader challenge:(OWHeaderDictionary *)challenge path:(NSString *)path reprompt:(BOOL)shouldReprompt;
{
    NSScanner *scanner;
    NSString *scheme, *realmName;
    OWAuthorizationRealm *realm;

    scanner = [NSScanner scannerWithString:authenticateHeader];
    if (![scanner scanUpToCharactersFromSet:whitespaceAndNewlineCharacterSet intoString:&scheme])
	scheme = nil;
    if (![scanner scanString:@"realm=\"" intoString:NULL] || ![scanner scanUpToCharactersFromSet:endRealmSet intoString:&realmName])
	realmName = hostAddress;
    realm = [self realmNamed:realmName atPath:path];
    [realm generateCredentialsForScheme:scheme challenge:challenge reprompt:shouldReprompt];
}

@end
