/*
This source file is part of Scol
For the latest info, see http://www.scolring.org

Copyright (c) 2010 Stephane Bisaro, aka Iri <iri@irizone.net>

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt

For others informations, please contact us from http://www.scolring.org/
*/

#include "curl.h"




/**
 * \brief internal Scol function : implicit callback destroy object
 *
 * \param mmachine : mmachine structure
 * \param int : system handle
 * \param int : scol handle
 *
 * \return int : 0
 */
static int ObjCurlDestroy (mmachine m, int handsys, int mobj)
{
    CURL * c;

    MMechostr(MSKDEBUG, "ObjCurlDestroy: entering ...\n");

	/* return the object handle */
    c = (CURL *) MMfetch (m, MTOP (mobj), OBJCURL_HANDLE);
    if (c == NULL)
    {
        MMechostr(MSKDEBUG, "ObjCurlDestroy : object already destroyed\n");
        return 0;
    }
    /*curl_easy_cleanup (c);*/

	/* unregister this object to the Scol machine */
    MMstore (m, MTOP (mobj), OBJCURL_HANDLE, (int) NULL);
    MMechostr(MSKDEBUG, "ObjCurlDestroy: object has been destroyed\n");

    return 0;
}


/**
 * \brief _curlDsUrl : explicit destroy object
 *
 * \param ObjCurl : object
 *
 * \return I : 0
 *
 * In Scol, return 0 if success, otherwise \a nil
 */
static int SCOL_curlDsUrl (mmachine m)
{
    int obj;

    MMechostr (MSKDEBUG, "SCOL_curlDsUrl entering ...\n");

    /* Get obect and test it */
    obj = MTOP (MMget (m, 0));
    if (obj == NIL)
    {
        MMechostr (MSKDEBUG, "SCOL_curlDsUrl : object aleady destroyed\n");
        MMset (m, 0, NIL);
        return 0;
    }

    OBJdelTM (m, OBJCURL_HANDLE, PTOM (obj));
    MMset (m, 0, 0);

    return 0;
}




/**
 * \brief Free the FTPcommands elements
 *
 * Libère chaque élément de la liste chainée
 */
static void freeFtpCommands (struct FTPcommands *l)
{
    while (l->pNext != NULL)
    {
        l = l->pNext;
        freeFtpCommands (l);
        free (l);
    }
    return;
}


/**
 * \brief Internal function to close a libcurl instance and free StrCurl structure
 *
 * \param StrCurl* : StrCurl structure
 *
 * \return int : always 0
 */
static int ObjCurlFinished (struct StrCurl* p_st)
{
    MMechostr (MSKDEBUG, "ObjCurlFinished : entering ...\n");

    /* libcurl */
    if (p_st->header != NULL)
        curl_slist_free_all (p_st->header);
    if (p_st->preheader != NULL)
        curl_slist_free_all (p_st->preheader);
    curl_easy_cleanup (p_st->hcurl);
    curl_global_cleanup();

    /* structure */
    /*if (p_st->header != NULL)
    {
        free (p_st->header);
        p_st->header = NULL;
    }
    if (p_st->preheader != NULL)
    {
        free (p_st->preheader);
        p_st->preheader = NULL;
    }*/
    if (p_st->ftpcmds != NULL)
    {
        freeFtpCommands (p_st->ftpcmds);
    }
    if (p_st->url != NULL)
    {
        free (p_st->url);
        p_st->url = NULL;
    }
    if ((p_st->anyfile != NULL) && (p_st->mode == 2))
    {
        fclose (p_st->anyfile);
        p_st->anyfile = NULL;
    }

    return 0;
}










/**
 * \brief internal Callback : read (typically with PUT request)
 *
 * \param void* : local file to read
 * \param size_t : size of one block
 * \param size_t : number of blocks
 * \param void* : StrCurl strcture
 *
 * \return size_t : the number of bytes read, or -1 if error (no callback defined)
 */
static size_t scol_readNewUrlCB (void *ptr, size_t size, size_t nmemb, void * p_strEx)
{
    size_t retcode;
    mmachine m;
    CURL * hCurl;
    int cb;
    struct StrCurl * st;

    MMechostr (MSKDEBUG, "scol_readNewUrlCB : entering ...\n");

    st = (struct StrCurl *) p_strEx;
    m = (mmachine) st->m2m;
    retcode = fread (ptr, size, nmemb, st->anyfile);
    hCurl = (CURL *) st->hcurl;

    /* test and prepare the callback */
    cb = OBJbeginreflex (m, ObjCurl, (int) hCurl, CURL_CB_NEW);
    if (cb)
    {
        MMechostr (0, "scol_readNewUrlCB error : no callback defined ! errcode = %d\n", cb);
        return -1;
    }

    MMpush (m, NIL);
    MMpush (m, ITOM (retcode));
    Mpushstrbloc (m,  st->url);
    Mpushstrbloc (m,  st->cerr);
    /* Call the Scol callback function */
    OBJcallreflex (m, 4);
    return retcode;
}



/**
 * \brief internal Callback : write (transfert finished !)
 *
 * \param struct StrCurl* : StrCurl strcture
 *
 * \return void
 */
static void scol_newUrlCBend (struct StrCurl * st)
{
    mmachine m;
    CURL * hCurl;
    int cb;

    MMechostr (MSKDEBUG, "scol_newUrlCBend : entering ...\n");

    m = (mmachine) st->m2m;
    hCurl = (CURL *) st->hcurl;

    /* test and prepare the callback */
    cb = OBJbeginreflex (m, ObjCurl, (int) hCurl, CURL_CB_NEW);
    if (cb)
    {
        MMechostr (0, "scol_newUrlCBend error : no callback defined ! errcode = %d\n", cb);
        return;
    }

    /* add fourth supplementals parameters : datas (NIl because the transmission is
    	ended), the size of theses (NIL too), the url and the hypothetic libcurl
    	error) */
    MMpush (m, NIL);
    MMpush (m, NIL);
    Mpushstrbloc (m,  st->url);
    Mpushstrbloc (m,  st->cerr);

    OBJcallreflex (m, 4);
    return;
}


/**
 * \brief internal Callback : write (transfert in progress !)
 *
 * \param void* : received packet from the server
 * \param size_t : size of one block
 * \param size_t : number of blocks
 * \param void* : StrCurl strcture
 *
 * \return size_t : the number of bytes written, or -1 if error (no callback defined)
 */
static size_t scol_newUrlCB (void * ptr, size_t size, size_t nmemb, void * p_strEx)
{
    mmachine m;
    char * data = NULL;
    int cb = 0;
    size_t n;
    struct StrCurl * st;
    CURL * hCurl;

    MMechostr (MSKDEBUG, "scol_newUrlCB : entering ...\n");
    st = (struct StrCurl *) p_strEx;

    m = (mmachine) st->m2m;
    hCurl = (CURL *) st->hcurl;

    cb = OBJbeginreflex (m, ObjCurl, (int) hCurl, CURL_CB_NEW);
    if (cb)     /* no registred Scol callback with theses parameters */
    {
        MMechostr (0, "scol_newUrlCB error : no callback defined ! errcode = %d\n", cb);
        return -1;  /* return -1 : libcurl stopps the connexion */
    }

    n = (sizeof (char) * size * nmemb);
    data = (char *) malloc (n+1);
    if (data == NULL)
        return -1;
    strncpy (data, ptr, n);
    data[n] = '\0';

    /* supplementals parameters */
    Mpushstrbloc (m,  data);            /* datas */
    MMpush (m, ITOM (size*nmemb));      /* its size */
    Mpushstrbloc (m,  st->url);      /* url (rappel) */
    Mpushstrbloc (m,  st->cerr);     /* hypothetic error message */
    /* launch the Scol callback */
    OBJcallreflex (m, 4);
    if (data)
        free (data);

    return size*nmemb;
}




/**
 * \brief internal Perform a new HTTP connection
 *
 * \param mmachine :
 * \param int : origin : 0 : _curlNewUrl, 1 : _curlNewUrlAuth
 *
 * \return
 */
static int CreateNewCurlInstance (mmachine m, int origin)
{

/* ****************************************************************************
 *
 *  Internals variables
 *
 * ***************************************************************************/

    int mchannel, murl, mheader, mtauth = NIL, mmodeauth, mauth, mmode, mdatas, mup, mcb;
    int sizetab, curltab, len, mode = 0;
    int modeverifssl = 1;   /* verify by default */
    long modeauth = CURLAUTH_ANY;   /* value by default, curl chooses the best (secure) solution */
    char * auth;
    /* char* protocol; */
    struct StrCurl st;

/* ****************************************************************************
 *
 *  Scol initialisation : get stack values, verifications
 *
 * ***************************************************************************/

    MMechostr (MSKDEBUG, "CreateNewCurlInstance : entering ...\n");

    mup = MMpull (m);
    mcb = MMpull (m);
    mdatas = MTOP (MMpull (m));
    mmode = MTOI (MMpull (m));
    if (origin == 1)
        mtauth = MTOP (MMpull (m));
    mheader = MTOP (MMpull (m));
    murl = MTOP (MMpull (m));
    mchannel = MMget (m, 0);

    if (mchannel == NIL)
    {
        MMechostr (0, "CreateNewCurlInstance error : channel is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }

    if (murl == NIL)
    {
        MMechostr (0, "CreateNewCurlInstance error : url is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }

    if (mmode >= 10)
    {
        modeverifssl = 0;
        mmode = mmode-10;
    }

    if ((mmode != NIL) && (mmode >= 0) && (mmode <= 2))
        mode = mmode;

    if (origin == 1)
    {
        if (mtauth == NIL)
        {
            MMechostr (0, "CreateNewCurlInstance error : authentification is nil\n");
            MMpull (m);
            MMpush (m, NIL);
            return 0;
        }

        /* Chaine contenant le login et le password */
        mauth = MTOP (MMfetch (m, mtauth, 0));
        len = sizeof (char) * (MMsizestr (m, mauth));
        auth = (char *) malloc (len+1);
        strncpy (auth, MMstartstr (m, mauth), len);
        auth[len] = '\0';

        /* Mode d'authentification : basic (1), digest (2), any (0) */
        mmodeauth = MTOI (MMfetch (m, mtauth, 1));
        if (mmodeauth == 1)
            modeauth = CURLAUTH_BASIC;
        else if (mmodeauth == 2)
            modeauth = CURLAUTH_DIGEST;
        /* else, keep CURLAUTH_ANY */
    }

/* ****************************************************************************
 *
 *  Curl initialisation
 *
 * ***************************************************************************/

    /* global libcurl initialisation */
    if (curl_global_init (CURL_GLOBAL_ALL) != CURLE_OK)
    { /* init falied */
        MMechostr (0, "CreateNewCurlInstance error : global init failed\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }

    /* libcurl session init */
    st.hcurl = curl_easy_init ();





    if (st.hcurl)
    {
        memset (st.cerr, '\0', CURL_ERROR_SIZE);

/* ****************************************************************************
 *
 *  Create the Scol object (ObjCurl)
 *
 * ***************************************************************************/

        sizetab = sizeof (st.hcurl) + 1;
        curltab = MMmalloc (m, sizetab, TYPETAB);

        if ((curltab == NIL))
        {
            MMechostr (0, "CreateNewCurlInstance error : insufficient memory\n");
            MMpull (m);
            MMpush (m, NIL);
            return ObjCurlFinished (&st);
        }

        MMstore (m, curltab, OBJCURL_HANDLE, (int) st.hcurl);
        MMpush (m, PTOM (curltab));
        OBJcreate (m, ObjCurl, (int) st.hcurl, -1, -1);
        /* now, stage 0 : the new Scol object */

/* ****************************************************************************
 *
 *  Define the Scol reflex
 *
 * ***************************************************************************/

        MMpush (m, mcb);
        MMpush (m, mup);

        st.m2m = m;
        st.mode = mode;

        OBJaddreflex (m, ObjCurl, CURL_CB_NEW);

/* ****************************************************************************
 *
 *  Define all curl parameters
 *
 * ***************************************************************************/

        /* Url ... */
        len = sizeof (char) * (MMsizestr (m, murl));
        st.url = (char *) malloc (len+1);
        strncpy (st.url, MMstartstr (m, murl), len);
        st.url[len] = '\0';

        if (!(strncasecmp (st.url, "file", 4)))
        {
            int basescol_lenmax = 128;
            char * basescol;
            basescol = (char *) malloc (sizeof (char) * basescol_lenmax);
            lib_sys_getPathPartition (&basescol, basescol_lenmax);

            if (strncasecmp (st.url+7, basescol, strlen (basescol)))
            {
                MMechostr (0, "CreateNewCurlInstance error : sorry, this file is unauthorized\n");
                free (basescol);
                MMpull (m);
                MMpush (m, NIL);
                return ObjCurlFinished (&st);
            }
            free (basescol);
        }

        /*protocol = (char*) malloc (sizeof (char) * 6);
        protocol[6] = '\0';
        memcpy (protocol, st.url, 5);*/

        /* headers list ... */
        if (mheader == NIL)
            st.header = NULL;
        else
        {
            char * h;
            int mh;
            while (mheader != NIL)
            {
                mh = MTOP (MMfetch (m, mheader, 0));
                if (mh != NIL)
                {
                    len = sizeof (char) * (MMsizestr (m, mheader));
                    h = (char *) malloc (len+1);
                    strncpy (h, MMstartstr (m, mh), len);
                    h[len] = '\0';

                    curl_slist_append (st.header, h);
                    free (h);
                }
                mheader = MTOI (MMfetch (m, mheader, 1));
            }
        }

/* ****************************************************************************
 *
 *  Set all curl parameters of the connection and perform it
 *
 * ***************************************************************************/

        curl_easy_setopt (st.hcurl, CURLOPT_URL, st.url);
        curl_easy_setopt (st.hcurl, CURLOPT_ERRORBUFFER, st.cerr);
        curl_easy_setopt (st.hcurl, CURLOPT_USERAGENT, "Scol_libcurl-agent/1.0");

        /* With authentification only */
        if (origin == 1)
        {
            curl_easy_setopt (st.hcurl, CURLOPT_HTTPAUTH, modeauth);
            curl_easy_setopt (st.hcurl, CURLOPT_USERPWD, auth);
        }

        /* headers */
        curl_easy_setopt (st.hcurl, CURLOPT_HTTPHEADER, st.header);

        /* https:// and don't verify SSL parameters (less secure, of course !) */
        if ((!(strncasecmp (st.url, "https", 5))) && (!modeverifssl))
        {
            curl_easy_setopt (st.hcurl, CURLOPT_SSL_VERIFYPEER, 0L);
            curl_easy_setopt (st.hcurl, CURLOPT_SSL_VERIFYHOST, 0L);
        }

        /* request is GET, POST or PUT ... */
        switch (st.mode)
        {
            /* POST request */
            case (1):
            {
                char * datas;

                len = sizeof (char) * (MMsizestr (m, mdatas));
                datas = (char *) malloc (len+1);
                strncpy (datas, MMstartstr (m, mdatas), len);
                datas[len+1] = '\0';

                curl_easy_setopt (st.hcurl, CURLOPT_POSTFIELDS, datas);
                curl_easy_setopt (st.hcurl, CURLOPT_WRITEDATA, &st);
                curl_easy_setopt (st.hcurl, CURLOPT_WRITEFUNCTION, scol_newUrlCB);
                free (datas);
                break;
            }

            /* PUT request */
            case (2):
            {
                int localfile;
                char * datas;
                struct stat file_info;

                len = sizeof (char) * (MMsizestr (m, mdatas));
                datas = (char *) malloc (len+1);
                strncpy (datas, MMstartstr (m, mdatas), len);
                datas[len] = '\0';

                /* get its size */
                localfile = open (datas, O_RDONLY);
                fstat (localfile, &file_info);
                close (localfile);

                st.anyfile = fopen (datas, "rb");
                if (st.anyfile == NULL)
                {
                    MMechostr (0, "CreateNewCurlInstance error with the request : PUT : file cannot open\n");
                    MMpush (m, NIL);
                    return ObjCurlFinished (&st);
                }

                curl_easy_setopt(st.hcurl, CURLOPT_READFUNCTION, scol_readNewUrlCB);
                curl_easy_setopt(st.hcurl, CURLOPT_UPLOAD, 1L);
                curl_easy_setopt(st.hcurl, CURLOPT_PUT, 1L);
                curl_easy_setopt(st.hcurl, CURLOPT_READDATA, &st);
                curl_easy_setopt(st.hcurl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) file_info.st_size);
                break;
            }

            /* GET request */
            default:
            {
                curl_easy_setopt (st.hcurl, CURLOPT_WRITEDATA, &st);
                curl_easy_setopt (st.hcurl, CURLOPT_WRITEFUNCTION, scol_newUrlCB);
            }
        }


        /* Run ... */
        st.result = curl_easy_perform (st.hcurl);
        /* Finished : call the last callback */
        scol_newUrlCBend (&st);

        /* End ... */
        return ObjCurlFinished (&st);
    }

    MMechostr (0, "CreateNewCurlInstance error : easy init failed\n");
    curl_global_cleanup();

    MMpull (m);
    MMpush (m, NIL);

    return 0;
}








/**
 * \brief internal Perform a new FTP connection
 *
 * \param mmachine :
 * \param int : origin : 0 : _curlNewFtp, 1 : _curlNewFtpGet, 2 : _curlNewFtpPut
 * \param int : is_ssh : 0 : nothing, 1 : SFTP
 *
 * \return
 */
static int CreateNewFTPInstance (mmachine m, int origin, int is_ssh)
{

/* ****************************************************************************
 *
 *  Internals variables
 *
 * ***************************************************************************/

    int mchannel, murl, mlogin, mpassword, mcommands = NIL, mcb, mup, mfilename = NIL;
    int mcommand, len;
    int sizetab, curltab;
    char *command, *password = NULL, *login = NULL, *filename, *customprefix;
    curl_off_t fsize = 0;
    struct StrCurl st;

/* ****************************************************************************
 *
 *  Initialisations : get stack entries, structure StrCurl, ...
 *
 * ***************************************************************************/

    MMechostr (MSKDEBUG, "CreateNewFTPInstance : entering ...\n");

    mup = MMpull (m);
    mcb = MMpull (m);

    mpassword = MTOP (MMpull (m));
    mlogin = MTOP (MMpull (m));
    if (origin != 1)    /* ! GET */
        mcommands = MTOP (MMpull (m));
    if (origin != 0)    /* GET or PUT */
        mfilename = MTOP (MMpull (m));
    murl = MTOP (MMpull (m));
    mchannel = MMget (m, 0);

    if (mchannel == NIL)
    {
        MMechostr (0, "CreateNewFTPInstance error : channel is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }

    if (murl == NIL)
    {
        MMechostr (0, "CreateNewFTPInstance error : url is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }

    /* password */
    if (mpassword != NIL)
    {
        len = sizeof (char) * MMsizestr (m, mpassword);
        password = (char *) malloc (len + 1);
        strncpy (password, MMstartstr (m, mpassword), len);
        password[len] = '\0';
    }

    /* username */
    if (mlogin != NIL)
    {
        len = sizeof (char) * MMsizestr (m, mlogin);
        login = (char *) malloc (len + 1);
        strncpy (login, MMstartstr (m, mlogin), len);
        login[len] = '\0';
    }

    /* url (server) */
    len = sizeof (char) * MMsizestr (m, murl);
    st.url = (char *) malloc (len + 1);
    strncpy (st.url, MMstartstr (m, murl), len);
    st.url [len] = '\0';
    if (is_ssh == 0)
        customprefix = "ftp://";
    else if (is_ssh == 1)
        customprefix = "sftp";
    else
        customprefix = "";
    lib_string_addPrefix (&st.url, customprefix);   /* add "ftp://" if needed */
    lib_string_addSuffixChar (&st.url, '/'); /* add a last slash if needed */

    /* filename (GET or PUT) */
    if (origin != 0)
    {
        if (mfilename == NIL)
        {
            MMechostr (0, "CreateNewFTPInstance error : filename is nil\n");
            MMpull (m);
            MMpush (m, NIL);
            return 0;
        }
        len = sizeof (char) * MMsizestr (m, mfilename);
        filename = (char *) malloc (len + 1);
        strncpy (filename, MMstartstr (m, mfilename), len);
        filename[len] = '\0';
        if (origin == 2)    /* PUT */
        {
            int basescol_lenmax = 256;
            char * basescol;
            struct stat file_info;

            basescol = (char *) malloc (sizeof (char) * basescol_lenmax);
            if (basescol == NULL)
            {
                MMechostr (0, "CreateNewFTPInstance error : this file is out of your Scol partition\n");
                MMpull (m);
                MMpush (m, NIL);
                return 0;
            }
            strncpy (basescol, filename, basescol_lenmax);
            basescol[basescol_lenmax]='\0';
            lib_sys_getPathPartition (&basescol, basescol_lenmax);
            strncat (basescol, filename, basescol_lenmax);

            st.anyfile = fopen (basescol, "rb");
            if (st.anyfile == NULL)
            {
                MMechostr (0, "CreateNewFTPInstance error : doesn't open the filename %s\n", filename);
                MMpull (m);
                MMpush (m, NIL);
                return 0;
            }

            /* update url */
            len = sizeof (char) * (strlen (st.url) + strlen (basename (filename)));
            st.url = (char *) realloc (st.url, len + 1);
            strncat (st.url, basename (filename), len);
            st.url[len] = '\0';

            stat (basescol, &file_info);
            fsize = (curl_off_t) file_info.st_size;
        }

        /* GET */
        else if (origin == 1)
        {
            len = sizeof (char) * (strlen (st.url) + strlen (filename));
            st.url = (char *) realloc (st.url, len + 1);
            strncat (st.url, filename, len);
            st.url[len] = '\0';

        }
    }

    /* headers / commands */
    if (origin != 1)
    {
        int mtc, mtflag;

        st.header = NULL;
        st.ftpcmds = (struct FTPcommands*) malloc (sizeof (struct FTPcommands));;

        while (mcommands != NIL)
        {
            mcommand = MTOP (MMfetch (m, mcommands, 0));
            if (mcommand != NIL)
            {
                mtc = MTOP (MMfetch (m, mcommand, 0));
                mtflag = MTOI (MMfetch (m, mcommand, 1));

                len = sizeof (char) * (MMsizestr (m, mtc));
                command = (char *) malloc (len+1);
                strncpy (command, MMstartstr (m, mtc), len);
                command[len] = '\0';

                if (origin == 0)
                {
                    struct FTPcommands *c;

                    c = (struct FTPcommands*) malloc (sizeof (struct FTPcommands));
                    if (c == NULL)
                    {
                        MMechostr (0, "CreateNewFTPInstance error : memory is not enough\n");
                        free (command);
                        MMpush (m, NIL);
                        return 0;
                    }
                    strncpy (c->command, command, 64);
                    c->command[63] = '\0';
                    c->flag = mtflag;
                    c->pNext = NULL;
                    st.ftpcmds->pNext = c;
                }
                else if (origin == 2)
                {
                    if (mtflag)
                        st.header = curl_slist_append (st.header, command);
                    else
                        st.preheader = curl_slist_append (st.preheader, command);
                }
                free (command);
            }
            mcommands = MTOP (MMfetch (m, mcommands, 1));
        }
    }

/* ****************************************************************************
 *
 *  Initialisation : libcurl
 *
 * ***************************************************************************/

    /* global libcurl initialisation */
    if (curl_global_init (CURL_GLOBAL_ALL) != CURLE_OK)
    { /* init falied */
        MMechostr (0, "CreateNewFTPInstance error : global init failed\n");
        MMpull (m);
        MMpush (m, NIL);
        return ObjCurlFinished (&st);
    }

    /* libcurl session init */
    st.hcurl = curl_easy_init ();

    if (st.hcurl)
    {
        memset (st.cerr, '\0', CURL_ERROR_SIZE);

/* ****************************************************************************
 *
 *  Create the Scol object (ObjCurl)
 *
 * ***************************************************************************/

        sizetab = sizeof (st.hcurl) + 1;
        curltab = MMmalloc (m, sizetab, TYPETAB);

        if ((curltab == NIL))
        {
            MMechostr (0, "CreateNewFTPInstance error : insufficient memory\n");
            MMpull (m);
            MMpush (m, NIL);
            return ObjCurlFinished (&st);
        }

        MMstore (m, curltab, OBJCURL_HANDLE, (int) st.hcurl);
        MMpush (m, PTOM (curltab));
        OBJcreate (m, ObjCurl, (int) st.hcurl, -1, -1);

/* ****************************************************************************
 *
 *  Define the Scol reflex
 *
 * ***************************************************************************/

        MMpush (m, mcb);
        MMpush (m, mup);

        st.m2m = m;
        st.mode = 0;

        OBJaddreflex (m, ObjCurl, CURL_CB_NEW);

/* ****************************************************************************
 *
 *  Set libcurl parameters and perform the demand
 *
 * ***************************************************************************/

        curl_easy_setopt (st.hcurl, CURLOPT_URL, st.url);
        /*printf ("----> url = %s\n", st.url);*/

        /* not anonymous */
        if (login != NULL)
        {
            curl_easy_setopt (st.hcurl, CURLOPT_USERNAME, login);
            free (login);
        }
        if (password != NULL)
        {
            curl_easy_setopt (st.hcurl, CURLOPT_PASSWORD, password);
            free (password);
        }

        /* curl_easy_setopt (st.hcurl, CURLOPT_FTP_USE_PRET, 1L); */ /* libcurl >= 7.20 */
        curl_easy_setopt (st.hcurl, CURLOPT_ERRORBUFFER, st.cerr);
        curl_easy_setopt (st.hcurl, CURLOPT_USERAGENT, "Scol_libcurl-agent/1.0");
        curl_easy_setopt (st.hcurl, CURLOPT_VERBOSE, 1L);

        curl_easy_setopt (st.hcurl, CURLOPT_WRITEDATA, &st);
        curl_easy_setopt (st.hcurl, CURLOPT_WRITEFUNCTION, scol_newUrlCB);

        /* PUT */
        if (origin == 2)
        {
            curl_easy_setopt (st.hcurl, CURLOPT_READFUNCTION, scol_readNewUrlCB);
            curl_easy_setopt (st.hcurl, CURLOPT_UPLOAD, 1L);
            curl_easy_setopt (st.hcurl, CURLOPT_READDATA, &st);
            curl_easy_setopt (st.hcurl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) fsize);
        }

        /* generic */
        if (origin == 0)
        {
            struct FTPcommands *lc;
            lc = st.ftpcmds;

            while (lc != NULL)
            {
                curl_easy_setopt (st.hcurl, CURLOPT_CUSTOMREQUEST, lc->command);
                lc = lc->pNext;
            }
        }
        else
        {
            /*curl_easy_setopt (st.hcurl, CURLOPT_HEADER, 1);*/
            curl_easy_setopt (st.hcurl, CURLOPT_PREQUOTE, st.preheader);
            curl_easy_setopt (st.hcurl, CURLOPT_POSTQUOTE, st.header);
        }

        /* SSH - SFTP */
        if (is_ssh == 1)
        {
            curl_easy_setopt (st.hcurl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PASSWORD);
            curl_easy_setopt (st.hcurl, CURLOPT_PORT, 22);
        }

        st.result = curl_easy_perform (st.hcurl);
        /* curl_easy_setopt (st.hcurl, CURLOPT_POSTQUOTE, NULL);*/
        scol_newUrlCBend (&st);

        return ObjCurlFinished (&st);
    }

    MMechostr (0, "CreateNewFTPInstance error : easy init failed\n");
    curl_global_cleanup();

    MMpull (m);
    MMpush (m, NIL);

    return 0;
}




/**
 * \brief internal Send an email (connection SMTP)
 *
 * \param mmachine
 * \return int : 0
 *
 * Scol prototype : fun [Chn S S [S r1] S
 */
static int CreateNewSMTPInstance (mmachine m)
{

/* ****************************************************************************
 *
 *  Internals variables
 *
 * ***************************************************************************/

    int mchannel, msmtpsrv, mfrom, mtos, mbody, mup, mcb;
    int mto, len, sizetab, curltab;
    char *from, *body, *to;
    struct StrCurl st;

/* ****************************************************************************
 *
 *  Initialisations : get stack entries, structure StrCurl, ...
 *
 * ***************************************************************************/

    MMechostr (MSKDEBUG, "CreateNewSMTPInstance : entering ...\n");

    mup = MMpull (m);
    mcb = MMpull (m);

    mbody = MTOP (MMpull (m));
    mtos = MTOP (MMpull (m));
    mfrom = MTOP (MMpull (m));
    msmtpsrv = MTOP (MMpull (m));
    mchannel = MMget (m, 0);

    if (mchannel == NIL)
    {
        MMechostr (0, "CreateNewSMTPInstance error : channel is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    if (msmtpsrv == NIL)
    {
        MMechostr (0, "CreateNewSMTPInstance error : server is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    if (mtos == NIL)
    {
        MMechostr (0, "CreateNewSMTPInstance error : dest to is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    if (mfrom == NIL)
    {
        MMechostr (0, "CreateNewSMTPInstance error : src from is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }

    len = sizeof (char) * MMsizestr (m, msmtpsrv);
    st.url = (char *) malloc (len + 1);
    if (st.url == NULL)
    {
        MMechostr (0, "CreateNewSMTPInstance error : memory failed\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    strncpy (st.url, MMstartstr (m, msmtpsrv), len);
    st.url[len] = '\0';

    len = sizeof (char) * MMsizestr (m, mfrom);
    from = (char *) malloc (len + 1);
    if (from == NULL)
    {
        MMechostr (0, "CreateNewSMTPInstance error : memory failed\n");
        MMpull (m);
        MMpush (m, NIL);
        return ObjCurlFinished (&st);
    }
    strncpy (from, MMstartstr (m, mfrom), len);
    from[len] = '\0';

    if (mbody != NIL)
    {
        len = sizeof (char) * MMsizestr (m, mbody);
        body = (char *) malloc (len + 1);
        if (body == NULL)
        {
            MMechostr (0, "CreateNewSMTPInstance error : memory failed\n");
            MMpull (m);
            MMpush (m, NIL);
            return ObjCurlFinished (&st);
        }
        strncpy (body, MMstartstr (m, mbody), len);
        body[len] = '\0';
    }
    else
        body = "";

    while (mtos != NIL)
    {
        mto = MTOP (MMfetch (m, mtos, 0));
        if (mto != NIL)
        {
            len = sizeof (char) * MMsizestr (m, mto);
            to = (char *) malloc (len + 1);
            if (to == NULL)
            {
                MMechostr (0, "CreateNewSMTPInstance error : memory failed\n");
                MMpull (m);
                MMpush (m, NIL);
                return ObjCurlFinished (&st);
            }
            strncpy (to, MMstartstr (m, mto), len);
            to[len] = '\0';

            st.header = curl_slist_append(st.header, to);
            free (to);
        }
        mtos = MTOP (MMfetch (m, mtos, 1));
    }



/* ****************************************************************************
 *
 *  Initialisation : libcurl
 *
 * ***************************************************************************/

    /* global libcurl initialisation */
    if (curl_global_init (CURL_GLOBAL_ALL) != CURLE_OK)
    { /* init falied */
        MMechostr (0, "CreateNewSMTPInstance error : global init failed\n");
        MMpull (m);
        MMpush (m, NIL);
        return ObjCurlFinished (&st);
    }

    /* libcurl session init */
    st.hcurl = curl_easy_init ();

    if (st.hcurl)
    {
        memset (st.cerr, '\0', CURL_ERROR_SIZE);

/* ****************************************************************************
 *
 *  Create the Scol object (ObjCurl)
 *
 * ***************************************************************************/

        sizetab = sizeof (st.hcurl) + 1;
        curltab = MMmalloc (m, sizetab, TYPETAB);

        if ((curltab == NIL))
        {
            MMechostr (0, "CreateNewSMTPInstance error : memory failed\n");
            MMpull (m);
            MMpush (m, NIL);
            return ObjCurlFinished (&st);
        }

        MMstore (m, curltab, OBJCURL_HANDLE, (int) st.hcurl);
        MMpush (m, PTOM (curltab));
        OBJcreate (m, ObjCurl, (int) st.hcurl, -1, -1);

/* ****************************************************************************
 *
 *  Define the Scol reflex
 *
 * ***************************************************************************/

        MMpush (m, mcb);
        MMpush (m, mup);

        st.m2m = m;
        st.mode = 0;

        OBJaddreflex (m, ObjCurl, CURL_CB_NEW);

/* ****************************************************************************
 *
 *  Set libcurl parameters and perform the demand
 *
 * ***************************************************************************/

        curl_easy_setopt (st.hcurl, CURLOPT_URL, st.url);

/*        curl_easy_setopt(st.hcurl, CURLOPT_MAIL_FROM, from);

        curl_easy_setopt(st.hcurl, CURLOPT_MAIL_RCPT, st.header);*/

        curl_easy_setopt(st.hcurl, CURLOPT_READDATA, body);


        st.result = curl_easy_perform (st.hcurl);

        return ObjCurlFinished (&st);
    }

    MMechostr (0, "CreateNewSMTPInstance error : easy init failed\n");
    curl_global_cleanup();

    MMpull (m);
    MMpush (m, NIL);

    return ObjCurlFinished (&st);
}





















/* ****************************************************************************
*
*
*   SCOL FUNCTIONS
*
*       _curlNewUrl
*       _curlNewUrlAuth
*       _curlNewFtp
*       _curlNewFtpGet
*       _curlNewFtpPut
*
**************************************************************************** */





/**
 * \brief _curlNewUrl : .
 *
 * Scol prototype : fun [Chn S [S r1] I S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl
 *
 * \param Chn : channel
 * \param S : url
 * \param [S r1] : header (can be nil)
 * \param I : request mode (get = 0, post = 1, put = 2 or +10 (not verify with https://))
 * \param S : datas (can be nil)
 * \param fun [ObjCurl u0 S I S S] u1 : reflex
 * \param u0 : user param
 * \return ObjCurl : a new object or nil if error 
 */
static int SCOL_curlNewUrl (mmachine m)
{
    MMechostr (MSKDEBUG, "SCOL_curlNewUrl : entering ...\n");

    return CreateNewCurlInstance (m, 0);
}


/**
 * \brief _curlNewUrlAuth : .
 *
 * Scol prototype : fun [Chn S [S I] I S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl
 *
 * \param Chn : channel
 * \param S : url
 * \param [S I] : auth (user:password) et mode (any = 0, basic = 1, digest = 2)
 * \param I : request mode (get = 0, post = 1, put = 2 or +10 (not verify with https://))
 * \param S : datas (can be nil)
 * \param fun [ObjCurl u0 S I S S] u1 : reflex
 * \param uo : user param
 * \return ObjCurl : a new object or nil if error 
 */
int SCOL_curlNewUrlAuth (mmachine m)
{
   MMechostr (MSKDEBUG, "SCOL_curlNewUrlAuth : entering ...\n");

   return CreateNewCurlInstance (m, 1);
}





/**
 * \brief _curlNewFtp : .
 *
 * Scol return : the new object (ObjCurl)
 *
 * Prototype : fun [Chn S [[S I] r1] S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl
 *
 * \param Chn : a channel
 * \param S : server ip or domain
 * \param [[S I] r1] : FTP commands / headers
 * \param S : username. Should be nil if anonymous request accepted by the server only
 * \param S : password. Should be nil if anonymous request accepted by the server only
 * \param fun [ObjCurl u0 S I S S] u1 : reflex
 * \param u0 : user parameter
 * \return ObjCurl : a new object
 */
int SCOL_curlNewFtp (mmachine m)
{
    MMechostr (MSKDEBUG, "SCOL_curlNewFtp : entering ...\n");

    return CreateNewFTPInstance (m, 0, 0);
}

/**
 * \brief _curlNewFtpGet : .
 *
 *
 * Scol return : the new object (ObjCurl)
 *
 * Prototype : fun [Chn S S S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl
 *
 * \param Chn : channel
 * \param S : server ip or domain
 * \param S : remote filename
 * \param S : username. Should be nil if anonymous request accepted by the server only
 * \param S : password. Should be nil if anonymous request accepted by the server only
 * \param fun [ObjCurl u0 S I S S] u1 : reflex
 * \param u0 : user parameter
 * \return ObjCurl : a new object or nil if error
 */
int SCOL_curlNewFtpGet (mmachine m)
{
    MMechostr (MSKDEBUG, "SCOL_curlNewFtpGet : entering ...\n");

    return CreateNewFTPInstance (m, 1, 0);
}

/**
 * \brief _curlNewFtpPut : .
 *
 * Scol return : the new object (ObjCurl)
 *
 * Prototype : fun [Chn S [[S I] r1] S S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl
 *
 * \param Chn : channel
 * \param S : server ip or domain
 * \param S : local filename (file to upload, must be in the Scol partition)
 * \param [[S I] r1] : FTP commands / headers (can be nil)
 * \param S : username. Should be nil if anonymous request accepted by the server only
 * \param S : password. Should be nil if anonymous request accepted by the server only
 * \param fun [ObjCurl u0 S I S S] u1 : reflex
 * \param u0 : user parameter
 * \return ObjCurl : a new object 
 */
int SCOL_curlNewFtpPut (mmachine m)
{
    MMechostr (MSKDEBUG, "SCOL_curlNewFtpPut : entering ...\n");

    return CreateNewFTPInstance (m, 2, 0);
}


/**
 * \brief _curlNewSFtp : .
 *
 * Scol return : the new object (ObjCurl)
 *
 * Prototype : fun [Chn S [[S I] r1] S S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl
 *
 * \param Chn : channel
 * \param S : server ip or domain
 * \param S : local filename (file to upload, must be in the Scol partition)
 * \param [[S I] r1] : SFTP commands / headers (can be nil)
 * \param S : username. Should be nil if anonymous request accepted by the server only
 * \param S : password. Should be nil if anonymous request accepted by the server only
 * \param fun [ObjCurl u0 S I S S] u1 : reflex
 * \param u0 : user parameter
 * \return ObjCurl : a newobjkecrt
 */
int SCOL_curlNewSFtp (mmachine m)
{
    MMechostr (MSKDEBUG, "SCOL_curlNewSFtp : entering ...\n");

    return CreateNewFTPInstance (m, 0, 1);
}


/**
 * \brief S_curlNewSmtp : .
 *
 *
 * Scol return : the new object (ObjCurl)
 *
 * Prototype : fun [Chn S S [S r1] S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl
 *
 * \param Chn : channel
 * \param S : smtp server (address)
 * \param S : from
 * \param [S r1] : to.
 * \param S : body.
 * \param fun [ObjCurl u0 S I S S] u1 : reflex
 * \param u0 : user parameter
 * \return ObjCurl : a new object or nil if error
 */
int SCOL_curlNewSmtp (mmachine m)
{
    MMechostr (MSKDEBUG, "SCOL_curlNewSmtp : entering ...\n");

    return CreateNewSMTPInstance (m);
}












int test (mmachine m)
{
    #define HANDLECOUNT 1
    #define HTTP_HANDLE 0

    FILE *fp;

    CURL *handles[HANDLECOUNT];
  CURLM *multi_handle;

  int still_running; /* keep number of running handles */
  int i;

  CURLMsg *msg; /* for picking up messages with the transfer status */
  int msgs_left; /* how many messages are left */


  /* Allocate one CURL handle per transfer */
  for (i=0; i<HANDLECOUNT; i++)
      handles[i] = curl_easy_init();

  /* set the options (I left out a few, you'll get the point anyway) */
  curl_easy_setopt (handles[HTTP_HANDLE], CURLOPT_URL, "http://iri3d.free.fr/Editeurs/SCS/2.5.2/setup_scs.exe");
  curl_easy_setopt (handles[HTTP_HANDLE], CURLOPT_WRITEFUNCTION, fwrite);
  fp = fopen ("/home/iri/Bureau/scol/scol/partition/tests/vrac/testmulti.txt", "w");
  curl_easy_setopt (handles[HTTP_HANDLE], CURLOPT_WRITEDATA, (FILE *) fp);

  /* init a multi stack */
  multi_handle = curl_multi_init();

  /* add the individual transfers */
  for (i=0; i<HANDLECOUNT; i++)
      curl_multi_add_handle(multi_handle, handles[i]);

  /* we start some action by calling perform right away */
  curl_multi_perform(multi_handle, &still_running);

  while(still_running) {
    struct timeval timeout;
    int rc; /* select() return code */

    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    int maxfd = -1;

    long curl_timeo = -1;

    FD_ZERO(&fdread);
    FD_ZERO(&fdwrite);
    FD_ZERO(&fdexcep);

    /* set a suitable timeout to play around with */
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    curl_multi_timeout(multi_handle, &curl_timeo);
    if(curl_timeo >= 0) {
      timeout.tv_sec = curl_timeo / 1000;
      if(timeout.tv_sec > 1)
        timeout.tv_sec = 1;
      else
        timeout.tv_usec = (curl_timeo % 1000) * 1000;
    }

    /* get file descriptors from the transfers */
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

    /* In a real-world program you OF COURSE check the return code of the
       function calls.  On success, the value of maxfd is guaranteed to be
       greater or equal than -1.  We call select(maxfd + 1, ...), specially in
       case of (maxfd == -1), we call select(0, ...), which is basically equal
       to sleep. */

    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

    switch(rc) {
    case -1:
      /* select error */
      break;
    case 0: /* timeout */
    default: /* action */
      curl_multi_perform(multi_handle, &still_running);
      break;
    }
  }

  /* See how the transfers went */
  while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
    if (msg->msg == CURLMSG_DONE) {
      int idx, found = 0;

      /* Find out which handle this message is about */
      for (idx=0; idx<HANDLECOUNT; idx++) {
        found = (msg->easy_handle == handles[idx]);
        if(found)
          break;
      }

      switch (idx) {
      case HTTP_HANDLE:
        printf("HTTP transfer completed with status %d\n", msg->data.result);
        break;
      }
    }
  }

  curl_multi_cleanup(multi_handle);

  /* Free the CURL handles */
  for (i=0; i<HANDLECOUNT; i++)
      curl_easy_cleanup(handles[i]);

    fclose (fp);

  MMpush (m, 0);

  return 0;

}





/* API definitions */

char* curl_name[CURL_PKG_NB] =
{
    "ObjCurl",  /* New type */

    "_curlNewUrl",
    "_curlNewUrlAuth",
    "_curlNewFtp",
    "_curlNewFtpGet",
    "_curlNewFtpPut",
    "_curlNewSFtp",
    "_curlNewSmtp",
    "_curlDsUrl",

    "test__"
};

int (*curl_fun[CURL_PKG_NB]) (mmachine m) =
{
    NULL,       /* ObjCurl */

    SCOL_curlNewUrl,    /* _curlNewUrl */
    SCOL_curlNewUrlAuth,    /* _curlNewUrlAuth */
    SCOL_curlNewFtp,        /* _curlNewFtp */
    SCOL_curlNewFtpGet,     /* _curlNewFtpGet */
    SCOL_curlNewFtpPut,     /* _curlNewFtpPut */
    SCOL_curlNewSFtp,        /* _curlNewSFtp */
    SCOL_curlNewSmtp,       /* _curlNewSmtp */
    SCOL_curlDsUrl,  /* _curlDsUrl */

    test
};

int curl_narg[CURL_PKG_NB] =
{
    TYPTYPE,         /* ObjCurl */

    7,          /* _curlNewUrl */
    8,          /* _curlNewUrlAuth */
    6,          /* _curlNewFtp */
    7,          /* _curlNewFtpGet */
    8,          /* _curlNewFtpPut */
    7,          /* _curlNewSFtp */
    6,           /* _curlNewSmtp */
    1,           /* _curlDsUrl */

    0
};

char* curl_type[CURL_PKG_NB] =
{
    NULL,            /* ObjCurl */

    "fun [Chn S [S r1] I S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl",        /* _curlNewUrl */
    "fun [Chn S [S r1] [S I] I S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl",         /* _curlNewUrlAuth */
    "fun [Chn S [[S I] r1] S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl",        /* _curlNewFtp */
    "fun [Chn S S S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl",         /* _curlNewFtpGet */
    "fun [Chn S S [[S I] r1] S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl",      /* _curlNewFtpPut */
    "fun [Chn S [[S I] r1] S S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl",        /* _curlNewSFtp */
    "fun [Chn S S [S r1] S fun [ObjCurl u0 S I S S] u1 u0] ObjCurl",             /* _curlNewSmtp */
    "fun [ObjCurl] I",                                                 /* _curlDsUrl */

    "fun [] I"
};

int SCOLinitCURLclass (mmachine m)
{
    int k = 0;
    MMechostr (MSKDEBUG, "SCOLinitCURLclass library : entering\n");

    ObjCurl = OBJregister (CURL_RFL_NB, 1, ObjCurlDestroy, "ObjCurlType");
    OBJgetUserEvent(); /* utile ? */

    k = PKhardpak (m, "CURLengine", CURL_PKG_NB, curl_name, curl_fun, curl_narg, curl_type);

    return k;
}
