// Copyright 1997-2002 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 <OWF/OWCookie.h>

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

#import <OWF/NSDate-OWExtensions.h>
#import <OWF/OWAddress.h>
#import <OWF/OWCookieDomain.h>
#import <OWF/OWHeaderDictionary.h>
#import <OWF/OWHTTPProcessor.h>
#import <OWF/OWHTTPSession.h>
#import <OWF/OWNetLocation.h>
#import <OWF/OWURL.h>

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Processors.subproj/Protocols.subproj/HTTP.subproj/OWCookie.m,v 1.38 2002/01/05 00:56:26 bungi Exp $")

NSString *OWCookieGlobalPath = @"/";

@implementation OWCookie

// Init and dealloc

- initWithDomain:(NSString *)aDomain path:(NSString *)aPath name:(NSString *)aName value:(NSString *)aValue expirationDate:(NSDate *)aDate secure:(BOOL)isSecure;
{
    OBASSERT(aDomain != nil);
    if (!aDomain) {
        // Not sure how this got here, but let's not make anything worse.
        [self release];
        return nil;
    }

    _domain = [[aDomain lowercaseString] copy];
    _path = [aPath copy];
    _name = [aName copy];
    _value = [aValue copy];
    _expirationDate = [aDate retain];
    _secure = isSecure;

    // Cookies are good until someone requests confirmation (since mostly they get read from our cookies file)
    _confirmationState = YES;
    
    return self;
}

- initWithDomain: (NSString *) aDomain dictionary: (NSDictionary *) aDictionary;
{
    NSString *expirationString;
    NSDate *date;
    BOOL isSecure;
    
    if ((expirationString = [aDictionary objectForKey:@"expires"])) {
        NSTimeInterval timeInterval;

        timeInterval = [expirationString doubleValue];
        date = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:timeInterval];
    } else {
        // Cookies with no specified expiration were supposed to have expired before being saved.
        [self release];
        return nil;
    }

    isSecure = [[aDictionary objectForKey:@"secure"] isEqualToString:@"YES"];

    return [self initWithDomain: aDomain
                           path: [aDictionary objectForKey: @"path"]
                           name: [aDictionary objectForKey: @"name"]
                          value: [aDictionary objectForKey: @"value"]
                 expirationDate: date
                         secure: isSecure];
    
}

- (void)dealloc;
{
    [_domain release];
    [_path release];
    [_name release];
    [_value release];
    [_expirationDate release];
    [_confirmationLock release];
    [super dealloc];
}


// API

- (NSString *)domain;
{
    return _domain;
}

- (NSString *)path;
{
    return _path;
}

- (NSString *)name;
{
    return _name;
}

- (NSString *)value;
{
    return _value;
}

- (NSDate *)expirationDate;
{
    return _expirationDate;
}

- (BOOL)isExpired;
{
    // Cookies with no expiration date don't expire until the end of the
    // session.  That is, we should not write the cookie to disk.
    if (!_expirationDate)
        return NO;
        
    return [_expirationDate timeIntervalSinceNow] < 0.0;
}

- (BOOL) expiresAtEndOfSession;
{
    return _expirationDate == nil;
}

// This is the only mutability method on OWCookie and should only be called
// before the cookie is registered in a domain.
- (void) expireAtEndOfSession;
{
    [_expirationDate release];
    _expirationDate = nil;
}

- (BOOL)secure;
{
    return _secure;
}

- (BOOL)appliesToAddress:(id <OWAddress>)anAddress;
{
    if ([self isExpired])
        return NO;

    if ([anAddress isKindOfClass:[OWAddress class]]) {
        OWURL *url;
        OWNetLocation *netLocation;

        url = [(OWAddress *)anAddress url];
        netLocation = [url parsedNetLocation];
        if (netLocation != nil && [[netLocation hostname] hasSuffix: _domain]) {
            if ([self appliesToPath:[url fetchPath]])
                return YES;
        }
    }
    return NO;
}

- (BOOL)appliesToHostname:(NSString *)aHostname path:(NSString *)aPath;
{
    return [aHostname hasSuffix: _domain] && [self appliesToPath:aPath];
}

- (BOOL)appliesToPath:(NSString *)aPath;
{
    BOOL applies;
    
    applies = [aPath hasPrefix: _path];
    
    if (OWCookiesDebug)
        NSLog(@"COOKIES: Path %@ applies to path %@ --> %d", _path, aPath, applies);

    return applies;
}

// Support for asynchronous cookie prompts

// This is called when adding a cookie and the user preferences are set to prompt.
- (void) setRequiresConfirmation;
{
    OBPRECONDITION(!_confirmationLock);
    _confirmationLock = [[NSConditionLock alloc] initWithCondition: NO];
}

// Called asynchronously to either confirm or deny the addition of a cookie.
- (void) setConfirmationState: (BOOL) confirmationState;
{
    [_confirmationLock lock];
    _confirmationState = confirmationState;
    [_confirmationLock unlockWithCondition: YES];
}

// Should be before passing cookies to external systems.  This will cause the calling thread to block until the confirmation state of the cookie is known.  If this method returns NO, the cookie should be ignored.
- (BOOL) awaitConfirmation;
{
    BOOL state;

#warning TJW: This object might get deallocated if the calling thread does not retain it inside the add/remove lock in the domain/path    
    [_confirmationLock lockWhenCondition: YES];
    state = _confirmationState;
    [_confirmationLock unlock];
    return state;
}

//
// Saving
//

- (void) appendXML: (OFDataBuffer *) xmlBuffer;
{    
    OFDataBufferAppendCString(xmlBuffer, "  <cookie name=\"");
    OFDataBufferAppendXMLQuotedString(xmlBuffer, (CFStringRef)_name);
    
    // Only save the path if it is a non-global value (the most common case).
    if (_path && ![_path isEqualToString: OWCookieGlobalPath]) {
        OFDataBufferAppendCString(xmlBuffer, "\" path=\"");
        OFDataBufferAppendXMLQuotedString(xmlBuffer, (CFStringRef)_path);
    }
    
    if (_value) {
        OFDataBufferAppendCString(xmlBuffer, "\" value=\"");
        OFDataBufferAppendXMLQuotedString(xmlBuffer, (CFStringRef)_value);
    }
    if (_expirationDate) {
        char string[100];
        NSTimeInterval expirationInterval;
        
        expirationInterval = [_expirationDate timeIntervalSinceReferenceDate];
        sprintf(string, "%.0f", expirationInterval);
        OFDataBufferAppendCString(xmlBuffer, "\" expires=\"");
        OFDataBufferAppendCString(xmlBuffer, string);
    }
    if (_secure) {
        OFDataBufferAppendCString(xmlBuffer, "\" secure=\"YES");
    }
    
    OFDataBufferAppendCString(xmlBuffer, "\" />\n");
}

- (NSComparisonResult) compare: (id) otherObject;
{
    if (![otherObject isKindOfClass: isa])
        return NSOrderedAscending;
    
    return [_name compare: [(OWCookie *)otherObject name]];
}


//
// Debugging
//

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

    debugDictionary = [super debugDictionary];

    if (_domain)
	[debugDictionary setObject: _domain forKey:@"domain"];
    if (_path)
	[debugDictionary setObject: _path forKey:@"path"];
    if (_name)
	[debugDictionary setObject: _name forKey:@"name"];
    if (_value)
	[debugDictionary setObject: _value forKey: @"value"];
    if (_expirationDate)
        [debugDictionary setObject: _expirationDate forKey: @"expires"];
    [debugDictionary setObject: _secure ? @"YES" : @"NO" forKey: @"secure"];

    return debugDictionary;
}

@end

