/* $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 #include #include /* check for standard C headers */ #if STDC_HEADERS # include # include #else # ifndef HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr (), *strrchr (); #endif #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef DMALLOC #include #endif #include #include 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; }