/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
#import "config.h"
#import "AuthenticationMac.h"

#import "AuthenticationCF.h"
#import "AuthenticationChallenge.h"
#import "AuthenticationClient.h"
#import "Credential.h"
#import "ProtectionSpace.h"

#import <Foundation/NSURLAuthenticationChallenge.h>
#import <Foundation/NSURLCredential.h>
#import <Foundation/NSURLProtectionSpace.h>

#if USE(CFNETWORK)

@interface NSURLProtectionSpace (Details)
- (CFURLProtectionSpaceRef) _cfurlprotectionspace;
- (id)_initWithCFURLProtectionSpace:(CFURLProtectionSpaceRef)cfProtSpace;
@end

@interface NSURLAuthenticationChallenge (Details)
+(NSURLAuthenticationChallenge *)_authenticationChallengeForCFAuthChallenge:(CFURLAuthChallengeRef)cfChallenge sender:(id <NSURLAuthenticationChallengeSender>)sender;
@end

@interface NSURLCredential (Details)
- (id) _initWithCFURLCredential:(CFURLCredentialRef)credential;
- (CFURLCredentialRef) _cfurlcredential;
@end

#endif

using namespace WebCore;

@interface WebCoreAuthenticationClientAsChallengeSender : NSObject <NSURLAuthenticationChallengeSender>
{
    AuthenticationClient* m_client;
#if USE(CFNETWORK)
    CFURLAuthChallengeRef m_cfChallenge;
#endif
}
- (id)initWithAuthenticationClient:(AuthenticationClient*)client;
- (AuthenticationClient*)client;
- (void)detachClient;
@end

@implementation WebCoreAuthenticationClientAsChallengeSender

- (id)initWithAuthenticationClient:(AuthenticationClient*)client
{
    self = [self init];
    if (!self)
        return nil;
    m_client = client;
    return self;
}

- (AuthenticationClient*)client
{
    return m_client;
}

- (void)detachClient
{
    m_client = 0;
}

- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (m_client)
        m_client->receivedCredential(core(challenge), core(credential));
}

- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (m_client)
        m_client->receivedRequestToContinueWithoutCredential(core(challenge));
}

- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (m_client)
        m_client->receivedCancellation(core(challenge));
}

#if USE(CFNETWORK)
- (void)setCFChallenge:(CFURLAuthChallengeRef)challenge
{
    m_cfChallenge = challenge;
}

- (CFURLAuthChallengeRef)cfChallenge
{
    return m_cfChallenge;
}
#endif

@end

namespace WebCore {

#if USE(CFNETWORK)

AuthenticationChallenge core(NSURLAuthenticationChallenge *macChallenge)
{
    WebCoreAuthenticationClientAsChallengeSender *challengeSender = (WebCoreAuthenticationClientAsChallengeSender*) [macChallenge sender];
    return AuthenticationChallenge([challengeSender cfChallenge], [challengeSender client]);
}

Credential core(NSURLCredential *macCredential)
{
    return core([macCredential _cfurlcredential]);
}

ProtectionSpace core(NSURLProtectionSpace *macSpace)
{
    return core([macSpace _cfurlprotectionspace]);
}

NSURLProtectionSpace *mac(const ProtectionSpace& coreSpace)
{
    RetainPtr<CFURLProtectionSpaceRef> protectionSpace(AdoptCF, createCF(coreSpace));
    return [[[NSURLProtectionSpace alloc] _initWithCFURLProtectionSpace:protectionSpace.get()] autorelease];
}

NSURLAuthenticationChallenge *mac(const AuthenticationChallenge& coreChallenge)
{
    AuthenticationClient* authClient = coreChallenge.authenticationClient();
    RetainPtr<WebCoreAuthenticationClientAsChallengeSender> challengeSender(AdoptNS, [[WebCoreAuthenticationClientAsChallengeSender alloc] initWithAuthenticationClient:authClient]);
    RetainPtr<CFURLAuthChallengeRef> authChallenge = coreChallenge.cfURLAuthChallengeRef();
    if (!authChallenge)
        authChallenge.adoptCF(createCF(coreChallenge));
    [challengeSender.get() setCFChallenge:authChallenge.get()];
    return [[NSURLAuthenticationChallenge _authenticationChallengeForCFAuthChallenge:authChallenge.get() sender:challengeSender.get()] autorelease];
}

NSURLCredential *mac(const Credential& coreCredential)
{
    RetainPtr<CFURLCredentialRef> credential(AdoptCF, createCF(coreCredential));
    return [[[NSURLCredential alloc] _initWithCFURLCredential:credential.get()] autorelease];
}

#else

#if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1050
// There is no constant in headers, but NTLM is supported.
NSString * const NSURLAuthenticationMethodNTLM = @"NSURLAuthenticationMethodNTLM";
#endif


AuthenticationChallenge::AuthenticationChallenge(const ProtectionSpace& protectionSpace,
                                                 const Credential& proposedCredential,
                                                 unsigned previousFailureCount,
                                                 const ResourceResponse& response,
                                                 const ResourceError& error)
    : AuthenticationChallengeBase(protectionSpace,
                                  proposedCredential,
                                  previousFailureCount,
                                  response,
                                  error)
{
}

AuthenticationChallenge::AuthenticationChallenge(NSURLAuthenticationChallenge *challenge)
    : AuthenticationChallengeBase(core([challenge protectionSpace]),
                                  core([challenge proposedCredential]),
                                  [challenge previousFailureCount],
                                  [challenge failureResponse],
                                  [challenge error])
    , m_sender([challenge sender])
    , m_nsChallenge(challenge)
{
}

void AuthenticationChallenge::setAuthenticationClient(AuthenticationClient* client)
{
    if (client) {
        m_sender.adoptNS([[WebCoreAuthenticationClientAsChallengeSender alloc] initWithAuthenticationClient:client]);
        m_nsChallenge.adoptNS([[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:m_nsChallenge.get() sender:m_sender.get()]);
    } else {
        if ([m_sender.get() isMemberOfClass:[WebCoreAuthenticationClientAsChallengeSender class]])
            [(WebCoreAuthenticationClientAsChallengeSender *)m_sender.get() detachClient];
    }
}

AuthenticationClient* AuthenticationChallenge::authenticationClient() const
{
    if ([m_sender.get() isMemberOfClass:[WebCoreAuthenticationClientAsChallengeSender class]])
        return [static_cast<WebCoreAuthenticationClientAsChallengeSender*>(m_sender.get()) client];
    
    return 0;
}

bool AuthenticationChallenge::platformCompare(const AuthenticationChallenge& a, const AuthenticationChallenge& b)
{
    if (a.sender() != b.sender())
        return false;
        
    if (a.nsURLAuthenticationChallenge() != b.nsURLAuthenticationChallenge())
        return false;

    return true;
}

NSURLAuthenticationChallenge *mac(const AuthenticationChallenge& coreChallenge)
{
    if (coreChallenge.nsURLAuthenticationChallenge())
        return coreChallenge.nsURLAuthenticationChallenge();
        
    return [[[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:mac(coreChallenge.protectionSpace())
                                                       proposedCredential:mac(coreChallenge.proposedCredential())
                                                     previousFailureCount:coreChallenge.previousFailureCount()
                                                          failureResponse:coreChallenge.failureResponse().nsURLResponse()
                                                                    error:coreChallenge.error()
                                                                   sender:coreChallenge.sender()] autorelease];
}

NSURLProtectionSpace *mac(const ProtectionSpace& coreSpace)
{
    NSString *proxyType = nil;
    NSString *protocol = nil;
    switch (coreSpace.serverType()) {
        case ProtectionSpaceServerHTTP:
            protocol = @"http";
            break;
        case ProtectionSpaceServerHTTPS:
            protocol = @"https";
            break;
        case ProtectionSpaceServerFTP:
            protocol = @"ftp";
            break;
        case ProtectionSpaceServerFTPS:
            protocol = @"ftps";
            break;
        case ProtectionSpaceProxyHTTP:
            proxyType = NSURLProtectionSpaceHTTPProxy;
            break;
        case ProtectionSpaceProxyHTTPS:
            proxyType = NSURLProtectionSpaceHTTPSProxy;
            break;
        case ProtectionSpaceProxyFTP:
            proxyType = NSURLProtectionSpaceFTPProxy;
            break;
        case ProtectionSpaceProxySOCKS:
            proxyType = NSURLProtectionSpaceSOCKSProxy;
            break;
        default:
            ASSERT_NOT_REACHED();
    }
  
    NSString *method = nil;
    switch (coreSpace.authenticationScheme()) {
        case ProtectionSpaceAuthenticationSchemeDefault:
            method = NSURLAuthenticationMethodDefault;
            break;
        case ProtectionSpaceAuthenticationSchemeHTTPBasic:
            method = NSURLAuthenticationMethodHTTPBasic;
            break;
        case ProtectionSpaceAuthenticationSchemeHTTPDigest:
            method = NSURLAuthenticationMethodHTTPDigest;
            break;
        case ProtectionSpaceAuthenticationSchemeHTMLForm:
            method = NSURLAuthenticationMethodHTMLForm;
            break;
        case ProtectionSpaceAuthenticationSchemeNTLM:
            method = NSURLAuthenticationMethodNTLM;
            break;
#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
        case ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested:
            method = NSURLAuthenticationMethodServerTrust;
            break;
        case ProtectionSpaceAuthenticationSchemeClientCertificateRequested:
            method = NSURLAuthenticationMethodClientCertificate;
            break;
#endif
        default:
            ASSERT_NOT_REACHED();
    }
    
    if (proxyType)
        return [[[NSURLProtectionSpace alloc] initWithProxyHost:coreSpace.host()
                                                           port:coreSpace.port()
                                                           type:proxyType
                                                          realm:coreSpace.realm()
                                           authenticationMethod:method] autorelease];
    return [[[NSURLProtectionSpace alloc] initWithHost:coreSpace.host()
                                                  port:coreSpace.port()
                                              protocol:protocol
                                                 realm:coreSpace.realm()
                                  authenticationMethod:method] autorelease];
}

NSURLCredential *mac(const Credential& coreCredential)
{
    if (coreCredential.isEmpty())
        return nil;

    NSURLCredentialPersistence persistence = NSURLCredentialPersistenceNone;
    switch (coreCredential.persistence()) {
        case CredentialPersistenceNone:
            break;
        case CredentialPersistenceForSession:
            persistence = NSURLCredentialPersistenceForSession;
            break;
        case CredentialPersistencePermanent:
            persistence = NSURLCredentialPersistencePermanent;
            break;
        default:
            ASSERT_NOT_REACHED();
    }

#if CERTIFICATE_CREDENTIALS_SUPPORTED
    if (coreCredential.type() == CredentialTypeClientCertificate) {
        return [[[NSURLCredential alloc] initWithIdentity:coreCredential.identity()
                                             certificates:(NSArray *)coreCredential.certificates()
                                              persistence:persistence]
                                              autorelease];
    }
#endif

    return [[[NSURLCredential alloc] initWithUser:coreCredential.user()
                                        password:coreCredential.password()
                                     persistence:persistence]
                                     autorelease];
}

AuthenticationChallenge core(NSURLAuthenticationChallenge *macChallenge)
{
    return AuthenticationChallenge(macChallenge);
}

ProtectionSpace core(NSURLProtectionSpace *macSpace)
{
    ProtectionSpaceServerType serverType = ProtectionSpaceProxyHTTP;
    
    if ([macSpace isProxy]) {
        NSString *proxyType = [macSpace proxyType];
        if ([proxyType isEqualToString:NSURLProtectionSpaceHTTPProxy])
            serverType = ProtectionSpaceProxyHTTP;
        else if ([proxyType isEqualToString:NSURLProtectionSpaceHTTPSProxy])
            serverType = ProtectionSpaceProxyHTTPS;
        else if ([proxyType isEqualToString:NSURLProtectionSpaceFTPProxy])
            serverType = ProtectionSpaceProxyFTP;
        else if ([proxyType isEqualToString:NSURLProtectionSpaceSOCKSProxy])
            serverType = ProtectionSpaceProxySOCKS;
        else 
            ASSERT_NOT_REACHED();
    } else {
        NSString *protocol = [macSpace protocol];
        if ([protocol caseInsensitiveCompare:@"http"] == NSOrderedSame)
            serverType = ProtectionSpaceServerHTTP;
        else if ([protocol caseInsensitiveCompare:@"https"] == NSOrderedSame)
            serverType = ProtectionSpaceServerHTTPS;
        else if ([protocol caseInsensitiveCompare:@"ftp"] == NSOrderedSame)
            serverType = ProtectionSpaceServerFTP;
        else if ([protocol caseInsensitiveCompare:@"ftps"] == NSOrderedSame)
            serverType = ProtectionSpaceServerFTPS;
        else
            ASSERT_NOT_REACHED();
    }

    ProtectionSpaceAuthenticationScheme scheme = ProtectionSpaceAuthenticationSchemeDefault;
    NSString *method = [macSpace authenticationMethod];
    if ([method isEqualToString:NSURLAuthenticationMethodDefault])
        scheme = ProtectionSpaceAuthenticationSchemeDefault;
    else if ([method isEqualToString:NSURLAuthenticationMethodHTTPBasic])
        scheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;
    else if ([method isEqualToString:NSURLAuthenticationMethodHTTPDigest])
        scheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;
    else if ([method isEqualToString:NSURLAuthenticationMethodHTMLForm])
        scheme = ProtectionSpaceAuthenticationSchemeHTMLForm;
    else if ([method isEqualToString:NSURLAuthenticationMethodNTLM])
        scheme = ProtectionSpaceAuthenticationSchemeNTLM;
#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
    else if ([method isEqualToString:NSURLAuthenticationMethodClientCertificate])
        scheme = ProtectionSpaceAuthenticationSchemeClientCertificateRequested;
    else if ([method isEqualToString:NSURLAuthenticationMethodServerTrust])
        scheme = ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested;
#endif
    else {
        scheme = ProtectionSpaceAuthenticationSchemeUnknown;
        ASSERT_NOT_REACHED();
    }
        
    return ProtectionSpace([macSpace host], [macSpace port], serverType, [macSpace realm], scheme);

}

Credential core(NSURLCredential *macCredential)
{
    CredentialPersistence persistence = CredentialPersistenceNone;
    switch ([macCredential persistence]) {
        case NSURLCredentialPersistenceNone:
            break;
        case NSURLCredentialPersistenceForSession:
            persistence = CredentialPersistenceForSession;
            break;
        case NSURLCredentialPersistencePermanent:
            persistence = CredentialPersistencePermanent;
            break;
        default:
            ASSERT_NOT_REACHED();
    }

#if CERTIFICATE_CREDENTIALS_SUPPORTED
    SecIdentityRef identity = [macCredential identity];
    if (identity)
        return Credential(identity, (CFArrayRef)[macCredential certificates], persistence);
#endif
    
    return Credential([macCredential user], [macCredential password], persistence);
}

#endif // USE(CFNETWORK)

} // namespace WebCore
