Thu Apr 28 2011 17:13:33

Asterisk developer's documentation


func_curl.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C)  2004 - 2006, Tilghman Lesher
00005  *
00006  * Tilghman Lesher <curl-20050919@the-tilghman.com>
00007  * and Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option)
00008  *
00009  * app_curl.c is distributed with no restrictions on usage or
00010  * redistribution.
00011  *
00012  * See http://www.asterisk.org for more information about
00013  * the Asterisk project. Please do not directly contact
00014  * any of the maintainers of this project for assistance;
00015  * the project provides a web site, mailing lists and IRC
00016  * channels for your use.
00017  *
00018  */
00019 
00020 /*! \file
00021  * 
00022  * \brief Curl - Load a URL
00023  *
00024  * \author Tilghman Lesher <curl-20050919@the-tilghman.com>
00025  *
00026  * \note Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option) 
00027  *
00028  * \extref Depends on the CURL library  - http://curl.haxx.se/
00029  * 
00030  * \ingroup functions
00031  */
00032  
00033 /*** MODULEINFO
00034    <depend>curl</depend>
00035  ***/
00036 
00037 #include "asterisk.h"
00038 
00039 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 294988 $")
00040 
00041 #include <curl/curl.h>
00042 
00043 #include "asterisk/lock.h"
00044 #include "asterisk/file.h"
00045 #include "asterisk/channel.h"
00046 #include "asterisk/pbx.h"
00047 #include "asterisk/cli.h"
00048 #include "asterisk/module.h"
00049 #include "asterisk/app.h"
00050 #include "asterisk/utils.h"
00051 #include "asterisk/threadstorage.h"
00052 
00053 #define CURLVERSION_ATLEAST(a,b,c) \
00054    ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
00055 
00056 #define CURLOPT_SPECIAL_HASHCOMPAT -500
00057 
00058 static void curlds_free(void *data);
00059 
00060 static struct ast_datastore_info curl_info = {
00061    .type = "CURL",
00062    .destroy = curlds_free,
00063 };
00064 
00065 struct curl_settings {
00066    AST_LIST_ENTRY(curl_settings) list;
00067    CURLoption key;
00068    void *value;
00069 };
00070 
00071 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
00072 
00073 static void curlds_free(void *data)
00074 {
00075    AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
00076    struct curl_settings *setting;
00077    if (!list) {
00078       return;
00079    }
00080    while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
00081       free(setting);
00082    }
00083    AST_LIST_HEAD_DESTROY(list);
00084 }
00085 
00086 enum optiontype {
00087    OT_BOOLEAN,
00088    OT_INTEGER,
00089    OT_INTEGER_MS,
00090    OT_STRING,
00091    OT_ENUM,
00092 };
00093 
00094 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
00095 {
00096    if (!strcasecmp(name, "header")) {
00097       *key = CURLOPT_HEADER;
00098       *ot = OT_BOOLEAN;
00099    } else if (!strcasecmp(name, "proxy")) {
00100       *key = CURLOPT_PROXY;
00101       *ot = OT_STRING;
00102    } else if (!strcasecmp(name, "proxyport")) {
00103       *key = CURLOPT_PROXYPORT;
00104       *ot = OT_INTEGER;
00105    } else if (!strcasecmp(name, "proxytype")) {
00106       *key = CURLOPT_PROXYTYPE;
00107       *ot = OT_ENUM;
00108    } else if (!strcasecmp(name, "dnstimeout")) {
00109       *key = CURLOPT_DNS_CACHE_TIMEOUT;
00110       *ot = OT_INTEGER;
00111    } else if (!strcasecmp(name, "userpwd")) {
00112       *key = CURLOPT_USERPWD;
00113       *ot = OT_STRING;
00114    } else if (!strcasecmp(name, "proxyuserpwd")) {
00115       *key = CURLOPT_PROXYUSERPWD;
00116       *ot = OT_STRING;
00117    } else if (!strcasecmp(name, "maxredirs")) {
00118       *key = CURLOPT_MAXREDIRS;
00119       *ot = OT_INTEGER;
00120    } else if (!strcasecmp(name, "referer")) {
00121       *key = CURLOPT_REFERER;
00122       *ot = OT_STRING;
00123    } else if (!strcasecmp(name, "useragent")) {
00124       *key = CURLOPT_USERAGENT;
00125       *ot = OT_STRING;
00126    } else if (!strcasecmp(name, "cookie")) {
00127       *key = CURLOPT_COOKIE;
00128       *ot = OT_STRING;
00129    } else if (!strcasecmp(name, "ftptimeout")) {
00130       *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
00131       *ot = OT_INTEGER;
00132    } else if (!strcasecmp(name, "httptimeout")) {
00133 #if CURLVERSION_ATLEAST(7,16,2)
00134       *key = CURLOPT_TIMEOUT_MS;
00135       *ot = OT_INTEGER_MS;
00136 #else
00137       *key = CURLOPT_TIMEOUT;
00138       *ot = OT_INTEGER;
00139 #endif
00140    } else if (!strcasecmp(name, "conntimeout")) {
00141 #if CURLVERSION_ATLEAST(7,16,2)
00142       *key = CURLOPT_CONNECTTIMEOUT_MS;
00143       *ot = OT_INTEGER_MS;
00144 #else
00145       *key = CURLOPT_CONNECTTIMEOUT;
00146       *ot = OT_INTEGER;
00147 #endif
00148    } else if (!strcasecmp(name, "ftptext")) {
00149       *key = CURLOPT_TRANSFERTEXT;
00150       *ot = OT_BOOLEAN;
00151    } else if (!strcasecmp(name, "ssl_verifypeer")) {
00152       *key = CURLOPT_SSL_VERIFYPEER;
00153       *ot = OT_BOOLEAN;
00154    } else if (!strcasecmp(name, "hashcompat")) {
00155       *key = CURLOPT_SPECIAL_HASHCOMPAT;
00156       *ot = OT_BOOLEAN;
00157    } else {
00158       return -1;
00159    }
00160    return 0;
00161 }
00162 
00163 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
00164 {
00165    struct ast_datastore *store;
00166    struct global_curl_info *list;
00167    struct curl_settings *cur, *new = NULL;
00168    CURLoption key;
00169    enum optiontype ot;
00170 
00171    if (chan) {
00172       if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00173          /* Create a new datastore */
00174          if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
00175             ast_log(LOG_ERROR, "Unable to allocate new datastore.  Cannot set any CURL options\n");
00176             return -1;
00177          }
00178 
00179          if (!(list = ast_calloc(1, sizeof(*list)))) {
00180             ast_log(LOG_ERROR, "Unable to allocate list head.  Cannot set any CURL options\n");
00181             ast_datastore_free(store);
00182          }
00183 
00184          store->data = list;
00185          AST_LIST_HEAD_INIT(list);
00186          ast_channel_datastore_add(chan, store);
00187       } else {
00188          list = store->data;
00189       }
00190    } else {
00191       /* Populate the global structure */
00192       list = &global_curl_info;
00193    }
00194 
00195    if (!parse_curlopt_key(name, &key, &ot)) {
00196       if (ot == OT_BOOLEAN) {
00197          if ((new = ast_calloc(1, sizeof(*new)))) {
00198             new->value = (void *)((long) ast_true(value));
00199          }
00200       } else if (ot == OT_INTEGER) {
00201          long tmp = atol(value);
00202          if ((new = ast_calloc(1, sizeof(*new)))) {
00203             new->value = (void *)tmp;
00204          }
00205       } else if (ot == OT_INTEGER_MS) {
00206          long tmp = atof(value) * 1000.0;
00207          if ((new = ast_calloc(1, sizeof(*new)))) {
00208             new->value = (void *)tmp;
00209          }
00210       } else if (ot == OT_STRING) {
00211          if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
00212             new->value = (char *)new + sizeof(*new);
00213             strcpy(new->value, value);
00214          }
00215       } else if (ot == OT_ENUM) {
00216          if (key == CURLOPT_PROXYTYPE) {
00217             long ptype =
00218 #if CURLVERSION_ATLEAST(7,10,0)
00219                CURLPROXY_HTTP;
00220 #else
00221                CURLPROXY_SOCKS5;
00222 #endif
00223             if (0) {
00224 #if CURLVERSION_ATLEAST(7,15,2)
00225             } else if (!strcasecmp(value, "socks4")) {
00226                ptype = CURLPROXY_SOCKS4;
00227 #endif
00228 #if CURLVERSION_ATLEAST(7,18,0)
00229             } else if (!strcasecmp(value, "socks4a")) {
00230                ptype = CURLPROXY_SOCKS4A;
00231 #endif
00232 #if CURLVERSION_ATLEAST(7,18,0)
00233             } else if (!strcasecmp(value, "socks5")) {
00234                ptype = CURLPROXY_SOCKS5;
00235 #endif
00236 #if CURLVERSION_ATLEAST(7,18,0)
00237             } else if (!strncasecmp(value, "socks5", 6)) {
00238                ptype = CURLPROXY_SOCKS5_HOSTNAME;
00239 #endif
00240             }
00241 
00242             if ((new = ast_calloc(1, sizeof(*new)))) {
00243                new->value = (void *)ptype;
00244             }
00245          } else {
00246             /* Highly unlikely */
00247             goto yuck;
00248          }
00249       }
00250 
00251       /* Memory allocation error */
00252       if (!new) {
00253          return -1;
00254       }
00255 
00256       new->key = key;
00257    } else {
00258 yuck:
00259       ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
00260       return -1;
00261    }
00262 
00263    /* Remove any existing entry */
00264    AST_LIST_LOCK(list);
00265    AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
00266       if (cur->key == new->key) {
00267          AST_LIST_REMOVE_CURRENT(list);
00268          free(cur);
00269          break;
00270       }
00271    }
00272    AST_LIST_TRAVERSE_SAFE_END
00273 
00274    /* Insert new entry */
00275    ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
00276    AST_LIST_INSERT_TAIL(list, new, list);
00277    AST_LIST_UNLOCK(list);
00278 
00279    return 0;
00280 }
00281 
00282 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00283 {
00284    struct ast_datastore *store;
00285    struct global_curl_info *list[2] = { &global_curl_info, NULL };
00286    struct curl_settings *cur = NULL;
00287    CURLoption key;
00288    enum optiontype ot;
00289    int i;
00290 
00291    if (parse_curlopt_key(data, &key, &ot)) {
00292       ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
00293       return -1;
00294    }
00295 
00296    if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00297       list[0] = store->data;
00298       list[1] = &global_curl_info;
00299    }
00300 
00301    for (i = 0; i < 2; i++) {
00302       if (!list[i]) {
00303          break;
00304       }
00305       AST_LIST_LOCK(list[i]);
00306       AST_LIST_TRAVERSE(list[i], cur, list) {
00307          if (cur->key == key) {
00308             if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
00309                snprintf(buf, len, "%ld", (long)cur->value);
00310             } else if (ot == OT_INTEGER_MS) {
00311                if ((long)cur->value % 1000 == 0) {
00312                   snprintf(buf, len, "%ld", (long)cur->value / 1000);
00313                } else {
00314                   snprintf(buf, len, "%.3f", (double)((long)cur->value) / 1000.0);
00315                }
00316             } else if (ot == OT_STRING) {
00317                ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
00318                ast_copy_string(buf, cur->value, len);
00319             } else if (key == CURLOPT_PROXYTYPE) {
00320                if (0) {
00321 #if CURLVERSION_ATLEAST(7,15,2)
00322                } else if ((long)cur->value == CURLPROXY_SOCKS4) {
00323                   ast_copy_string(buf, "socks4", len);
00324 #endif
00325 #if CURLVERSION_ATLEAST(7,18,0)
00326                } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
00327                   ast_copy_string(buf, "socks4a", len);
00328 #endif
00329                } else if ((long)cur->value == CURLPROXY_SOCKS5) {
00330                   ast_copy_string(buf, "socks5", len);
00331 #if CURLVERSION_ATLEAST(7,18,0)
00332                } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
00333                   ast_copy_string(buf, "socks5hostname", len);
00334 #endif
00335 #if CURLVERSION_ATLEAST(7,10,0)
00336                } else if ((long)cur->value == CURLPROXY_HTTP) {
00337                   ast_copy_string(buf, "http", len);
00338 #endif
00339                } else {
00340                   ast_copy_string(buf, "unknown", len);
00341                }
00342             }
00343             break;
00344          }
00345       }
00346       AST_LIST_UNLOCK(list[i]);
00347       if (cur) {
00348          break;
00349       }
00350    }
00351 
00352    return cur ? 0 : -1;
00353 }
00354 
00355 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
00356 {
00357    register int realsize = size * nmemb;
00358    struct ast_str **pstr = (struct ast_str **)data;
00359 
00360    ast_debug(3, "Called with data=%p, str=%p, realsize=%d, len=%zu, used=%zu\n", data, *pstr, realsize, ast_str_size(*pstr), ast_str_strlen(*pstr));
00361 
00362    ast_str_append_substr(pstr, 0, ptr, realsize);
00363 
00364    ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
00365 
00366    return realsize;
00367 }
00368 
00369 static const char *global_useragent = "asterisk-libcurl-agent/1.0";
00370 
00371 static int curl_instance_init(void *data)
00372 {
00373    CURL **curl = data;
00374 
00375    if (!(*curl = curl_easy_init()))
00376       return -1;
00377 
00378    curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
00379    curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
00380    curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
00381    curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
00382 
00383    return 0;
00384 }
00385 
00386 static void curl_instance_cleanup(void *data)
00387 {
00388    CURL **curl = data;
00389 
00390    curl_easy_cleanup(*curl);
00391 
00392    ast_free(data);
00393 }
00394 
00395 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
00396 
00397 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
00398 {
00399    struct ast_str *str = ast_str_create(16);
00400    int ret = -1;
00401    AST_DECLARE_APP_ARGS(args,
00402       AST_APP_ARG(url);
00403       AST_APP_ARG(postdata);
00404    );
00405    CURL **curl;
00406    struct curl_settings *cur;
00407    struct ast_datastore *store = NULL;
00408    int hashcompat = 0;
00409    AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
00410 
00411    *buf = '\0';
00412    
00413    if (ast_strlen_zero(info)) {
00414       ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
00415       ast_free(str);
00416       return -1;
00417    }
00418 
00419    AST_STANDARD_APP_ARGS(args, info);  
00420 
00421    if (chan) {
00422       ast_autoservice_start(chan);
00423    }
00424 
00425    if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
00426       ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
00427       return -1;
00428    }
00429 
00430    AST_LIST_LOCK(&global_curl_info);
00431    AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
00432       if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00433          hashcompat = (cur->value != NULL) ? 1 : 0;
00434       } else {
00435          curl_easy_setopt(*curl, cur->key, cur->value);
00436       }
00437    }
00438 
00439    if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00440       list = store->data;
00441       AST_LIST_LOCK(list);
00442       AST_LIST_TRAVERSE(list, cur, list) {
00443          if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00444             hashcompat = (cur->value != NULL) ? 1 : 0;
00445          } else {
00446             curl_easy_setopt(*curl, cur->key, cur->value);
00447          }
00448       }
00449    }
00450 
00451    curl_easy_setopt(*curl, CURLOPT_URL, args.url);
00452    curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
00453 
00454    if (args.postdata) {
00455       curl_easy_setopt(*curl, CURLOPT_POST, 1);
00456       curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
00457    }
00458 
00459    curl_easy_perform(*curl);
00460 
00461    if (store) {
00462       AST_LIST_UNLOCK(list);
00463    }
00464    AST_LIST_UNLOCK(&global_curl_info);
00465 
00466    if (args.postdata) {
00467       curl_easy_setopt(*curl, CURLOPT_POST, 0);
00468    }
00469 
00470    if (ast_str_strlen(str)) {
00471       ast_str_trim_blanks(str);
00472 
00473       ast_debug(3, "str='%s'\n", ast_str_buffer(str));
00474       if (hashcompat) {
00475          char *remainder = ast_str_buffer(str);
00476          char *piece;
00477          struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
00478          struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
00479          int rowcount = 0;
00480          while (fields && values && (piece = strsep(&remainder, "&"))) {
00481             char *name = strsep(&piece, "=");
00482             if (!piece) {
00483                piece = "";
00484             }
00485             ast_uri_decode(piece);
00486             ast_uri_decode(name);
00487             ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", name);
00488             ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", piece);
00489             rowcount++;
00490          }
00491          pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
00492          ast_copy_string(buf, ast_str_buffer(values), len);
00493          ast_free(fields);
00494          ast_free(values);
00495       } else {
00496          ast_copy_string(buf, ast_str_buffer(str), len);
00497       }
00498       ret = 0;
00499    }
00500    ast_free(str);
00501 
00502    if (chan)
00503       ast_autoservice_stop(chan);
00504    
00505    return ret;
00506 }
00507 
00508 struct ast_custom_function acf_curl = {
00509    .name = "CURL",
00510    .synopsis = "Retrieves the contents of a URL",
00511    .syntax = "CURL(url[,post-data])",
00512    .desc =
00513    "  url       - URL to retrieve\n"
00514    "  post-data - Optional data to send as a POST (GET is default action)\n",
00515    .read = acf_curl_exec,
00516 };
00517 
00518 struct ast_custom_function acf_curlopt = {
00519    .name = "CURLOPT",
00520    .synopsis = "Set options for use with the CURL() function",
00521    .syntax = "CURLOPT(<option>)",
00522    .desc =
00523 "  cookie         - Send cookie with request [none]\n"
00524 "  conntimeout    - Number of seconds to wait for connection\n"
00525 "  dnstimeout     - Number of seconds to wait for DNS response\n"
00526 "  ftptext        - For FTP, force a text transfer (boolean)\n"
00527 "  ftptimeout     - For FTP, the server response timeout\n"
00528 "  header         - Retrieve header information (boolean)\n"
00529 "  httptimeout    - Number of seconds to wait for HTTP response\n"
00530 "  maxredirs      - Maximum number of redirects to follow\n"
00531 "  proxy          - Hostname or IP to use as a proxy\n"
00532 "  proxytype      - http, socks4, or socks5\n"
00533 "  proxyport      - port number of the proxy\n"
00534 "  proxyuserpwd   - A <user>:<pass> to use for authentication\n"
00535 "  referer        - Referer URL to use for the request\n"
00536 "  useragent      - UserAgent string to use\n"
00537 "  userpwd        - A <user>:<pass> to use for authentication\n"
00538 "  ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
00539 "  hashcompat     - Result data will be compatible for use with HASH()\n"
00540 "",
00541    .read = acf_curlopt_read,
00542    .write = acf_curlopt_write,
00543 };
00544 
00545 static int unload_module(void)
00546 {
00547    int res;
00548 
00549    res = ast_custom_function_unregister(&acf_curl);
00550    res |= ast_custom_function_unregister(&acf_curlopt);
00551 
00552    return res;
00553 }
00554 
00555 static int load_module(void)
00556 {
00557    int res;
00558 
00559    if (!ast_module_check("res_curl.so")) {
00560       if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
00561          ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
00562          return AST_MODULE_LOAD_DECLINE;
00563       }
00564    }
00565 
00566    res = ast_custom_function_register(&acf_curl);
00567    res |= ast_custom_function_register(&acf_curlopt);
00568 
00569    return res;
00570 }
00571 
00572 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Load external URL");
00573