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

#include "config.h"
#include "ncrc.h"
#include "ocinternal.h"
#include "ocdebug.h"
#include "occurlfunctions.h"

#define OC_MAX_REDIRECTS 20L

/* Mnemonic */
#define OPTARG void*

/* Define some .rc file entries of interest*/
#define NETRCFILETAG "HTTP.NETRC"

/* Check return value */
#define CHECK(state,flag,value) {if(check(state,flag,(void*)value) != OC_NOERR) {goto done;}}

static OCerror
check(OCstate* state, int flag, void* value)
{
    OCerror stat = ocset_curlopt(state,flag,value);
    return stat;
}

/*
Set a specific curl flag; primary wrapper for curl_easy_setopt
*/
OCerror
ocset_curlopt(OCstate* state, int flag, void* value)
{
    OCerror stat = OC_NOERR;
    CURLcode cstat = CURLE_OK;
    cstat = OCCURLERR(state,curl_easy_setopt(state->curl,flag,value));
    if(cstat != CURLE_OK)
	stat = OC_ECURL;
    return stat;
}

/*
Update a specific flag from state
*/
OCerror
ocset_curlflag(OCstate* state, int flag)
{
    OCerror stat = OC_NOERR;

    switch (flag) {

    case CURLOPT_USERPWD: /* Does both user and pwd */
        if(state->auth.creds.user != NULL && state->auth.creds.pwd != NULL) {
	    CHECK(state, CURLOPT_USERNAME, state->auth.creds.user);
	    CHECK(state, CURLOPT_PASSWORD, state->auth.creds.pwd);
            CHECK(state, CURLOPT_HTTPAUTH, (OPTARG)CURLAUTH_ANY);
	}
	break;

    case CURLOPT_COOKIEJAR: case CURLOPT_COOKIEFILE:
        if(state->auth.curlflags.cookiejar) {
	    /* Assume we will read and write cookies to same place */
	    CHECK(state, CURLOPT_COOKIEJAR, state->auth.curlflags.cookiejar);
	    CHECK(state, CURLOPT_COOKIEFILE, state->auth.curlflags.cookiejar);
        }
	break;

    case CURLOPT_NETRC: case CURLOPT_NETRC_FILE:
	if(state->auth.curlflags.netrc) {
	    CHECK(state, CURLOPT_NETRC, (OPTARG)CURL_NETRC_REQUIRED);
	    CHECK(state, CURLOPT_NETRC_FILE, state->auth.curlflags.netrc);
        }
	break;

    case CURLOPT_VERBOSE:
	if(state->auth.curlflags.verbose)
	    CHECK(state, CURLOPT_VERBOSE, (OPTARG)1L);
	break;

    case CURLOPT_TIMEOUT:
	if(state->auth.curlflags.timeout)
	    CHECK(state, CURLOPT_TIMEOUT, (OPTARG)((long)state->auth.curlflags.timeout));
	break;

    case CURLOPT_USERAGENT:
        if(state->auth.curlflags.useragent)
	    CHECK(state, CURLOPT_USERAGENT, state->auth.curlflags.useragent);
	break;

    case CURLOPT_FOLLOWLOCATION:
        CHECK(state, CURLOPT_FOLLOWLOCATION, (OPTARG)1L);
	break;

    case CURLOPT_MAXREDIRS:
	CHECK(state, CURLOPT_MAXREDIRS, (OPTARG)OC_MAX_REDIRECTS);
	break;

    case CURLOPT_ERRORBUFFER:
	CHECK(state, CURLOPT_ERRORBUFFER, state->error.curlerrorbuf);
	break;

    case CURLOPT_ENCODING:
#ifdef CURLOPT_ENCODING
	if(state->auth.curlflags.compress) {
	    CHECK(state, CURLOPT_ENCODING,"deflate, gzip");
        }
#endif
	break;

    case CURLOPT_PROXY:
	if(state->auth.proxy.host != NULL) {
	    CHECK(state, CURLOPT_PROXY, state->auth.proxy.host);
	    CHECK(state, CURLOPT_PROXYPORT, (OPTARG)(long)state->auth.proxy.port);
	    if(state->auth.proxy.user != NULL && state->auth.proxy.pwd != NULL) {
                CHECK(state, CURLOPT_PROXYUSERNAME, state->auth.proxy.user);
                CHECK(state, CURLOPT_PROXYPASSWORD, state->auth.proxy.pwd);
#ifdef CURLOPT_PROXYAUTH
	        CHECK(state, CURLOPT_PROXYAUTH, (long)CURLAUTH_ANY);
#endif
	    }
	}
	break;

    case CURLOPT_USE_SSL:
    case CURLOPT_SSLCERT: case CURLOPT_SSLKEY:
    case CURLOPT_SSL_VERIFYPEER: case CURLOPT_SSL_VERIFYHOST:
    {
        struct ssl* ssl = &state->auth.ssl;
        CHECK(state, CURLOPT_SSL_VERIFYPEER, (OPTARG)(ssl->verifypeer?1L:0L));
        CHECK(state, CURLOPT_SSL_VERIFYHOST, (OPTARG)(ssl->verifyhost?1L:0L));
        if(ssl->certificate)
            CHECK(state, CURLOPT_SSLCERT, ssl->certificate);
        if(ssl->key)
            CHECK(state, CURLOPT_SSLKEY, ssl->key);
        if(ssl->keypasswd)
            /* libcurl prior to 7.16.4 used 'CURLOPT_SSLKEYPASSWD' */
            CHECK(state, CURLOPT_KEYPASSWD, ssl->keypasswd);
        if(ssl->cainfo)
            CHECK(state, CURLOPT_CAINFO, ssl->cainfo);
        if(ssl->capath)
            CHECK(state, CURLOPT_CAPATH, ssl->capath);
    }
    break;

#ifdef HAVE_CURLOPT_BUFFERSIZE
    case CURLOPT_BUFFERSIZE:
	CHECK(state, CURLOPT_BUFFERSIZE, (OPTARG)state->curlbuffersize);
	break;
#endif

#ifdef HAVE_CURLOPT_KEEPALIVE
    case CURLOPT_TCP_KEEPALIVE:
	if(state->curlkeepalive.active != 0)
	    CHECK(state, CURLOPT_TCP_KEEPALIVE, (OPTARG)1L);
	if(state->curlkeepalive.idle > 0)
	    CHECK(state, CURLOPT_TCP_KEEPIDLE, (OPTARG)state->curlkeepalive.idle);
	if(state->curlkeepalive.interval > 0)
	    CHECK(state, CURLOPT_TCP_KEEPINTVL, (OPTARG)state->curlkeepalive.interval);
	break;
#endif

    default:
        nclog(NCLOGWARN,"Attempt to update unexpected curl flag: %d",flag);
	break;
    }
done:
    return stat;
}


/* Set various general curl flags per fetch  */
OCerror
ocset_flags_perfetch(OCstate* state)
{
    OCerror stat = OC_NOERR;
    /* currently none */
    return stat;
}

/* Set various general curl flags per link */

OCerror
ocset_flags_perlink(OCstate* state)
{
    OCerror stat = OC_NOERR;

    /* Following are always set */
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_ENCODING);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_NETRC);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_VERBOSE);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_TIMEOUT);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_USERAGENT);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_COOKIEJAR);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_USERPWD);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_PROXY);
    if(stat == OC_NOERR) stat = ocset_curlflag(state,CURLOPT_USE_SSL);
    if(stat == OC_NOERR) stat = ocset_curlflag(state, CURLOPT_FOLLOWLOCATION);
    if(stat == OC_NOERR) stat = ocset_curlflag(state, CURLOPT_MAXREDIRS);
    if(stat == OC_NOERR) stat = ocset_curlflag(state, CURLOPT_ERRORBUFFER);

#ifdef HAVE_CURLOPT_BUFFERSIZE
    /* Optional */
    if(stat == OC_NOERR && state->curlbuffersize > 0)
	stat = ocset_curlflag(state, CURLOPT_BUFFERSIZE);
#endif
#ifdef HAVE_CURLOPT_KEEPALIVE
    if(stat == NC_NOERR && state->curlkeepalive.active != 0)
        stat = ocset_curlflag(state, CURLOPT_TCP_KEEPALIVE);
#endif
    return stat;
}

void
oc_curl_debug(OCstate* state)
{
    state->auth.curlflags.verbose = 1;
    ocset_curlflag(state,CURLOPT_VERBOSE);
    ocset_curlflag(state,CURLOPT_ERRORBUFFER);
}

/* Misc. */

int
ocrc_netrc_required(OCstate* state)
{
    char* netrcfile = NC_rclookup(NETRCFILETAG,state->uri->uri);
    return (netrcfile != NULL || state->auth.curlflags.netrc != NULL ? 0 : 1);
}

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

/* See if http: protocol is supported */
void
oc_curl_protocols(OCstate* 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("http",*proto)==0)
	    state->auth.curlflags.proto_https=1;
    }
}
