From 2c738041bdc432430d7965328d7e2a85c856246c Mon Sep 17 00:00:00 2001 From: Tilghman Lesher Date: Fri, 5 Sep 2008 19:12:03 +0000 Subject: Add the CURLOPT dialplan function, which permits setting various options for use with the CURL dialplan function. (closes issue #12920) Reported by: davevg Patches: 20080904__bug12920.diff.txt uploaded by Corydon76 (license 14) Tested by: Corydon76, davevg git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@141328 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- funcs/func_curl.c | 414 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 377 insertions(+), 37 deletions(-) (limited to 'funcs/func_curl.c') diff --git a/funcs/func_curl.c b/funcs/func_curl.c index 3c4e50bd3..f53ef7375 100644 --- a/funcs/func_curl.c +++ b/funcs/func_curl.c @@ -50,16 +50,314 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/utils.h" #include "asterisk/threadstorage.h" +#define CURLVERSION_ATLEAST(a,b,c) \ + ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c)))) + +static void curlds_free(void *data); + +static struct ast_datastore_info curl_info = { + .type = "CURL", + .destroy = curlds_free, +}; + +struct curl_settings { + AST_LIST_ENTRY(curl_settings) list; + CURLoption key; + void *value; +}; + +AST_LIST_HEAD_STATIC(global_curl_info, curl_settings); + +static void curlds_free(void *data) +{ + AST_LIST_HEAD(global_curl_info, curl_settings) *list = data; + struct curl_settings *cur; + if (!list) { + return; + } + while ((cur = AST_LIST_REMOVE_HEAD(list, list))) { + free(cur); + } + AST_LIST_HEAD_DESTROY(list); +} + +enum optiontype { + OT_BOOLEAN, + OT_INTEGER, + OT_INTEGER_MS, + OT_STRING, + OT_ENUM, +}; + +static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot) +{ + if (!strcasecmp(name, "header")) { + *key = CURLOPT_HEADER; + *ot = OT_BOOLEAN; + } else if (!strcasecmp(name, "proxy")) { + *key = CURLOPT_PROXY; + *ot = OT_STRING; + } else if (!strcasecmp(name, "proxyport")) { + *key = CURLOPT_PROXYPORT; + *ot = OT_INTEGER; + } else if (!strcasecmp(name, "proxytype")) { + *key = CURLOPT_PROXYTYPE; + *ot = OT_ENUM; + } else if (!strcasecmp(name, "dnstimeout")) { + *key = CURLOPT_DNS_CACHE_TIMEOUT; + *ot = OT_INTEGER; + } else if (!strcasecmp(name, "userpwd")) { + *key = CURLOPT_USERPWD; + *ot = OT_STRING; + } else if (!strcasecmp(name, "proxyuserpwd")) { + *key = CURLOPT_PROXYUSERPWD; + *ot = OT_STRING; + } else if (!strcasecmp(name, "maxredirs")) { + *key = CURLOPT_MAXREDIRS; + *ot = OT_INTEGER; + } else if (!strcasecmp(name, "referer")) { + *key = CURLOPT_REFERER; + *ot = OT_STRING; + } else if (!strcasecmp(name, "useragent")) { + *key = CURLOPT_USERAGENT; + *ot = OT_STRING; + } else if (!strcasecmp(name, "cookie")) { + *key = CURLOPT_COOKIE; + *ot = OT_STRING; + } else if (!strcasecmp(name, "ftptimeout")) { + *key = CURLOPT_FTP_RESPONSE_TIMEOUT; + *ot = OT_INTEGER; + } else if (!strcasecmp(name, "httptimeout")) { +#if CURLVERSION_ATLEAST(7,16,2) + *key = CURLOPT_TIMEOUT_MS; + *ot = OT_INTEGER_MS; +#else + *key = CURLOPT_TIMEOUT; + *ot = OT_INTEGER; +#endif + } else if (!strcasecmp(name, "conntimeout")) { +#if CURLVERSION_ATLEAST(7,16,2) + *key = CURLOPT_CONNECTTIMEOUT_MS; + *ot = OT_INTEGER_MS; +#else + *key = CURLOPT_CONNECTTIMEOUT; + *ot = OT_INTEGER; +#endif + } else if (!strcasecmp(name, "ftptext")) { + *key = CURLOPT_TRANSFERTEXT; + *ot = OT_BOOLEAN; + } else { + return -1; + } + return 0; +} + +static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value) +{ + struct ast_datastore *store; + AST_LIST_HEAD(global_curl_info, curl_settings) *list; + struct curl_settings *cur, *new = NULL; + CURLoption key; + enum optiontype ot; + + if (chan) { + if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) { + /* Create a new datastore */ + if (!(store = ast_datastore_alloc(&curl_info, NULL))) { + ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n"); + return -1; + } + + if (!(list = ast_calloc(1, sizeof(*list)))) { + ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n"); + ast_datastore_free(store); + } + + store->data = list; + AST_LIST_HEAD_INIT(list); + ast_channel_datastore_add(chan, store); + } else { + list = store->data; + } + } else { + /* Populate the global structure */ + list = (struct global_curl_info *)&global_curl_info; + } + + if (!parse_curlopt_key(name, &key, &ot)) { + if (ot == OT_BOOLEAN) { + if ((new = ast_calloc(1, sizeof(*new)))) { + new->value = (void *)ast_true(value); + } + } else if (ot == OT_INTEGER) { + long tmp = atol(value); + if ((new = ast_calloc(1, sizeof(*new)))) { + new->value = (void *)tmp; + } + } else if (ot == OT_INTEGER_MS) { + long tmp = atof(value) * 1000.0; + if ((new = ast_calloc(1, sizeof(*new)))) { + new->value = (void *)tmp; + } + } else if (ot == OT_STRING) { + if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) { + new->value = (char *)new + sizeof(*new); + strcpy(new->value, value); + } + } else if (ot == OT_ENUM) { + if (key == CURLOPT_PROXYTYPE) { + long ptype = +#if CURLVERSION_ATLEAST(7,10,0) + CURLPROXY_HTTP; +#else + CURLPROXY_SOCKS5; +#endif + if (0) { +#if CURLVERSION_ATLEAST(7,15,2) + } else if (!strcasecmp(value, "socks4")) { + ptype = CURLPROXY_SOCKS4; +#endif +#if CURLVERSION_ATLEAST(7,18,0) + } else if (!strcasecmp(value, "socks4a")) { + ptype = CURLPROXY_SOCKS4A; +#endif +#if CURLVERSION_ATLEAST(7,18,0) + } else if (!strcasecmp(value, "socks5")) { + ptype = CURLPROXY_SOCKS5; +#endif +#if CURLVERSION_ATLEAST(7,18,0) + } else if (!strncasecmp(value, "socks5", 6)) { + ptype = CURLPROXY_SOCKS5_HOSTNAME; +#endif + } + + if ((new = ast_calloc(1, sizeof(*new)))) { + new->value = (void *)ptype; + } + } else { + /* Highly unlikely */ + goto yuck; + } + } + + /* Memory allocation error */ + if (!new) { + return -1; + } + + new->key = key; + } else { +yuck: + ast_log(LOG_ERROR, "Unrecognized option: %s\n", name); + return -1; + } + + /* Remove any existing entry */ + AST_LIST_LOCK(list); + AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) { + if (cur->key == new->key) { + AST_LIST_REMOVE_CURRENT(list); + free(cur); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + + /* Insert new entry */ + ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value); + AST_LIST_INSERT_TAIL(list, new, list); + AST_LIST_UNLOCK(list); + + return 0; +} + +static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct ast_datastore *store; + AST_LIST_HEAD(global_curl_info, curl_settings) *list[2] = { (struct global_curl_info *)&global_curl_info, NULL }; + struct curl_settings *cur; + CURLoption key; + enum optiontype ot; + int i; + + if (parse_curlopt_key(data, &key, &ot)) { + ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data); + return -1; + } + + if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) { + list[0] = store->data; + list[1] = (struct global_curl_info *)&global_curl_info; + } + + for (i = 0; i < 2; i++) { + if (!list[i]) { + break; + } + AST_LIST_LOCK(list[i]); + AST_LIST_TRAVERSE(list[i], cur, list) { + if (cur->key == key) { + if (ot == OT_BOOLEAN || ot == OT_INTEGER) { + snprintf(buf, len, "%ld", (long)cur->value); + } else if (ot == OT_INTEGER_MS) { + if ((long)cur->value % 1000 == 0) { + snprintf(buf, len, "%ld", (long)cur->value / 1000); + } else { + snprintf(buf, len, "%.3f", (double)((long)cur->value) / 1000.0); + } + } else if (ot == OT_STRING) { + ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value); + ast_copy_string(buf, cur->value, len); + } else if (key == CURLOPT_PROXYTYPE) { + if (0) { +#if CURLVERSION_ATLEAST(7,15,2) + } else if ((long)cur->value == CURLPROXY_SOCKS4) { + ast_copy_string(buf, "socks4", len); +#endif +#if CURLVERSION_ATLEAST(7,18,0) + } else if ((long)cur->value == CURLPROXY_SOCKS4A) { + ast_copy_string(buf, "socks4a", len); +#endif + } else if ((long)cur->value == CURLPROXY_SOCKS5) { + ast_copy_string(buf, "socks5", len); +#if CURLVERSION_ATLEAST(7,18,0) + } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) { + ast_copy_string(buf, "socks5hostname", len); +#endif +#if CURLVERSION_ATLEAST(7,10,0) + } else if ((long)cur->value == CURLPROXY_HTTP) { + ast_copy_string(buf, "http", len); +#endif + } else { + ast_copy_string(buf, "unknown", len); + } + } + break; + } + } + AST_LIST_UNLOCK(list[i]); + if (cur) { + break; + } + } + + return cur ? 0 : -1; +} + static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) { register int realsize = size * nmemb; - struct ast_str **str = (struct ast_str **)data; + struct ast_str **pstr = (struct ast_str **)data; + + ast_debug(3, "Called with data=%p, str=%p, realsize=%d, len=%d, used=%d\n", data, *pstr, realsize, (*pstr)->len, (*pstr)->used); - if (ast_str_make_space(str, (*str)->used + realsize + 1) == 0) { - memcpy(&(*str)->str[(*str)->used], ptr, realsize); - (*str)->used += realsize; + if (ast_str_make_space(pstr, (((*pstr)->used + realsize + 1) / 512 + 1) * 512 + 470) == 0) { + memcpy(&((*pstr)->str[(*pstr)->used]), ptr, realsize); + (*pstr)->used += realsize; } + ast_debug(3, "Now, len=%d, used=%d\n", (*pstr)->len, (*pstr)->used); + return realsize; } @@ -91,29 +389,6 @@ static void curl_instance_cleanup(void *data) AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup); -static int curl_internal(struct ast_str **chunk, char *url, char *post) -{ - CURL **curl; - - if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) - return -1; - - curl_easy_setopt(*curl, CURLOPT_URL, url); - curl_easy_setopt(*curl, CURLOPT_WRITEDATA, (void *) chunk); - - if (post) { - curl_easy_setopt(*curl, CURLOPT_POST, 1); - curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, post); - } - - curl_easy_perform(*curl); - - if (post) - curl_easy_setopt(*curl, CURLOPT_POST, 0); - - return 0; -} - static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len) { struct ast_str *str = ast_str_create(16); @@ -121,6 +396,10 @@ static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, AST_APP_ARG(url); AST_APP_ARG(postdata); ); + CURL **curl; + struct curl_settings *cur; + struct ast_datastore *store = NULL; + AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL; *buf = '\0'; @@ -132,20 +411,54 @@ static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, AST_STANDARD_APP_ARGS(args, info); - if (chan) + if (chan) { ast_autoservice_start(chan); + } - if (!curl_internal(&str, args.url, args.postdata)) { - if (str->used) { - str->str[str->used] = '\0'; - if (str->str[str->used - 1] == '\n') { - str->str[str->used - 1] = '\0'; - } + if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) { + ast_log(LOG_ERROR, "Cannot allocate curl structure\n"); + return -1; + } + + AST_LIST_LOCK(&global_curl_info); + AST_LIST_TRAVERSE(&global_curl_info, cur, list) { + curl_easy_setopt(*curl, cur->key, cur->value); + } - ast_copy_string(buf, str->str, len); + if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) { + list = store->data; + AST_LIST_LOCK(list); + AST_LIST_TRAVERSE(list, cur, list) { + curl_easy_setopt(*curl, cur->key, cur->value); } - } else { - ast_log(LOG_ERROR, "Cannot allocate curl structure\n"); + } + + curl_easy_setopt(*curl, CURLOPT_URL, args.url); + curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str); + + if (args.postdata) { + curl_easy_setopt(*curl, CURLOPT_POST, 1); + curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata); + } + + curl_easy_perform(*curl); + + if (store) { + AST_LIST_UNLOCK(list); + } + AST_LIST_UNLOCK(&global_curl_info); + + if (args.postdata) { + curl_easy_setopt(*curl, CURLOPT_POST, 0); + } + + if (str->used) { + str->str[str->used] = '\0'; + if (str->str[str->used - 1] == '\n') { + str->str[str->used - 1] = '\0'; + } + + ast_copy_string(buf, str->str, len); } ast_free(str); @@ -165,11 +478,37 @@ struct ast_custom_function acf_curl = { .read = acf_curl_exec, }; +struct ast_custom_function acf_curlopt = { + .name = "CURLOPT", + .synopsis = "Set options for use with the CURL() function", + .syntax = "CURLOPT(