/*
 * zurl.c - download file over http 
 * (c) HerzAusGold 2006 
 *
 */
 /*
  *
  * routines to get a file from the web 
  *-----------------------------------------------------------------------
  * same licence as the CURL source  
  * 
  * Play at least one level of the game * Rocks'n'Diamonds *
  * available at:     http://www.artsoft.org
  *
  * You can start with classic, or my levels available
  * here: http://www.zomis.net/rnd/download.php?id=421  
  * Or most likely SnakeBite, BD2K3, JUE 
  * 
  *  
  */

#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>
#include <curl/types.h> /* new for v7 */
#include <curl/easy.h>  /* new for v7 */
#include <string.h>

#define ZURL_QUOTE   '"'    /* " */
#define ZURL_SLASH   '/'    /* / */
#define ZURL_BLANK   ' '    /*   */

typedef double zurlBar_t;  /* progessBar - only for illustration, debugging */

/*********************************************************************************/
/* some memory alloc and free functions                                          */
/*********************************************************************************/
void *checked_calloc(unsigned long size)
{
  void *ptr;
  ptr = calloc(1, size);
  return ptr;
}

void *checked_malloc(unsigned long size)
{
  void *ptr;
  ptr = malloc(size);
  return ptr;
}

void checked_free(void *ptr)
{
  if (ptr != NULL)	/* this check should be done by free() anyway */
    free(ptr);
}

void ptr_free(void **ptr)
{
  if (*ptr != NULL) {	
    checked_free(*ptr);
    *ptr = NULL;
  }
}

char *getStringCopy(char *s)
{
  char *s_copy;

  if (s == NULL)
    return NULL;

  s_copy = checked_malloc(strlen(s) + 1);
  strcpy(s_copy, s);

  return s_copy;
}

char *getPath2(char *path1, char *path2)
{
  char *complete_path = checked_malloc(strlen(path1) + 1 +
				       strlen(path2) + 1);

  sprintf(complete_path, "%s/%s", path1, path2);

  return complete_path;
}

/*********************************************************************************/

/*--> zurl.h *********************************************************************/


#define ZURL_DEBUGFILE      1
#define ZURL_FINDFILENAME   2

typedef struct zurl_debug_s {
/* general */
    long  m_mode;
    long  m_done;
    FILE *m_dbgfile;
    char *m_pathname;
    char **m_pfilename;  /* pointer to real filename pointer */
} zurl_debug_t;


typedef struct zurl_s {
/* general */
    char *m_file_url;
    char *m_proxy_url;
    long  m_use_post;
    char *m_post_fields;
/* options and flags */
    long  m_optConnectTimeout;
    long  m_optBuffersize;
    long  m_optLowSpeedLimit;
    long  m_optLowSpeedTime;
    long  m_flagFreshConnect;
    long  m_flagAutoReferer;
    long  m_flagFollowLocation;
/* for the file */
    long  m_resume;  
    FILE *m_savefile;
    char *m_savefileas;
/* for debugging */
    long  m_debug;
    long  m_verbose;
    long  m_progress;
    void *m_debugData; 
} zurl_t;

/*<-- zurl.h *********************************************************************/


/* writeFunction - write to file 
*/  
static int writeFunction(void *ptr, size_t size, size_t nmemb, zurl_t *zu)
{
  if (!zu->m_savefile) {
    zu->m_savefile = fopen(zu->m_savefileas, "wb");
  }
  if (zu->m_savefile) {
    return fwrite(ptr, size, nmemb, zu->m_savefile);
  } else {
    return 0;
  }
}

/* progressFunction - show the progress 
*/  
static int progressFunction(zurlBar_t *ptr,  /* mydata */
                            double t,   /* dltotal */ 
                            double d,   /* dlnow   */
                            double ultotal,
                            double ulnow)
{
    *ptr = (double) (d*100.0/t);  /* mydata */
    fprintf(stderr, "%d / %d (%g %%)\n", d, t, d*100.0/t);
    return 0;
}

/* debugFunction - for strange debugging  
* rawBytes comes straight from libcurl and userptr from setopt DEBUGDATA, 
* though we're not using it here.
*/
static int debugFunction(CURL *handle,       /* the handle/transfer this concerns */
                         curl_infotype type, /* what kind of data */
                         char *data,         /* points to the data */
                         size_t size,        /* size of the data pointed to */
                         void *userptr)
{      
    static char  seastr1[]  = {'G','E','T',' ',0};
    static int   lx1  = 4;
    static char  seastr2[]  = {'f','i','l','e','n','a','m','e','=',ZURL_QUOTE,0};
    static int   lx2  = 10;
    static char  donestr[]  = {'C','o','n','t','e','n','t','-','T','y','p','e',0};
    static int   lxd  = 12;

    int    tt = type;             
    size_t sz = size; 
    char  *dd = data;

    /* userptr */ 
    if (userptr != NULL) {
      zurl_debug_t *dp = userptr;
      if (dp->m_mode & ZURL_DEBUGFILE) {
        fwrite (data, size, 1, dp->m_dbgfile);
      }
      if (dp->m_mode & ZURL_FINDFILENAME) {
        if (!(dp->m_done & ZURL_FINDFILENAME)) 
        {
          size_t sz = 0;
          char *p1=NULL, *p2=NULL;
          char *pa=NULL, *pe=NULL;
          /* search */
          while (sz < size) {
            if (sz < size - lx1) {
              if (strncmp(data+sz, seastr1, lx1) == 0) {
                p1 = data + sz + lx1;
                sz += lx1;
                break;
              }
            }
            if (sz < size - lx2) {
              if (strncmp(data+sz, seastr2, lx2) == 0) {
                p2 = data + sz + lx2;
                sz += lx2;
                break;
              }
            }
            if (sz < size - lxd) {
              if (strncmp(data+sz, donestr, lxd) == 0) {
                dp->m_done |= ZURL_FINDFILENAME;  /* done */
                sz += lxd;
                break;
              }
            }

            sz++;
          }
          /* p1 found */
          if (p1) {
            pa = p1;
            while (sz < size) {
              if (data[sz] == ZURL_SLASH) {
                pa = data+sz+1;
              }
              if (data[sz] == ZURL_BLANK) {
                pe = data+sz;
                break;
              }
              sz++;
            }
          }
          /* p2 found */
          if (p2) {
            pa = p2;
            while (sz < size) {
              if (data[sz] == ZURL_QUOTE) {
                pe = data+sz;
                break;
              }
              sz++;
            }
          }

          /* check */
          if (pa && pe && pe > pa) {
            char v = *pe;
            *pe = 0;
            if (*(dp->m_pfilename) != NULL) {
              ptr_free((void*)dp->m_pfilename);
            }
            *(dp->m_pfilename) = getPath2(dp->m_pathname, pa);
            *pe = v;
          }
        } 
      } /* ZURL_FINDFILENAME */
    }

    return 0;
}


/* checkFileSize - get file size 
*/  
static long checkFileSize(char* filename)
{
  long sz  = 0;
  FILE *fp = fopen(filename, "r");
  if (fp) {
    fseek(fp, 0, SEEK_END);
    sz = ftell(fp);
    fclose(fp);
  }
  return sz;
}


/*************************************************************
 zurl_easyGet - get and download file 
*************************************************************/
int zurl_easyGet(zurl_t *zu)
{    
    /* curl handle */
    CURL      *cuh;  /* the curl handle */
    CURLcode  ret;
    double    filesize = 0;
    zurlBar_t bar = 0;   /* progressBar */
 
    /* global init, only once in an application*/
    /* curl_global_init(CURL_GLOBAL_ALL); */

    /* init the curl session */
    cuh = curl_easy_init();
    if (cuh == NULL) {
      return CURLE_FAILED_INIT;  /* if handle is null(no internet connection), abort file download */
    }
     
    /*--- set easy options ---*/

    if (zu->m_flagFreshConnect) {
      curl_easy_setopt (cuh, CURLOPT_FRESH_CONNECT, TRUE);   /*Set to use fresh connection every time*/
    }
    if (zu->m_optConnectTimeout < 0) {
      curl_easy_setopt (cuh, CURLOPT_CONNECTTIMEOUT, 5);     /*Time out in second*/
    } else if (zu->m_optConnectTimeout > 0) {
      curl_easy_setopt (cuh, CURLOPT_CONNECTTIMEOUT, zu->m_optConnectTimeout);     
    }

    if (zu->m_savefileas != NULL && zu->m_savefileas[0] != 0) {
      filesize = checkFileSize(zu->m_savefileas);
      if (zu->m_resume && filesize > 0) {
        curl_easy_setopt (cuh, CURLOPT_RESUME_FROM, filesize);  /*Resume file download start chunk from position XXX bytes*/
        zu->m_savefile = fopen (zu->m_savefileas, "ab+");
      } else {
        zu->m_savefile = fopen (zu->m_savefileas, "wb");
      }
    }
    curl_easy_setopt (cuh, CURLOPT_USERAGENT, "libcurl-agent/1.0");  /* agent */

    /* some options (default == -1) */
    if (zu->m_optBuffersize < 0) {
      curl_easy_setopt (cuh, CURLOPT_BUFFERSIZE, 500);      /* File buffer size */
    } else if (zu->m_optBuffersize > 0) {
      curl_easy_setopt (cuh, CURLOPT_BUFFERSIZE, zu->m_optBuffersize );      
    }
    if (zu->m_optLowSpeedLimit < 0) {
      curl_easy_setopt (cuh, CURLOPT_LOW_SPEED_LIMIT, 10);  /* Time lower limit */
    } else if (zu->m_optLowSpeedLimit > 0) {
      curl_easy_setopt (cuh, CURLOPT_LOW_SPEED_LIMIT, zu->m_optLowSpeedLimit);  
    }
    if (zu->m_optLowSpeedTime < 0) {
      curl_easy_setopt (cuh, CURLOPT_LOW_SPEED_TIME, 5);    /* Download lower speed limit */
    } else if (zu->m_optLowSpeedTime > 0) {
      curl_easy_setopt (cuh, CURLOPT_LOW_SPEED_TIME, zu->m_optLowSpeedTime);   
    }

    /* proxy server */
    if (zu->m_proxy_url != NULL && zu->m_proxy_url[0] != 0) {
      curl_easy_setopt (cuh, CURLOPT_PROXY, zu->m_proxy_url);  /* using proxy server, format http://proxyserver:portno */
    }

    /* some flags */
    if (zu->m_flagAutoReferer) {
      curl_easy_setopt (cuh, CURLOPT_AUTOREFERER, TRUE);    /* Set to follow referer location */
    }
    if (zu->m_flagFollowLocation) {
      curl_easy_setopt (cuh, CURLOPT_FOLLOWLOCATION, TRUE); /* Set to follow location */
    }

    /* the one and only url */
    curl_easy_setopt (cuh, CURLOPT_URL, zu->m_file_url);  /* File download URL */

    /* post fields */
    if (zu->m_use_post) {
      if (zu->m_post_fields != NULL && zu->m_post_fields[0] != 0) {
        curl_easy_setopt (cuh, CURLOPT_POST, 1);            /* Set to 1 with POST field append */
        curl_easy_setopt (cuh, CURLOPT_POSTFIELDS, zu->m_post_fields);  /* Your field submit format "a=1&b=2&c=3" */
      } else {
        curl_easy_setopt (cuh, CURLOPT_POST, 0);            /* Set to 0 no POST field append */
      }
    }
               
    /* callback functions  - pointer to file-pointer (for findfile)  */                                        
    curl_easy_setopt (cuh, CURLOPT_WRITEDATA, zu);   /* Get data here...*/
    curl_easy_setopt (cuh, CURLOPT_WRITEFUNCTION, writeFunction);
    if (zu->m_debugData != NULL) {
      curl_easy_setopt (cuh, CURLOPT_DEBUGDATA, zu->m_debugData); /* and debug info here...*/
    }
    curl_easy_setopt (cuh, CURLOPT_DEBUGFUNCTION, debugFunction);
    
    /* verbose, debug */
    if (zu->m_verbose) {
      curl_easy_setopt (cuh, CURLOPT_VERBOSE, TRUE);
    }
    /* progress */
    if (zu->m_progress) {
      curl_easy_setopt (cuh, CURLOPT_NOPROGRESS, FALSE);      /*Get file transfer progress here...*/
      curl_easy_setopt (cuh, CURLOPT_PROGRESSFUNCTION, progressFunction); 
      curl_easy_setopt (cuh, CURLOPT_PROGRESSDATA, (void*) &bar);      /*Get progress data*/
    }

    /* perform now! */
    ret = curl_easy_perform(cuh);
    
    if (zu->m_savefile) {
      fclose(zu->m_savefile);
      zu->m_savefile = NULL;
    } 
    curl_easy_cleanup (cuh);
         
    /* global cleanup, only once in an application*/
    /* curl_global_cleanup(); */

    return ret;   
} /* zurl_easy_get */


/*************************************************************
 zurl_get - get and download file 
*************************************************************/
int zurl_get(char *dir, char *url, char *proxy, long debug,
             char **pFilename) 
{
  int               ret;
  zurl_t            zu;
  zurl_debug_t      debugData;
  char              *dbgfilename = NULL;

  zu.m_file_url          = url;    /* pointer to url */
  zu.m_proxy_url         = proxy;  /* proxy */
  zu.m_use_post          = FALSE;  /* no post */
  zu.m_post_fields       = NULL;   /* no post fields */
  zu.m_debug             = debug;

  debugData.m_mode        = ZURL_FINDFILENAME;
  debugData.m_done        = 0;
  debugData.m_dbgfile     = NULL;
  debugData.m_pathname    = dir;
  debugData.m_pfilename   = &zu.m_savefileas;   /* filled in zurl_easy_get */

  if (zu.m_debug) {
    dbgfilename = getPath2(dir,"zdebug.tmp");
    debugData.m_mode      |= ZURL_DEBUGFILE;
    debugData.m_dbgfile   = fopen(dbgfilename,"wb");
  }

  zu.m_optConnectTimeout  = 0;   /* none */
  zu.m_optBuffersize      = -1;  /* default */ 
  zu.m_optLowSpeedLimit   = -1;  /* default */
  zu.m_optLowSpeedTime    = -1;  /* default */
  zu.m_flagFreshConnect   = 0;   /* none */
  zu.m_flagAutoReferer    = 1;   /* on */
  zu.m_flagFollowLocation = 1;   /* on */

  zu.m_resume             = 0;      /* no resume */
  zu.m_savefile           = NULL;   /* file pointer */
  zu.m_savefileas         = NULL;   /* savefileas - pointer here! */

  zu.m_debugData          = &debugData;
  zu.m_progress           = 0;   /* progress=1,silent=0 */
  zu.m_verbose            = 1;   /* verbose=1,silent=0 */

  ret = zurl_easyGet(&zu);

  if (zu.m_savefileas) {
    if(pFilename) {
      *pFilename = getStringCopy(zu.m_savefileas);
    }
    ptr_free(&zu.m_savefileas);
  }
  if (zu.m_savefile) {
    fclose(zu.m_savefile);
    zu.m_savefile = NULL;
  }

  if (debugData.m_dbgfile) {
    fclose(debugData.m_dbgfile);
    debugData.m_dbgfile = NULL;
  }
  if (dbgfilename) {
    ptr_free(&dbgfilename);
  }

  return ret;
}


/*************************************************************
 main
 usage:
 zurl <url> <dest-dir> <proxy>
 
 write the downloaded file to <dest-dir> or "C:\\temp"
 use proxy: http://proxyserver:portno
   
*************************************************************/
int main(int argc, char *argv[])
{
  char *dir = NULL;
  char *url = NULL;
  char *proxy = NULL;
  char *pFilename = NULL;

  if (argc > 1) {
    url = argv[1];
  }
  if (argc > 2) {
    dir = argv[2];
  } else {
    dir = getStringCopy("C:\\temp");
  }
  if (argc > 3) {
    proxy = argv[3];
  } 
  if (url) {
    zurl_get(dir, url, proxy, 1, &pFilename);
  }
  if (pFilename != NULL) {
    printf("zurl get file: %s\n", pFilename);
  }

  checked_free(pFilename);
} 
