/* ====================================================================
 * FTD4Linux Software License, Version 1.0
 *
 * Copyright (c) 2000-2004 The FTD4Linux Development Team.  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.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        FTD4Linux Development Team (http://www.ftd4linux.2y.net/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "FTD" and "FTD4Linux" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact alguno@ftd4linux.2y.net.
 *
 * 5. Products derived from this software may not be called "FTD4Linux",
 *    nor may "FTD4Linux" appear in their name, without prior written
 *    permission of the FTD4Linux Development Team.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 THE FTD4LINUX DEVELOPMENT TEAM OR
 * ITS 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#ifdef __CEGCC__
#define _WIN32
#endif
#include <sys/stat.h>
#include <fcntl.h>
#ifdef __CEGCC__
#include <windows.h>
#include <sys/fcntl.h>
#define _open open
#else
#ifndef _MSC_VER
#include <unistd.h>
#endif
#endif /* WIN32 */
#include <glib.h>
#include <gmodule.h>
#include <curl.h>
#include <types.h> /* new for v7 */
#include <easy.h> /* new for v7 */
#include <time.h>

#include "ftd4linux.h"
#include "ftd_app_core.h"
#include "ftd_config.h"
#include "ftd_file.h"

static void (*ftd_mail_callback) (gpointer) = NULL;
static gpointer ftd_mail_data = NULL;
static time_t last_updated = 0;

void
ftd_http_curl_set_ftd_mail_callback(void (*cb) (gpointer), gpointer data)
{
    ftd_mail_callback = cb;
    ftd_mail_data = data;
}

/**
 * Een functie-type dat een md5 hash genereert voor authenticatie bij de FTD servers.
 * Deze functie wordt opgehaald uit de authenticatie-plugin.
 *
 * @arg     url         De url die bezocht gaat worden.
 * @arg     wachtwoord  Het wachtwoord waarmee ingelogd moet worden.
 *
 * @return  De md5 die gebruikt dient te worden voor authenticatie.
 */
typedef char *  (*FtdAuthFunc)     (char *url, FtdString *wachtwoord);

// Declaraties
int          ftd_curl_write_function(void *, size_t, size_t, void *);
int          ftd_curl_file_write_function(void *, size_t, size_t, void *);
FtdErrorCode ftd_http_curl_done(CURLcode, FtdString *, char **, gboolean);
FtdErrorCode ftd_http_curl_add_auth(char *, char **);

/**
 * Plaatst nmemb elementen van size lengte in een ::FtdString in stream.
 * Dit is een callback functie voor de curl bibliotheek.
 *
 * @arg     buffer          De data die in stream moet komen.
 * @arg     size            De lengte van een element uit buffer.
 * @arg     nmemb           Het aantal elementen in buffer.
 * @arg     stream          Een pointer naar waar de data opgeslagen moet worden. In FTD4linux wordt hier een ::FtdString voor gebruikt.
 *
 * @return  Het aantal bytes dat in stream geschreven is.
 */
int ftd_curl_write_function(void *buffer, size_t size, size_t nmemb, void *stream)
{
    char *buf;
    FtdString *data = (FtdString *) stream;
    g_assert(data);

    /* De buffer is NIET \0 terminated. Los dat op
       door een tijdelijke buffer te gebruiken */
    buf = (char *) g_malloc((int) (size * nmemb) + 1);
    memcpy(buf, buffer, size * nmemb);
    buf[size * nmemb] = '\0';
    data = ftd_string_append(data, buf);
    g_free(buf);

    return (int) (size * nmemb);
}

/**
 * Plaatst nmemb elementen van size lengte in een file, gegeven via stream.
 * Dit is een callback functie voor de curl bibliotheek.
 *
 * @arg     buffer          De data die in stream moet komen.
 * @arg     size            De lengte van een element uit buffer.
 * @arg     nmemb           Het aantal elementen in buffer.
 * @arg     stream          Een pointer naar waar de data opgeslagen moet worden. In FTD4linux onder WIN32 wordt hier mogelijk een file voor gebruikt.
 *
 * @return  Het aantal bytes dat in stream geschreven is.
 */
int ftd_curl_file_write_function(void *buffer, size_t size, size_t nmemb, void *stream)
{
    int *fp = (int *) stream;

    g_assert(fp);

    if (write(*fp, buffer, size * nmemb) < size * nmemb) {
        g_print("write(): error = %s (%i)", strerror(errno), errno);
    }

    return (int) (size * nmemb);
}

/**
 * Voert een aanroep naar een internet-adres uit.
 *
 * @arg     url         De url die opgevraagd moet worden.
 * @arg     parameters  Een lijst met ::FtdHttpParameter pointers naar alle parameters voor het opvragen van de url.
 * @arg     appendHost  True indien de host nog aan de url toegevoegd moet worden. Hiervoor worden de FTD servers gebruikt.
 * @arg     uitvoer     Hierin wordt de uitvoer van de server geplaatst. Een eventueel foutbericht wordt hier ook in geplaatst.
 * @arg     addAuth     Hiermee wordt aangegeven of de FTD auth gegevens in dit request verwerkt moeten worden of niet
 * @arg     filename    Indien niet-NULL zal dit een bestandsnaam bevatten waarin de gedownloade data opgeslagen staat.
 *                      Dit bestand dient na gebruik verwijderd te worden en filename dient vrijgegeven te worden met g_free
 *
 * @return  Een fout-code die aangeeft of het opvragen van de url succesvol was.
 */
FtdErrorCode
ftd_http_curl_execute(const char *url, FtdList *parameters, gboolean appendHost, FtdString **uitvoer, gboolean addAuth, char **filename, gboolean expect_data)
{
    FtdString       *args;
    gboolean         beenhere;
    FtdList         *list;
    CURL            *curl = NULL;
    CURLcode         curl_retval;
    FtdErrorCode     retval;
    FtdString       *urlTotal;
    FtdString       *serverOutput;
    char            *md5_hash = NULL;
    char            *proxyUserPw = NULL;
    char            *errmsg = NULL;
    int              fp;

    g_assert(core);
    g_assert(url);

    if (addAuth) {
        g_assert(core->config->username);
        g_assert(core->config->password);
    }

    curl = curl_easy_init();
    if(!curl) {
        *uitvoer = NULL;
        g_warning("ftd_http_curl_execute(): curl_easy_init() mislukt\n");
        return ERROR_CURL_INIT;
    }

    /* parameters */
    args = ftd_string_new("");
    beenhere = FALSE;
    list = parameters;
    while (list) {
        char                *urlencoded;
        FtdHttpParameter    *param;
        char                *buffer;
        int                  len;

        param = ftd_list_value(list);
        g_assert(param);

        urlencoded = curl_escape(ftd_string_value(param->paramValue), ftd_string_length(param->paramValue));

        len = ftd_string_length(param->paramName) + strlen(urlencoded) + 3;
        buffer = (char *) g_malloc(len + 1);
        if (beenhere == FALSE) {
            beenhere = TRUE;
            snprintf(buffer, len, "?%s=%s", ftd_string_value(param->paramName), urlencoded);
        } else {
            snprintf(buffer, len, "&%s=%s", ftd_string_value(param->paramName), urlencoded);
        }
        ftd_string_append(args, buffer);
        curl_free (urlencoded);
        g_free (buffer);
        list = ftd_list_next(list);
    }

    /* url */
    if (appendHost) {
        urlTotal = ftd_string_new("http://");
        if (strncmp(url, FTD_DUMPS_DIR, strlen(FTD_DUMPS_DIR)) == 0) {
            urlTotal = ftd_string_append(urlTotal, FTD_DUMPS_HOST);
            addAuth = FALSE;
        } else {
            urlTotal = ftd_string_append(urlTotal, FTD_URL_HOST);
        }
        urlTotal = ftd_string_append(urlTotal, url);
    } else {
        urlTotal = ftd_string_new(url);
    }
    urlTotal = ftd_string_append(urlTotal, ftd_string_value(args));
    ftd_string_free(args);

    if (addAuth) {
        FtdErrorCode retval;
        retval = ftd_http_curl_add_auth(ftd_string_value(urlTotal), &md5_hash);
        if (!md5_hash && retval != ERROR_UNAUTH) {
            /* MD5 hash kan niet aangemaakt worden..kap er maar mee */
            if (uitvoer) {
                *uitvoer = NULL;
            }
            curl_easy_cleanup(curl);
            ftd_string_free(urlTotal);
            return retval;
        }

        if (retval != ERROR_UNAUTH) {
            curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
            curl_easy_setopt(curl, CURLOPT_USERPWD, md5_hash);
        }
    }

    /* overige */
    serverOutput = ftd_string_new("");

    curl_easy_setopt(curl, CURLOPT_URL, ftd_string_value(urlTotal));
    if (filename) {
#ifdef __CEGCC__
        *filename = g_strdup("/My Documents/OpenFTD/tmpXXXXXX");
        fp = mkstemp(*filename);
#else
        fp = g_file_open_tmp(NULL, filename, NULL);

#endif
        if (fp == -1) {
            return ERROR_UNKNOWN;
        }

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ftd_curl_file_write_function);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fp);
    } else {
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ftd_curl_write_function);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, serverOutput);
    }
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, TRUE);
    curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
    if (ftd_svn_get_revision()) {
        char *user_agent = g_strdup_printf("OpenFTD 1.0 SVN Revision %s (%s)", ftd_svn_get_revision(), OS);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
        g_free(user_agent);
    } else {
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "OpenFTD 1.0 Beta 6 (" OS ")" );
    }
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 20);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, TRUE);
    //curl_easy_setopt(curl, CURLOPT_VERBOSE, TRUE);
    if (core->config->proxyHost && core->config->proxyPort) {
        curl_easy_setopt(curl, CURLOPT_PROXY, ftd_string_value(core->config->proxyHost));
        curl_easy_setopt(curl, CURLOPT_PROXYPORT, atoi(ftd_string_value(core->config->proxyPort)));

        if (core->config->proxyUser && core->config->proxyPassword) {
            int len = ftd_string_length(core->config->proxyUser) + ftd_string_length(core->config->proxyPassword) + 2;
            proxyUserPw = g_malloc (len);
            snprintf(proxyUserPw, len, "%s:%s", ftd_string_value(core->config->proxyUser), ftd_string_value(core->config->proxyPassword));
            curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxyUserPw);
        }
    }

    curl_retval = curl_easy_perform(curl);

    if (filename) {
        close(fp);
        if (curl_retval != CURLE_OK) {
            retval = ERROR_UNKNOWN;
        } else {
            retval = ERROR_NONE;
        }
    } else {
        retval = ftd_http_curl_done(curl_retval, serverOutput, &errmsg, expect_data);
    }

    if (errmsg) {
        ftd_string_free(serverOutput);
        serverOutput = ftd_string_new(errmsg);
        g_free(errmsg);
    }

    if (addAuth) {
        g_free(md5_hash);
    }
    if (proxyUserPw) {
        g_free(proxyUserPw);
    }
    curl_easy_cleanup(curl);
    ftd_string_free(urlTotal);

    if (uitvoer) {
        *uitvoer = serverOutput;
    }

    if (last_updated + 5 * 60 < time(NULL)) {
        last_updated = time(NULL);
        if (ftd_mail_callback) {
            ftd_mail_callback(ftd_mail_data);
        }
    }

    return retval;
}

/**
 * Rondt een verzoek aan de FTD servers af.
 * Bevat een groot deel van de foutafhandeling.
 *
 * @arg     retval          De foutcode zoals teruggegeven door de curl functies.
 * @arg     serverOutput    De uitvoer van de server.
 * @arg     errmsg          Een eventueel foutbericht wordt hierin geplaatst.
 *
 * @return  Een foutcode die in ::FtdErrorCode termen de betekenis van de respons aangeeft.
 */
FtdErrorCode
ftd_http_curl_done(CURLcode retval, FtdString *serverOutput, char **errmsg, gboolean expect_data)
{
    g_assert(core);

    switch(retval) {
        case CURLE_OK:
            if (ftd_string_length(serverOutput) == 0 && expect_data) {
                return ERROR_NODATA;
            }
            break;
        case CURLE_COULDNT_RESOLVE_HOST:
            return ERROR_DNS;
        case CURLE_COULDNT_RESOLVE_PROXY:
            return ERROR_DNS_PROXY;
        case CURLE_COULDNT_CONNECT:
            return ERROR_CONNECT;
        case CURLE_HTTP_RETURNED_ERROR:
            return ERROR_UNKNOWN;
        case CURLE_READ_ERROR:
        case CURLE_RECV_ERROR:
            return ERROR_READ;
        case CURLE_OPERATION_TIMEOUTED:
            return ERROR_TRANSFER_TIMEOUT;
        case CURLE_WRITE_ERROR:
        case CURLE_SEND_ERROR:
            return ERROR_SEND;
        case CURLE_UNSUPPORTED_PROTOCOL:
        case CURLE_FAILED_INIT:
        case CURLE_URL_MALFORMAT:
        case CURLE_URL_MALFORMAT_USER:
        default:
            return ERROR_UNKNOWN;
    }

    /* Kijk of we geldige uitvoer gehad hebben */
    if (strncmp(ftd_string_value(serverOutput), "UNAUTH", 6) == 0) {
        return ERROR_UNAUTH;
    }

    if (strncmp(ftd_string_value(serverOutput), "BAN", 3) == 0) {
        return ERROR_BAN;
    }

    if (strncmp(ftd_string_value(serverOutput), "Could not connect to the database", 33) == 0) {
        return ERROR_DB;
    }

    if (strncmp(ftd_string_value(serverOutput), "UPD", 3) == 0) {
        return ERROR_UPDATE;
    }

    if (strncmp(ftd_string_value(serverOutput), "MSG", 3) == 0) {
        *errmsg = g_strdup(ftd_string_value(serverOutput) + 3);
        return ERROR_MSG;
    }

    return ERROR_NONE;
}

/**
 * Maakt inlog-gegevens voor een HTTP basic authenticatie via curl.
 *
 * @arg     url         De url die opgevraagd gaat worden.
 * @arg     auth        Hierin wordt de md5hash geplaatst voor de authenticatie.
 *
 * @return  Een fout-code die aangaf of het aanmaken van de md5 hash succesvol was.
 */
FtdErrorCode
ftd_http_curl_add_auth(char *url, char **auth)
{
    char *md5;
    int len;
    void *handle;
    FtdAuthFunc authFunc;   //authenticatie functie
#ifdef BUILTIN_PLUGIN
    char *plugin_location;
#endif

    g_assert(core);
    g_assert(core->config);

#ifdef __CEGCC__
    // Voor WinCE wordt de http plugin volledig geintegreerd in de core
    #define authFunc ftd_plugin_auth
#else
    if (!g_module_supported()) {
        return ERROR_PLUGIN_UNSUPPORTED;
    }

    /* plugin laden */
#ifndef WIN32
#ifndef BUILTIN_PLUGIN
    if (!core->config->authPlugin) {
        return ERROR_PLUGIN_LOCATION;
    }
#endif
#endif

#ifdef BUILTIN_PLUGIN
    plugin_location = ftd_file_parse_lib_dir("/libftd_http_plugin.so.2.0");
    handle = g_module_open(plugin_location, G_MODULE_BIND_LAZY);
    g_free(plugin_location);
#else
    handle = g_module_open(ftd_string_value(core->config->authPlugin), G_MODULE_BIND_LAZY);
#endif

    if (!handle) {
        return ERROR_PLUGIN_LOAD;
    }

    /* pointer naar auth functie opslaan */
    g_module_symbol(handle, "ftd_plugin_auth", (gpointer) &authFunc);
    if (!authFunc) {
        g_module_close(handle);
        return ERROR_PLUGIN_EXECUTE;
    }

    if (!core->config->password) {
        g_module_close(handle);
        return ERROR_UNAUTH;
    }
#endif

    md5 = authFunc(url, core->config->password);

    /* Juiste MD5 hash gevonden, ga verder met het verzamelen van de headers */
    len = ftd_string_length(core->config->username) + strlen(md5) + 2;
    *auth = g_malloc(len);
    snprintf(*auth, len, "%s:%s", ftd_string_value(core->config->username), md5);
    g_free (md5);

#ifndef __CEGCC__
    g_module_close(handle);
#endif

    return ERROR_NONE;
}
