/* Copyright 2009, UCAR/Unidata and OPeNDAP, Inc.
   See the COPYRIGHT file for more information. */

#include "config.h"
#include "ocinternal.h"
#include "ocdebug.h"
#include "ocrc.h"

/* Condition on libcurl version */
/* Set up an alias as needed */
#ifndef HAVE_CURLOPT_KEYPASSWD
#define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD
#endif


static char* combinecredentials(const char* user, const char* pwd);

void
oc_curl_debug(OCstate* state)
{
    curl_easy_setopt(state->curl,CURLOPT_VERBOSE,1);
    curl_easy_setopt(state->curl,CURLOPT_ERRORBUFFER,(void*)state->curlerror);
}

void
oc_curl_printerror(OCstate* state)
{
    fprintf(stderr,"curl error details: %s\n",state->curlerror);
}

/* Determine if this version of curl supports
       "file://..." &/or "https://..." urls.
*/
void
oc_curl_protocols(struct OCGLOBALSTATE* state)
{
    const char* const* proto; /*weird*/
    curl_version_info_data* curldata;
    curldata = curl_version_info(CURLVERSION_NOW);
    for(proto=curldata->protocols;*proto;proto++) {
        if(strcmp("file",*proto)==0) {state->curl.proto_file=1;break;}
        if(strcmp("http",*proto)==0) {state->curl.proto_https=1;break;}
    }
    if(ocdebug > 0) {
        oclog(OCLOGNOTE,"Curl file:// support = %d",state->curl.proto_file);
        oclog(OCLOGNOTE,"Curl https:// support = %d",state->curl.proto_https);
    }
}


/* Set various general curl flags */
int
ocset_curl_flags(OCstate* state)
{
    CURLcode cstat = CURLE_OK;
    CURL* curl = state->curl;
    struct OCcurlflags* flags = &state->curlflags;

#ifdef CURLOPT_ENCODING
    if (flags->compress) {
	cstat = curl_easy_setopt(curl, CURLOPT_ENCODING,"deflate, gzip");
	if(cstat != CURLE_OK) goto done;
	OCDBG(1,"CURLOPT_ENCODING=deflate, gzip");
    }
#endif
    if (flags->cookiejar || flags->cookiefile) {
	cstat = curl_easy_setopt(curl, CURLOPT_COOKIESESSION, 1);
	if (cstat != CURLE_OK) goto done;
	OCDBG(1,"CURLOPT_COOKIESESSION=1");
    }
    if (flags->cookiejar) {
	cstat = curl_easy_setopt(curl, CURLOPT_COOKIEJAR, flags->cookiejar);
	if (cstat != CURLE_OK) goto done;
	OCDBG1(1,"CURLOPT_COOKIEJAR=%s",flags->cookiejar);
    }
    if (flags->cookiefile) {
	cstat = curl_easy_setopt(curl, CURLOPT_COOKIEFILE, flags->cookiefile);
	if (cstat != CURLE_OK) goto done;
	OCDBG1(1,"CURLOPT_COOKIEFILE=%s",flags->cookiefile);
    }
    if (flags->verbose) {
	cstat = curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
	if (cstat != CURLE_OK) goto done;
	OCDBG1(1,"CURLOPT_VERBOSE=%ld",1L);
    }

    if (flags->timeout) {
	cstat = curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)flags->timeout);
	if (cstat != CURLE_OK) goto done;
	OCDBG1(1,"CURLOPT_TIMEOUT=%ld",1L);
    }

    if (flags->useragent) {
	cstat = curl_easy_setopt(curl, CURLOPT_USERAGENT, flags->useragent);
	if (cstat != CURLE_OK) goto done;
	OCDBG1(1,"CURLOPT_USERAGENT=%s",flags->useragent);
    }

    /* Following are always set */
    cstat = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    OCDBG1(1,"CURLOPT_FOLLOWLOCATION=%ld",1L);
    cstat = curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L);
    OCDBG1(1,"CURLOPT_MAXREDIRS=%ld",10L);

    cstat = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error.curlerrorbuf);
    OCDBG1(1,"CURLOPT_ERRORBUFFER",0);

done:
    return cstat;
}

int
ocset_proxy(OCstate* state)
{
    CURLcode cstat;
    CURL* curl = state->curl;
    struct OCproxy *proxy = &state->proxy;
    struct OCcredentials *creds = &state->creds;

    cstat = curl_easy_setopt(curl, CURLOPT_PROXY, proxy->host);
    if (cstat != CURLE_OK) return OC_ECURL;
    OCDBG1(1,"CURLOPT_PROXY=%s",proxy->host);

    cstat = curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy->port);
    if (cstat != CURLE_OK) return OC_ECURL;
    OCDBG1(1,"CURLOPT_PROXYPORT=%d",proxy->port);

    if (creds->username) {
        char *combined = combinecredentials(creds->username,creds->password);
        if (!combined) return OC_ENOMEM;
        cstat = curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, combined);
        if (cstat != CURLE_OK) return OC_ECURL;
	OCDBG1(1,"CURLOPT_PROXYUSERPWD=%s",combined);
#ifdef CURLOPT_PROXYAUTH
        cstat = curl_easy_setopt(curl, CURLOPT_PROXYAUTH, (long)CURLAUTH_ANY);
        if(cstat != CURLE_OK) goto fail;
	OCDBG1(1,"CURLOPT_PROXYAUTH=%ld",(long)CURLAUTH_ANY);
#endif
        free(combined);
    }
    return OC_NOERR;
}

int
ocset_ssl(OCstate* state)
{
    CURLcode cstat = CURLE_OK;
    CURL* curl = state->curl;
    struct OCSSL* ssl = &state->ssl;
    long verify = (ssl->validate?1L:0L);
    cstat=curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, verify);
    if (cstat != CURLE_OK) goto fail;
    OCDBG1(1,"CURLOPT_SSL_VERIFYPEER=%ld",verify);
    cstat=curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (verify?2L:0L));
    if (cstat != CURLE_OK) goto fail;
    OCDBG1(1,"CURLOPT_SSL_VERIFYHOST=%ld",(verify?2L:0L));
#ifdef OCIGNORE
    if(verify)
#endif
    {
        if(ssl->certificate) {
            cstat = curl_easy_setopt(curl, CURLOPT_SSLCERT, ssl->certificate);
            if(cstat != CURLE_OK) goto fail;
	    OCDBG1(1,"CURLOPT_SSLCERT=%s",ssl->certificate);
        }
        if(ssl->key) {
            cstat = curl_easy_setopt(curl, CURLOPT_SSLKEY, ssl->key);
            if(cstat != CURLE_OK) goto fail;
	    OCDBG1(1,"CURLOPT_SSLKEY=%s",ssl->key);
        }
        if(ssl->keypasswd) {
            /* libcurl prior to 7.16.4 used 'CURLOPT_SSLKEYPASSWD' */
            cstat = curl_easy_setopt(curl, CURLOPT_KEYPASSWD, ssl->keypasswd);
            if(cstat != CURLE_OK) goto fail;
	    OCDBG1(1,"CURLOPT_SSLKEY=%s",ssl->key);
        }
        if(ssl->cainfo) {
            cstat = curl_easy_setopt(curl, CURLOPT_CAINFO, ssl->cainfo);
            if(cstat != CURLE_OK) goto fail;
	    OCDBG1(1,"CURLOPT_CAINFO=%s",ssl->cainfo);
        }
        if(ssl->capath) {
            cstat = curl_easy_setopt(curl, CURLOPT_CAPATH, ssl->capath);
            if(cstat != CURLE_OK) goto fail;
	    OCDBG1(1,"CURLOPT_CAPATH=%s",ssl->capath);
        }
        {
            cstat = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, ssl->verifypeer);
            if(cstat != CURLE_OK) goto fail;
	    OCDBG1(1,"CURLOPT_SSL_VERIFYPEER=%d",ssl->verifypeer);
        }
    }    
    return OC_NOERR;

fail:
    return OC_ECURL;
}

/* This is called with arguments while the other functions in this file are
 * used with global values read from the.dodsrc file. The reason is that
 * we may have multiple password sources.
 */
int
ocset_user_password(OCstate* state)
{
    CURLcode cstat;
    CURL* curl = state->curl;
    char* combined = NULL;
    const char* userC = state->creds.username;
    const char* passwordC = state->creds.password;

    if(userC == NULL || passwordC == NULL) return OC_NOERR;

    combined = combinecredentials(userC,passwordC);
    if (!combined) return OC_ENOMEM;
    cstat = curl_easy_setopt(curl, CURLOPT_USERPWD, combined);
    if (cstat != CURLE_OK) goto done;
    OCDBG1(1,"CURLOPT_USERPWD=%s",combined);
    cstat = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
    if (cstat != CURLE_OK) goto done;
    OCDBG1(1,"CURLOPT_HTTPAUTH=%ld",(long)CURLAUTH_ANY);

done:
    if(combined != NULL) free(combined);
    return (cstat == CURLE_OK?OC_NOERR:OC_ECURL);
}


static char*
combinecredentials(const char* user, const char* pwd)
{
    int userPassSize = strlen(user) + strlen(pwd) + 2;
    char *userPassword = malloc(sizeof(char) * userPassSize);
    if (!userPassword) {
        oclog(OCLOGERR,"Out of Memory\n");
	return NULL;
    }
    strcpy(userPassword, user);
    strcat(userPassword, ":");
    strcat(userPassword, pwd);
    return userPassword;
}
