/* $Id: curl.c,v 1.3 2004/05/18 19:32:15 agraef Exp $ */

/* This file is part of the Q programming system.

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

   The Q programming system 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

/* This module currently supports all options up to libcurl 7.10.2. Older
   libcurl versions (>= 7.9.x) are supported (7.9.8 has been tested), versions
   < 7.9 surely need some work. */

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

/* system headers */

#include <stdio.h>
#include <ctype.h>
#include <math.h>

/* check for standard C headers */
#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
#endif

#ifdef HAVE_MALLOC_H
#include <stdlib.h>
#endif

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef DMALLOC
#include <dmalloc.h>
#endif

#include <libq.h>
#include <curl/curl.h>

MODULE(curl)

/* ByteStr data structure, see clib.c */

typedef struct bstr {
  long size;
  unsigned char *v;
} bstr_t;

/* Curl handle data structure */

typedef struct exprl {
  long key;
  expr x;
  struct exprl *next;
} exprl;

typedef struct {
  CURL *handle;
  /* error handling */
  CURLcode code;
  char msg[CURL_ERROR_SIZE];
  /* cached data */
  exprl *cache;
  struct curl_slist *header, *quote, *postquote;
  struct curl_httppost *post;
} curl_t;

static inline expr mkerr(curl_t *curl)
{
  expr x = mkapp(mkapp(mksym(sym(curl_error)), mkint(curl->code)),
		 (curl->msg[0])?mkstr(strdup(curl->msg)):mkvoid);
  curl->code = CURLE_OK;
  curl->msg[0] = 0;
  return x;
}

static exprl *add_cache(curl_t *curl, long key, expr x)
{
  exprl *xl = curl->cache;
  /* first check for an existing entry under the same key */
  while (xl && xl->key != key) xl = xl->next;
  if (xl) {
    freeref(xl->x);
    xl->x = newref(x);
    return xl;
  }
  /* nothing found, allocate a new entry */
  xl = (exprl*)malloc(sizeof(exprl));
  if (!xl) return NULL;
  xl->key = key;
  xl->x = newref(x);
  xl->next = curl->cache;
  curl->cache = xl;
  return xl;
}

static void free_cache(curl_t *curl)
{
  exprl *xl = curl->cache;
  while (xl) {
    exprl *next = xl->next;
    freeref(xl->x);
    free(xl);
    xl = next;
  }
  if (curl->header) curl_slist_free_all(curl->header);
  if (curl->quote) curl_slist_free_all(curl->quote);
  if (curl->postquote) curl_slist_free_all(curl->postquote);
  if (curl->post) curl_formfree(curl->post);
}

DESTRUCTOR(curl,Curl,ptr)
{
  curl_t *curl = (curl_t*)ptr;
  free_cache(curl);
  if (curl->handle) curl_easy_cleanup(curl->handle);
}

/* initialization and finalization */

INIT(curl)
{
  curl_global_init(CURL_GLOBAL_SSL);
}

FINI(curl)
{
  curl_global_cleanup();
}

/* manifest constants */

/* error codes:

CURLE_OK,
CURLE_UNSUPPORTED_PROTOCOL,
CURLE_FAILED_INIT,
CURLE_URL_MALFORMAT,
CURLE_URL_MALFORMAT_USER,
CURLE_COULDNT_RESOLVE_PROXY,
CURLE_COULDNT_RESOLVE_HOST,
CURLE_COULDNT_CONNECT,
CURLE_FTP_WEIRD_SERVER_REPLY,
CURLE_FTP_ACCESS_DENIED,
CURLE_FTP_USER_PASSWORD_INCORRECT,
CURLE_FTP_WEIRD_PASS_REPLY,
CURLE_FTP_WEIRD_USER_REPLY,
CURLE_FTP_WEIRD_PASV_REPLY,
CURLE_FTP_WEIRD_227_FORMAT,
CURLE_FTP_CANT_GET_HOST,
CURLE_FTP_CANT_RECONNECT,
CURLE_FTP_COULDNT_SET_BINARY,
CURLE_PARTIAL_FILE,
CURLE_FTP_COULDNT_RETR_FILE,
CURLE_FTP_WRITE_ERROR,
CURLE_FTP_QUOTE_ERROR,
CURLE_HTTP_NOT_FOUND,
CURLE_WRITE_ERROR,
CURLE_MALFORMAT_USER,
CURLE_FTP_COULDNT_STOR_FILE,
CURLE_READ_ERROR,
CURLE_OUT_OF_MEMORY,
CURLE_OPERATION_TIMEOUTED,
CURLE_FTP_COULDNT_SET_ASCII,
CURLE_FTP_PORT_FAILED,
CURLE_FTP_COULDNT_USE_REST,
CURLE_FTP_COULDNT_GET_SIZE,
CURLE_HTTP_RANGE_ERROR,
CURLE_HTTP_POST_ERROR,
CURLE_SSL_CONNECT_ERROR,
CURLE_BAD_DOWNLOAD_RESUME,
CURLE_FILE_COULDNT_READ_FILE,
CURLE_LDAP_CANNOT_BIND,
CURLE_LDAP_SEARCH_FAILED,
CURLE_LIBRARY_NOT_FOUND,
CURLE_FUNCTION_NOT_FOUND,
CURLE_ABORTED_BY_CALLBACK,
CURLE_BAD_FUNCTION_ARGUMENT,
CURLE_BAD_CALLING_ORDER,
CURLE_HTTP_PORT_FAILED,
CURLE_BAD_PASSWORD_ENTERED,
CURLE_TOO_MANY_REDIRECTS,
CURLE_UNKNOWN_TELNET_OPTION,
CURLE_TELNET_OPTION_SYNTAX,
CURLE_OBSOLETE,
CURLE_SSL_PEER_CERTIFICATE,
CURLE_GOT_NOTHING,
CURLE_SSL_ENGINE_NOTFOUND,
CURLE_SSL_ENGINE_SETFAILED,
CURLE_SEND_ERROR,
CURLE_RECV_ERROR,
CURLE_SHARE_IN_USE,
CURLE_SSL_CERTPROBLEM,
CURLE_SSL_CIPHER,
CURLE_SSL_CACERT,
CURLE_BAD_CONTENT_ENCODING

*/

/* various option values:

CURLINFO_TEXT,
CURLINFO_HEADER_IN,
CURLINFO_HEADER_OUT,
CURLINFO_DATA_IN,
CURLINFO_DATA_OUT,

CURLFORM_PTRCONTENTS, // a.k.a. CURLFORM_CONTENT
CURLFORM_FILECONTENT,
CURLFORM_FILE,
CURLFORM_BUFFER,

CURL_NETRC_OPTIONAL,
CURL_NETRC_IGNORED,
CURL_NETRC_REQUIRED,

CURL_TIMECOND_IFMODSINCE,
CURL_TIMECOND_IFUNMODSINCE,

CURL_HTTP_VERSION_NONE,
CURL_HTTP_VERSION_1_0,
CURL_HTTP_VERSION_1_1,

CURLCLOSEPOLICY_LEAST_RECENTLY_USED,
CURLCLOSEPOLICY_OLDEST

*/

/* info values:

CURLINFO_EFFECTIVE_URL,
CURLINFO_HTTP_CODE,
CURLINFO_FILETIME,
CURLINFO_TOTAL_TIME,
CURLINFO_NAMELOOKUP_TIME,
CURLINFO_CONNECT_TIME,
CURLINFO_PRETRANSFER_TIME,
CURLINFO_STARTTRANSFER_TIME,
CURLINFO_REDIRECT_TIME,
CURLINFO_REDIRECT_COUNT,
CURLINFO_SIZE_UPLOAD,
CURLINFO_SIZE_DOWNLOAD,
CURLINFO_SPEED_DOWNLOAD,
CURLINFO_SPEED_UPLOAD,
CURLINFO_HEADER_SIZE,
CURLINFO_REQUEST_SIZE,
CURLINFO_SSL_VERIFYRESULT,
CURLINFO_CONTENT_LENGTH_DOWNLOAD,
CURLINFO_CONTENT_LENGTH_UPLOAD,
CURLINFO_CONTENT_TYPE

*/

/* FIXME: to be implemented (libcurl > 7.10.2):

CURLOPT_NETRC_FILE
CURLOPT_HTTPAUTH
CURLAUTH_BASIC
CURLAUTH_DIGEST
CURLAUTH_GSSNEGOTIATE
CURLAUTH_NTLM
CURLAUTH_ANY
CURLAUTH_ANYSAFE
CURLOPT_PROXYAUTH
CURLOPT_HTTP200ALIASES
CURLOPT_FTP_USE_EPRT
CURLOPT_FTP_CREATE_MISSING_DIRS
CURLOPT_FTP_RESPONSE_TIMEOUT
CURLOPT_RESUME_FROM_LARGE
CURLOPT_INFILESIZE_LARGE
CURLOPT_MAXFILESIZE_LARGE
CURLOPT_PRIVATE

CURLINFO_RESPONSE_CODE
CURLINFO_PRIVATE
CURLINFO_HTTPAUTH_AVAIL
CURLINFO_PROXYAUTH_AVAIL

*/

/* Options and infos not supported by older libcurl versions. We just remap
   options not present to -1 and reject them in the curl_setopt and
   curl_getinfo functions. */

#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLOPT_NOSIGNAL (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070402
#define CURLOPT_PASSWDFUNCTION (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070702
#define CURLOPT_HEADERFUNCTION (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLOPT_PROXYTYPE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070300
#define CURLOPT_HTTPPROXYTUNNEL (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070300
#define CURLOPT_INTERFACE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_DNS_CACHE_TIMEOUT (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_DNS_USE_GLOBAL_CACHE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLOPT_BUFFERSIZE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070500
#define CURLOPT_MAXREDIRS (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070200
#define CURLOPT_POSTFIELDSIZE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070900
#define CURLOPT_COOKIEJAR (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070801
#define CURLOPT_HTTPGET (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070500
#define CURLOPT_FILETIME (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070700
#define CURLOPT_MAXCONNECTS (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070700
#define CURLOPT_CLOSEPOLICY (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070700
#define CURLCLOSEPOLICY_LEAST_RECENTLY_USED (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070700
#define CURLCLOSEPOLICY_OLDEST (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070700
#define CURLOPT_FRESH_CONNECT (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070700
#define CURLOPT_FORBID_REUSE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_SSLCERTTYPE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_SSLKEY (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_SSLKEYTYPE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_SSLKEYPASSWD (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_SSLENGINE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070903
#define CURLOPT_SSLENGINE_DEFAULT (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070402
#define CURLOPT_SSL_VERIFYPEER (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070402
#define CURLOPT_CAINFO (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070908
#define CURLOPT_CAPATH (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070801
#define CURLOPT_SSL_VERIFYHOST (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070300
#define CURLOPT_KRB4LEVEL (-1)
#endif

#if LIBCURL_VERSION_NUM < 0x070500
#define CURLINFO_FILETIME (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070907
#define CURLINFO_REDIRECT_TIME (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070907
#define CURLINFO_REDIRECT_COUNT (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070402
#define CURLINFO_SSL_VERIFYRESULT (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070601
#define CURLINFO_CONTENT_LENGTH_DOWNLOAD (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLINFO_CONTENT_LENGTH_UPLOAD (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070904
#define CURLINFO_CONTENT_TYPE (-1)
#endif

/* These aren't mentioned in the docs, but appear to be available for
   version >= 7.10.0 only, so they are listed here. */

#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLOPT_ENCODING (-1)
#endif

#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLE_BAD_DOWNLOAD_RESUME (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLE_SHARE_IN_USE (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLE_SSL_CERTPROBLEM (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLE_SSL_CIPHER (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLE_SSL_CACERT (-1)
#endif
#if LIBCURL_VERSION_NUM < 0x070a00
#define CURLE_BAD_CONTENT_ENCODING (-1)
#endif

/* supported CURL options (setopt): */

static CURLoption options[] = {

  CURLOPT_VERBOSE,
  CURLOPT_HEADER,
  CURLOPT_NOPROGRESS,
  CURLOPT_NOSIGNAL,

  CURLOPT_WRITEFUNCTION,
  CURLOPT_READFUNCTION,
  CURLOPT_PROGRESSFUNCTION,
#if LIBCURL_VERSION_NUM < 0x071000
  CURLOPT_PASSWDFUNCTION,
#endif
  CURLOPT_HEADERFUNCTION,
  CURLOPT_DEBUGFUNCTION,

  CURLOPT_FAILONERROR,

  CURLOPT_URL,
  CURLOPT_PROXY,
  CURLOPT_PROXYPORT,
  CURLOPT_PROXYTYPE,
  CURLOPT_HTTPPROXYTUNNEL,
  CURLOPT_INTERFACE,
  CURLOPT_DNS_CACHE_TIMEOUT,
  CURLOPT_DNS_USE_GLOBAL_CACHE,
  CURLOPT_BUFFERSIZE,

  CURLOPT_NETRC,
  CURLOPT_USERPWD,
  CURLOPT_PROXYUSERPWD,

  CURLOPT_ENCODING,
  CURLOPT_FOLLOWLOCATION,
  CURLOPT_MAXREDIRS,
  CURLOPT_PUT,
  CURLOPT_POST,
  CURLOPT_POSTFIELDS,
  CURLOPT_POSTFIELDSIZE,
  CURLOPT_HTTPPOST,
  CURLOPT_REFERER,
  CURLOPT_USERAGENT,
  CURLOPT_HTTPHEADER,
  CURLOPT_COOKIE,
  CURLOPT_COOKIEFILE,
  CURLOPT_COOKIEJAR,
  CURLOPT_TIMECONDITION,
  CURLOPT_TIMEVALUE,
  CURLOPT_HTTPGET,
  CURLOPT_HTTP_VERSION,

  CURLOPT_FTPPORT,
  CURLOPT_QUOTE,
  CURLOPT_POSTQUOTE,
  CURLOPT_PREQUOTE,
  CURLOPT_FTPLISTONLY,
  CURLOPT_FTPAPPEND,
  CURLOPT_FTP_USE_EPSV,

  CURLOPT_TRANSFERTEXT,
  CURLOPT_CRLF,
  CURLOPT_RANGE,
  CURLOPT_RESUME_FROM,
  CURLOPT_CUSTOMREQUEST,
  CURLOPT_FILETIME,
  CURLOPT_NOBODY,
  CURLOPT_INFILESIZE,
  CURLOPT_UPLOAD,

  CURLOPT_TIMEOUT,
  CURLOPT_LOW_SPEED_LIMIT,
  CURLOPT_LOW_SPEED_TIME,
  CURLOPT_MAXCONNECTS,
  CURLOPT_CLOSEPOLICY,
  CURLOPT_FRESH_CONNECT,
  CURLOPT_FORBID_REUSE,
  CURLOPT_CONNECTTIMEOUT,

  CURLOPT_SSLCERT,
  CURLOPT_SSLCERTTYPE,
  CURLOPT_SSLCERTPASSWD,
  CURLOPT_SSLKEY,
  CURLOPT_SSLKEYTYPE,
  CURLOPT_SSLKEYPASSWD,
  CURLOPT_SSLENGINE,
  CURLOPT_SSLENGINE_DEFAULT,
  CURLOPT_SSLVERSION,
  CURLOPT_SSL_VERIFYPEER,
  CURLOPT_CAINFO,
  CURLOPT_CAPATH,
  CURLOPT_RANDOM_FILE,
  CURLOPT_EGDSOCKET,
  CURLOPT_SSL_VERIFYHOST,
  CURLOPT_SSL_CIPHER_LIST,
  CURLOPT_KRB4LEVEL

};

static int valid_option(CURLoption option)
{
  if (option > 0) {
    int i, n = sizeof(options)/sizeof(CURLoption);
    for (i = 0; i < n; i++)
      if (options[i] == option)
	return 1;
    return 0;
  } else
    return 0;
}

FUNCTION(curl,curl_vars,argc,argv)
{
  if (argc != 0) return __FAIL;
  return mktuplel
    (183,

     mkint(CURLE_OK),
     mkint(CURLE_UNSUPPORTED_PROTOCOL),
     mkint(CURLE_FAILED_INIT),
     mkint(CURLE_URL_MALFORMAT),
     mkint(CURLE_URL_MALFORMAT_USER),
     mkint(CURLE_COULDNT_RESOLVE_PROXY),
     mkint(CURLE_COULDNT_RESOLVE_HOST),
     mkint(CURLE_COULDNT_CONNECT),
     mkint(CURLE_FTP_WEIRD_SERVER_REPLY),
     mkint(CURLE_FTP_ACCESS_DENIED),
     mkint(CURLE_FTP_USER_PASSWORD_INCORRECT),
     mkint(CURLE_FTP_WEIRD_PASS_REPLY),
     mkint(CURLE_FTP_WEIRD_USER_REPLY),
     mkint(CURLE_FTP_WEIRD_PASV_REPLY),
     mkint(CURLE_FTP_WEIRD_227_FORMAT),
     mkint(CURLE_FTP_CANT_GET_HOST),
     mkint(CURLE_FTP_CANT_RECONNECT),
     mkint(CURLE_FTP_COULDNT_SET_BINARY),
     mkint(CURLE_PARTIAL_FILE),
     mkint(CURLE_FTP_COULDNT_RETR_FILE),
     mkint(CURLE_FTP_WRITE_ERROR),
     mkint(CURLE_FTP_QUOTE_ERROR),
     mkint(CURLE_HTTP_NOT_FOUND),
     mkint(CURLE_WRITE_ERROR),
     mkint(CURLE_MALFORMAT_USER),
     mkint(CURLE_FTP_COULDNT_STOR_FILE),
     mkint(CURLE_READ_ERROR),
     mkint(CURLE_OUT_OF_MEMORY),
     mkint(CURLE_OPERATION_TIMEOUTED),
     mkint(CURLE_FTP_COULDNT_SET_ASCII),
     mkint(CURLE_FTP_PORT_FAILED),
     mkint(CURLE_FTP_COULDNT_USE_REST),
     mkint(CURLE_FTP_COULDNT_GET_SIZE),
     mkint(CURLE_HTTP_RANGE_ERROR),
     mkint(CURLE_HTTP_POST_ERROR),
     mkint(CURLE_SSL_CONNECT_ERROR),
     mkint(CURLE_BAD_DOWNLOAD_RESUME),
     mkint(CURLE_FILE_COULDNT_READ_FILE),
     mkint(CURLE_LDAP_CANNOT_BIND),
     mkint(CURLE_LDAP_SEARCH_FAILED),
     mkint(CURLE_LIBRARY_NOT_FOUND),
     mkint(CURLE_FUNCTION_NOT_FOUND),
     mkint(CURLE_ABORTED_BY_CALLBACK),
     mkint(CURLE_BAD_FUNCTION_ARGUMENT),
     mkint(CURLE_BAD_CALLING_ORDER),
     mkint(CURLE_HTTP_PORT_FAILED),
     mkint(CURLE_BAD_PASSWORD_ENTERED),
     mkint(CURLE_TOO_MANY_REDIRECTS),
     mkint(CURLE_UNKNOWN_TELNET_OPTION),
     mkint(CURLE_TELNET_OPTION_SYNTAX),
     mkint(CURLE_OBSOLETE),
     mkint(CURLE_SSL_PEER_CERTIFICATE),
     mkint(CURLE_GOT_NOTHING),
     mkint(CURLE_SSL_ENGINE_NOTFOUND),
     mkint(CURLE_SSL_ENGINE_SETFAILED),
     mkint(CURLE_SEND_ERROR),
     mkint(CURLE_RECV_ERROR),
     mkint(CURLE_SHARE_IN_USE),
     mkint(CURLE_SSL_CERTPROBLEM),
     mkint(CURLE_SSL_CIPHER),
     mkint(CURLE_SSL_CACERT),
     mkint(CURLE_BAD_CONTENT_ENCODING),

     mkint(CURLINFO_TEXT),
     mkint(CURLINFO_HEADER_IN),
     mkint(CURLINFO_HEADER_OUT),
     mkint(CURLINFO_DATA_IN),
     mkint(CURLINFO_DATA_OUT),

     mkint(CURLFORM_PTRCONTENTS),
     mkint(CURLFORM_FILECONTENT),
     mkint(CURLFORM_FILE),
     mkint(CURLFORM_BUFFER),

     mkint(CURL_NETRC_OPTIONAL),
     mkint(CURL_NETRC_IGNORED),
     mkint(CURL_NETRC_REQUIRED),

     mkint(CURL_TIMECOND_IFMODSINCE),
     mkint(CURL_TIMECOND_IFUNMODSINCE),

     mkint(CURL_HTTP_VERSION_NONE),
     mkint(CURL_HTTP_VERSION_1_0),
     mkint(CURL_HTTP_VERSION_1_1),

     mkint(CURLCLOSEPOLICY_LEAST_RECENTLY_USED),
     mkint(CURLCLOSEPOLICY_OLDEST),

     mkint(CURLOPT_VERBOSE),
     mkint(CURLOPT_HEADER),
     mkint(CURLOPT_NOPROGRESS),
     mkint(CURLOPT_NOSIGNAL),
     mkint(CURLOPT_WRITEFUNCTION),
     mkint(CURLOPT_READFUNCTION),
     mkint(CURLOPT_PROGRESSFUNCTION),
#if LIBCURL_VERSION_NUM < 0x071000
     mkint(CURLOPT_PASSWDFUNCTION),
#endif
     mkint(CURLOPT_HEADERFUNCTION),
     mkint(CURLOPT_DEBUGFUNCTION),

     mkint(CURLOPT_FAILONERROR),

     mkint(CURLOPT_URL),
     mkint(CURLOPT_PROXY),
     mkint(CURLOPT_PROXYPORT),
     mkint(CURLOPT_PROXYTYPE),
     mkint(CURLOPT_HTTPPROXYTUNNEL),
     mkint(CURLOPT_INTERFACE),
     mkint(CURLOPT_DNS_CACHE_TIMEOUT),
     mkint(CURLOPT_DNS_USE_GLOBAL_CACHE),
     mkint(CURLOPT_BUFFERSIZE),

     mkint(CURLOPT_NETRC),
     mkint(CURLOPT_USERPWD),
     mkint(CURLOPT_PROXYUSERPWD),

     mkint(CURLOPT_ENCODING),
     mkint(CURLOPT_FOLLOWLOCATION),
     mkint(CURLOPT_MAXREDIRS),
     mkint(CURLOPT_PUT),
     mkint(CURLOPT_POST),
     mkint(CURLOPT_POSTFIELDS),
     mkint(CURLOPT_POSTFIELDSIZE),
     mkint(CURLOPT_HTTPPOST),
     mkint(CURLOPT_REFERER),
     mkint(CURLOPT_USERAGENT),
     mkint(CURLOPT_HTTPHEADER),
     mkint(CURLOPT_COOKIE),
     mkint(CURLOPT_COOKIEFILE),
     mkint(CURLOPT_COOKIEJAR),
     mkint(CURLOPT_TIMECONDITION),
     mkint(CURLOPT_TIMEVALUE),
     mkint(CURLOPT_HTTPGET),
     mkint(CURLOPT_HTTP_VERSION),

     mkint(CURLOPT_FTPPORT),
     mkint(CURLOPT_QUOTE),
     mkint(CURLOPT_POSTQUOTE),
     mkint(CURLOPT_PREQUOTE),
     mkint(CURLOPT_FTPLISTONLY),
     mkint(CURLOPT_FTPAPPEND),
     mkint(CURLOPT_FTP_USE_EPSV),

     mkint(CURLOPT_TRANSFERTEXT),
     mkint(CURLOPT_CRLF),
     mkint(CURLOPT_RANGE),
     mkint(CURLOPT_RESUME_FROM),
     mkint(CURLOPT_CUSTOMREQUEST),
     mkint(CURLOPT_FILETIME),
     mkint(CURLOPT_NOBODY),
     mkint(CURLOPT_INFILESIZE),
     mkint(CURLOPT_UPLOAD),

     mkint(CURLOPT_TIMEOUT),
     mkint(CURLOPT_LOW_SPEED_LIMIT),
     mkint(CURLOPT_LOW_SPEED_TIME),
     mkint(CURLOPT_MAXCONNECTS),
     mkint(CURLOPT_CLOSEPOLICY),
     mkint(CURLOPT_FRESH_CONNECT),
     mkint(CURLOPT_FORBID_REUSE),
     mkint(CURLOPT_CONNECTTIMEOUT),

     mkint(CURLOPT_SSLCERT),
     mkint(CURLOPT_SSLCERTTYPE),
     mkint(CURLOPT_SSLCERTPASSWD),
     mkint(CURLOPT_SSLKEY),
     mkint(CURLOPT_SSLKEYTYPE),
     mkint(CURLOPT_SSLKEYPASSWD),
     mkint(CURLOPT_SSLENGINE),
     mkint(CURLOPT_SSLENGINE_DEFAULT),
     mkint(CURLOPT_SSLVERSION),
     mkint(CURLOPT_SSL_VERIFYPEER),
     mkint(CURLOPT_CAINFO),
     mkint(CURLOPT_CAPATH),
     mkint(CURLOPT_RANDOM_FILE),
     mkint(CURLOPT_EGDSOCKET),
     mkint(CURLOPT_SSL_VERIFYHOST),
     mkint(CURLOPT_SSL_CIPHER_LIST),
     mkint(CURLOPT_KRB4LEVEL),

     mkint(CURLINFO_EFFECTIVE_URL),
     mkint(CURLINFO_HTTP_CODE),
     mkint(CURLINFO_FILETIME),
     mkint(CURLINFO_TOTAL_TIME),
     mkint(CURLINFO_NAMELOOKUP_TIME),
     mkint(CURLINFO_CONNECT_TIME),
     mkint(CURLINFO_PRETRANSFER_TIME),
     mkint(CURLINFO_STARTTRANSFER_TIME),
     mkint(CURLINFO_REDIRECT_TIME),
     mkint(CURLINFO_REDIRECT_COUNT),
     mkint(CURLINFO_SIZE_UPLOAD),
     mkint(CURLINFO_SIZE_DOWNLOAD),
     mkint(CURLINFO_SPEED_DOWNLOAD),
     mkint(CURLINFO_SPEED_UPLOAD),
     mkint(CURLINFO_HEADER_SIZE),
     mkint(CURLINFO_REQUEST_SIZE),
     mkint(CURLINFO_SSL_VERIFYRESULT),
     mkint(CURLINFO_CONTENT_LENGTH_DOWNLOAD),
     mkint(CURLINFO_CONTENT_LENGTH_UPLOAD),
     mkint(CURLINFO_CONTENT_TYPE)

     );
}

/* interface functions */

FUNCTION(curl,curl_init,argc,argv)
{
  if (argc == 0) {
    curl_t *curl = (curl_t*)malloc(sizeof(curl_t));
    if (!curl || !(curl->handle = curl_easy_init())) return __ERROR;
    curl->cache = NULL;
    curl->code = CURLE_OK;
    curl->msg[0] = 0;
    curl_easy_setopt(curl->handle, CURLOPT_ERRORBUFFER, curl->msg);
    curl->header = curl->quote = curl->postquote = NULL;
    curl->post = NULL;
    return mkobj(type(Curl), curl);
  } else
    return __FAIL;
}

FUNCTION(curl,curl_cleanup,argc,argv)
{
  curl_t *curl;
  if (argc == 1 && isobj(argv[0], type(Curl), (void**)&curl) &&
      curl->handle) {
    free_cache(curl);
    curl_easy_cleanup(curl->handle);
    curl->handle = NULL;
    curl->cache = NULL;
    curl->code = CURLE_OK;
    curl->msg[0] = 0;
    curl->header = curl->quote = curl->postquote = NULL;
    curl->post = NULL;
    return mkvoid;
  } else
    return __FAIL;
}

static size_t write_cb(void *ptr, size_t size, size_t nmemb, expr x)
{
  bstr_t *m = malloc(sizeof(bstr_t));
  expr y, b;
  long ret;
  if (!m) return 0;
  acquire_lock();
  m->size = 0; m->v = NULL;
  if (!(b = mkobj(type(ByteStr), (void*)m))) {
    free(m);
    release_lock();
    return 0;
  }
  newref(b);
  m->size = size*nmemb; m->v = ptr;
  if (!(y = eval(mkapp(x, b))) || !isint(y, &ret))
    ret = 0;
  dispose(y);
  m->size = 0; m->v = NULL;
  freeref(b);
  release_lock();
  return ret;
}

static size_t read_cb(void *ptr, size_t size, size_t nmemb, expr x)
{
  expr y;
  bstr_t *m;
  long ret, count;
  acquire_lock();
  if (!(y = eval(mkapp(x, mkuint(size*nmemb)))) ||
      !isobj(y, type(ByteStr), (void**)&m))
    ret = 0;
  else {
    ret = m->size; count = ret;
    if (count > size*nmemb) count = size*nmemb;
    memcpy(ptr, m->v, count);
  }
  dispose(y);
  release_lock();
  return ret;
}

static size_t header_cb(void *ptr, size_t size, size_t nmemb, expr x)
{
  bstr_t *m = malloc(sizeof(bstr_t));
  expr y, b;
  long ret;
  if (!m) return -1;
  acquire_lock();
  m->size = 0; m->v = NULL;
  if (!(b = mkobj(type(ByteStr), (void*)m))) {
    free(m);
    release_lock();
    return -1;
  }
  newref(b);
  m->size = size*nmemb; m->v = ptr;
  if (!(y = eval(mkapp(x, b))) || !isint(y, &ret))
    ret = -1;
  dispose(y);
  m->size = 0; m->v = NULL;
  freeref(b);
  release_lock();
  return ret;
}

static int progress_cb(expr x, double dltotal, double dlnow,
		       double ultotal, double ulnow)
{
  expr y;
  long ret = 0;
  acquire_lock();
  if (!(y = eval(mkapp(x, mktuplel(4, mkfloat(dltotal),
				   mkfloat(dlnow),
				   mkfloat(ultotal),
				   mkfloat(ulnow))))) ||
      !isvoid(y))
    ret = -1;
  dispose(y);
  release_lock();
  return ret;
}

static int passwd_cb(expr x, char *prompt, char *buffer, int buflen)
{
  expr y;
  long ret = 0;
  char *s;
  if (buflen <= 0) return -1;
  acquire_lock();
  if (!(y = eval(mkapp(mkapp(x, mkstr(strdup(prompt))),
		       mkint(buflen)))) ||
      !isstr(y, &s))
    ret = -1;
  else {
    int l = strlen(s);
    /* FIXME: do we always need the terminating NUL here? */
    if (l >= buflen) {
      strncpy(buffer, s, buflen-1);
      buffer[buflen-1] = 0;
    } else
      strcpy(buffer, s);
  }
  dispose(y);
  release_lock();
  return ret;
}

static int debug_cb(CURL *handle, curl_infotype info, char *data, size_t size,
		    expr x)
{
  bstr_t *m = malloc(sizeof(bstr_t));
  expr b, y;
  if (!m) return 0;
  acquire_lock();
  m->size = 0; m->v = NULL;
  if (!(b = mkobj(type(ByteStr), (void*)m))) {
    free(m);
    release_lock();
    return 0;
  }
  newref(b);
  m->size = size; m->v = data;
  y = eval(mkapp(mkapp(x, mkint(info)), b));
  dispose(y);
  m->size = 0; m->v = NULL;
  freeref(b);
  release_lock();
  return 0;
}

FUNCTION(curl,curl_setopt,argc,argv)
{
  curl_t *curl;
  long option;
  if (argc == 3 && isobj(argv[0], type(Curl), (void**)&curl) &&
      curl->handle && isint(argv[1], &option) && 
      valid_option(option)) {
    if (option >= CURLOPTTYPE_FUNCTIONPOINT) {
      /* callback */
      int (*cb)();
      void *dflt = NULL;
      expr x = argv[2], y = x;
      CURLoption dataopt;
      switch (option) {
      case CURLOPT_WRITEFUNCTION:
	cb = (int(*)())write_cb;
	dataopt = CURLOPT_FILE; /* a.k.a. CURLOPT_WRITEDATA */
	dflt = stdout;
	break;
      case CURLOPT_READFUNCTION:
	cb = (int(*)())read_cb;
	dataopt = CURLOPT_INFILE; /* a.k.a. CURLOPT_READDATA */
	dflt = stdin;
	break;
      case CURLOPT_PROGRESSFUNCTION:
	cb = (int(*)())progress_cb;
	dataopt = CURLOPT_PROGRESSDATA;
	break;
#if LIBCURL_VERSION_NUM < 0x071000
      case CURLOPT_PASSWDFUNCTION:
	cb = (int(*)())passwd_cb;
	dataopt = CURLOPT_PASSWDDATA;
	break;
#endif
      case CURLOPT_HEADERFUNCTION:
	cb = (int(*)())header_cb;
	dataopt = CURLOPT_WRITEHEADER;
	dflt = stdout;
	break;
      case CURLOPT_DEBUGFUNCTION:
	cb = (int(*)())debug_cb;
	dataopt = CURLOPT_DEBUGDATA;
	if (!isvoid(x)) {
	  y = mkapp(x, argv[0]);
	  if (!y) return __ERROR;
	}
	break;
      default:
	return __FAIL;
      }
#if 0
      /* FIXME: This is supposed to reset the callbacks to the default
	 behaviour, but that doesn't work yet (libcurl segfaults), so we
	 disable it for now. */
      if (isvoid(x)) {
	cb = NULL;
	x = dflt;
      }
#endif
      curl->code = curl_easy_setopt(curl->handle, option, cb);
      if (curl->code == CURLE_OK) {
	curl->code = curl_easy_setopt(curl->handle, dataopt, x);
	if (curl->code != CURLE_OK)
	  curl_easy_setopt(curl->handle, option, NULL);
      }
      if (curl->code == CURLE_OK && !add_cache(curl, option, y)) {
	curl_easy_setopt(curl->handle, option, NULL);
	curl_easy_setopt(curl->handle, dataopt, dflt);
	return __ERROR;
      }
    } else if (option >= CURLOPTTYPE_OBJECTPOINT &&
	     /* wrong classification in curl.h? */
	     option != CURLOPT_FILETIME)
      switch (option) {
      case CURLOPT_HTTPHEADER:
      case CURLOPT_QUOTE:
      case CURLOPT_POSTQUOTE:
	{
	  /* string lists */
	  expr x = argv[2], hd, tl;
	  char *val;
	  struct curl_slist *sl = NULL;
	  while (iscons(x, &hd, &tl) && isstr(hd, &val)) {
	    struct curl_slist *sl1 = curl_slist_append(sl, val);
	    if (!sl1) {
	      if (sl) curl_slist_free_all(sl);
	      return __ERROR;
	    }
	    x = tl;
	  }
	  if (!isnil(x)) {
	    if (sl) curl_slist_free_all(sl);
	    return __FAIL;
	  }
	  curl->code = curl_easy_setopt(curl->handle, option, sl);
	  if (curl->code != CURLE_OK) {
	    if (sl) curl_slist_free_all(sl);
	  } else {
	    switch (option) {
	    case CURLOPT_HTTPHEADER:
	      if (curl->header) curl_slist_free_all(curl->header);
	      curl->header = NULL;
	      break;
	    case CURLOPT_QUOTE:
	      if (curl->quote) curl_slist_free_all(curl->quote);
	      curl->quote = NULL;
	      break;
	    case CURLOPT_POSTQUOTE:
	      if (curl->postquote) curl_slist_free_all(curl->postquote);
	      curl->postquote = NULL;
	      break;
	    }
	    if (!add_cache(curl, option, argv[2])) {
	      curl_easy_setopt(curl->handle, option, NULL);
	      if (sl) curl_slist_free_all(sl);
	      return __ERROR;
	    }
	    switch (option) {
	    case CURLOPT_HTTPHEADER:
	      curl->header = sl;
	      break;
	    case CURLOPT_QUOTE:
	      curl->quote = sl;
	      break;
	    case CURLOPT_POSTQUOTE:
	      curl->postquote = sl;
	      break;
	    }
	  }
	}
	break;
      case CURLOPT_HTTPPOST:
	{
	  /* list of HTTP POST sections */
	  expr x = argv[2], hd, tl, *xv;
	  int i, k, n;
	  struct curl_httppost *post = NULL, *last = NULL;
#ifdef FORMDEBUG
	  printf("entering\n");
#endif
	  while (iscons(x, &hd, &tl) && istuple(hd, &n, &xv) && n >= 3) {
	    char *name, *data = NULL, *datafile = NULL, *filename = NULL,
	      *content_type = NULL;
	    bstr_t *bdata = NULL;
	    long tag;
	    struct curl_forms forms[100];
#ifdef FORMDEBUG
	    printf("processing form section\n");
#endif
	    if (!isstr(xv[0], &name) || !isint(xv[1], &tag)) {
	      if (post) curl_formfree(post);
	      return __FAIL;
	    }
	    switch (tag) {
	    case CURLFORM_PTRCONTENTS:
#ifdef FORMDEBUG
	      printf("got CURLFORM_PTRCONTENTS (%s)\n", name);
#endif
	      if (!isstr(xv[2], &data) &&
		  !isobj(xv[2], type(ByteStr), (void**)&bdata) ||
		  n > 4 || n == 4 && !isstr(xv[3], &content_type)) {
		if (post) curl_formfree(post);
		return __FAIL;
	      }
	      i = 0;
	      forms[i].option = CURLFORM_PTRCONTENTS;
	      if (data) {
		forms[i].value  = data;
		i++;
		forms[i].option = CURLFORM_CONTENTSLENGTH;
		forms[i].value  = (void*)strlen(data);
	      } else {
		forms[i].value  = bdata->v;
		i++;
		forms[i].option = CURLFORM_CONTENTSLENGTH;
		forms[i].value  = (void*)bdata->size;
	      }
	      i++;
	      if (content_type) {
		forms[i].option = CURLFORM_CONTENTTYPE;
		forms[i].value  = content_type;
		i++;
	      }
	      forms[i].option = CURLFORM_END;
	      break;
	    case CURLFORM_FILECONTENT:
#ifdef FORMDEBUG
	      printf("got CURLFORM_FILECONTENT (%s)\n", name);
#endif
	      if (!isstr(xv[2], &datafile) ||
		  n > 4 || n == 4 && !isstr(xv[3], &content_type)) {
		if (post) curl_formfree(post);
		return __FAIL;
	      }
	      i = 0;
	      forms[i].option = CURLFORM_FILECONTENT;
	      forms[i].value  = datafile;
	      i++;
	      if (content_type) {
		forms[i].option = CURLFORM_CONTENTTYPE;
		forms[i].value  = content_type;
		i++;
	      }
	      forms[i].option = CURLFORM_END;
	      break;
	    default:
	      i = 0; k = 1;
	      while (k < n && isint(xv[k], &tag) &&
		     (tag == CURLFORM_FILE && isstr(xv[k+1], &datafile) ||
		      tag == CURLFORM_BUFFER &&
		      (isstr(xv[k+1], &data) ||
		       isobj(xv[k+1], type(ByteStr), (void**)&bdata)) &&
		      isstr(xv[k+2], &filename))) {
		if (i+5 >= 100) {
		  if (post) curl_formfree(post);
		  return __ERROR;
		}
#ifdef FORMDEBUG
		printf("processing part at %d (%s)\n", k, name);
#endif
		if (tag == CURLFORM_FILE) {
#ifdef FORMDEBUG
		  printf("got CURLFORM_FILE (%s)\n", name);
#endif
		  k += 2;
		  if (k < n && isstr(xv[k], &filename)) {
		    k++;
		    if (k < n && isstr(xv[k], &content_type)) k++;
		  }
		  forms[i].option = CURLFORM_FILE;
		  forms[i].value  = datafile;
		  i++;
		  if (filename) {
		    forms[i].option = CURLFORM_FILENAME;
		    forms[i].value  = filename;
		    i++;
		  }
		  if (content_type) {
		    forms[i].option = CURLFORM_CONTENTTYPE;
		    forms[i].value  = content_type;
		    i++;
		  }
		} else {
#ifdef FORMDEBUG
		  printf("got CURLFORM_BUFFER (%s)\n", name);
#endif
		  k += 3;
		  if (k < n && isstr(xv[k], &content_type)) k++;
		  forms[i].option = CURLFORM_BUFFER;
		  forms[i].value  = filename;
		  i++;
		  forms[i].option = CURLFORM_BUFFERPTR;
		  if (data) {
		    forms[i].value  = data;
		    i++;
		    forms[i].option = CURLFORM_BUFFERLENGTH;
		    forms[i].value  = (void*)strlen(data);
		  } else {
		    forms[i].value  = bdata->v;
		    i++;
		    forms[i].option = CURLFORM_BUFFERLENGTH;
		    forms[i].value  = (void*)bdata->size;
		  }
		  i++;
		  if (content_type) {
		    forms[i].option = CURLFORM_CONTENTTYPE;
		    forms[i].value  = content_type;
		    i++;
		  }
		}
		data = datafile = filename = content_type = NULL;
		bdata = NULL;
	      }
	      if (k < n) {
		if (post) curl_formfree(post);
		return __FAIL;
	      }
	      forms[i].option = CURLFORM_END;
	    }
#ifdef FORMDEBUG
	    printf("adding section (%s)\n", name);
#endif
	    if (curl_formadd(&post, &last,
			     CURLFORM_PTRNAME, name,
			     CURLFORM_ARRAY, forms,
			     CURLFORM_END)) {
	      if (post) curl_formfree(post);
	      return __ERROR;
	    }
	    x = tl;
	  }
	  if (!isnil(x)) {
	    if (post) curl_formfree(post);
	    return __FAIL;
	  }
#ifdef FORMDEBUG
	  printf("setting CURLOPT_HTTPPOST option\n");
#endif
	  curl->code = curl_easy_setopt(curl->handle, option, post);
	  if (curl->code != CURLE_OK) {
	    if (post) curl_formfree(post);
	  } else {
	    if (curl->post) curl_formfree(curl->post);
	    curl->post = NULL;
	    if (!add_cache(curl, option, argv[2])) {
	      curl_easy_setopt(curl->handle, option, NULL);
	      if (post) curl_formfree(post);
	      return __ERROR;
	    }
	    curl->post = post;
	  }
#ifdef FORMDEBUG
	  printf("done\n");
#endif
	}
	break;
      default:
	{
	  /* string value */
	  char *val;
	  if (isvoid(argv[2]))
	    val = NULL;
	  else if (!isstr(argv[2], &val))
	    return __FAIL;
	  curl->code = curl_easy_setopt(curl->handle, option, val);
	  if (curl->code == CURLE_OK && !add_cache(curl, option, argv[2])) {
	    curl_easy_setopt(curl->handle, option, NULL);
	    return __ERROR;
	  }
	}
	break;
      }
    else {
      /* Bool/Int value */
      int flag;
      long val;
      if (isbool(argv[2], &flag))
	val = flag;
      else if (!isint(argv[2], &val))
	return __FAIL;
      curl->code = curl_easy_setopt(curl->handle, option, val);
    }
    if (curl->code != CURLE_OK)
      return mkerr(curl);
    else
      return mkvoid;
  } else
    return __FAIL;
}

FUNCTION(curl,curl_perform,argc,argv)
{
  curl_t *curl;
  if (argc == 1 && isobj(argv[0], type(Curl), (void**)&curl) &&
      curl->handle) {
    release_lock();
    /* FIXME: we need to handle Ctrl-C here */
    curl->code = curl_easy_perform(curl->handle);
    acquire_lock();
    if (curl->code != CURLE_OK)
      return mkerr(curl);
    else
      return mkvoid;
  } else
    return __FAIL;
}

FUNCTION(curl,curl_getinfo,argc,argv)
{
  curl_t *curl;
  long info;
  if (argc == 2 && isobj(argv[0], type(Curl), (void**)&curl) &&
      curl->handle && isint(argv[1], &info) &&
      (info&CURLINFO_MASK) > 0 && (info&CURLINFO_MASK) < CURLINFO_LASTONE) {
    char *s;
    long i;
    double f;
    switch (info&CURLINFO_TYPEMASK) {
    case CURLINFO_STRING:
      curl->code = curl_easy_getinfo(curl->handle, info, &s);
      break;
    case CURLINFO_LONG:
      curl->code = curl_easy_getinfo(curl->handle, info, &i);
      break;
    case CURLINFO_DOUBLE:
      curl->code = curl_easy_getinfo(curl->handle, info, &f);
      break;
    default: return __FAIL;
    }
    if (curl->code != CURLE_OK)
      return mkerr(curl);
    else {
      expr x;
      switch (info&CURLINFO_TYPEMASK) {
      case CURLINFO_STRING:
	x = mkstr(strdup(s));
	break;
      case CURLINFO_LONG:
	x = mkint(i);
	break;
      case CURLINFO_DOUBLE:
	x = mkfloat(f);
	break;
      }
      return x;
    }
  } else
    return __FAIL;
}

FUNCTION(curl,curl_version,argc,argv)
{
  if (argc == 0) {
    char *s = curl_version();
    if (s)
      return mkstr(strdup(s));
    else
      return __FAIL;
  } else
    return __FAIL;
}

/* curl_free doesn't seem to be available in libcurl < 7.10.0 */
#if LIBCURL_VERSION_NUM < 0x070a00
#define curl_free free
#endif

FUNCTION(curl,curl_escape,argc,argv)
{
  char *s;
  if (argc == 1 && isstr(argv[0], &s)) {
    char *t = curl_escape(s, 0);
    if (t) {
      s = strdup(t);
      curl_free(t);
      return mkstr(s);
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(curl,curl_unescape,argc,argv)
{
  char *s;
  if (argc == 1 && isstr(argv[0], &s)) {
    char *t = curl_unescape(s, 0);
    if (t) {
      s = strdup(t);
      curl_free(t);
      return mkstr(s);
    } else
      return __FAIL;
  } else
    return __FAIL;
}


syntax highlighted by Code2HTML, v. 0.9.1