Sat Apr 26 2014 22:01:36

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    <support_level>core</support_level>
00036  ***/
00037 
00038 #include "asterisk.h"
00039 
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 383461 $")
00041 
00042 #include <curl/curl.h>
00043 
00044 #include "asterisk/lock.h"
00045 #include "asterisk/file.h"
00046 #include "asterisk/channel.h"
00047 #include "asterisk/pbx.h"
00048 #include "asterisk/cli.h"
00049 #include "asterisk/module.h"
00050 #include "asterisk/app.h"
00051 #include "asterisk/utils.h"
00052 #include "asterisk/threadstorage.h"
00053 
00054 /*** DOCUMENTATION
00055    <function name="CURL" language="en_US">
00056       <synopsis>
00057          Retrieve content from a remote web or ftp server
00058       </synopsis>
00059       <syntax>
00060          <parameter name="url" required="true" />
00061          <parameter name="post-data">
00062             <para>If specified, an <literal>HTTP POST</literal> will be
00063             performed with the content of
00064             <replaceable>post-data</replaceable>, instead of an
00065             <literal>HTTP GET</literal> (default).</para>
00066          </parameter>
00067       </syntax>
00068       <description />
00069       <see-also>
00070          <ref type="function">CURLOPT</ref>
00071       </see-also>
00072    </function>
00073    <function name="CURLOPT" language="en_US">
00074       <synopsis>
00075          Sets various options for future invocations of CURL.
00076       </synopsis>
00077       <syntax>
00078          <parameter name="key" required="yes">
00079             <enumlist>
00080                <enum name="cookie">
00081                   <para>A cookie to send with the request.  Multiple
00082                   cookies are supported.</para>
00083                </enum>
00084                <enum name="conntimeout">
00085                   <para>Number of seconds to wait for a connection to succeed</para>
00086                </enum>
00087                <enum name="dnstimeout">
00088                   <para>Number of seconds to wait for DNS to be resolved</para>
00089                </enum>
00090                <enum name="ftptext">
00091                   <para>For FTP URIs, force a text transfer (boolean)</para>
00092                </enum>
00093                <enum name="ftptimeout">
00094                   <para>For FTP URIs, number of seconds to wait for a
00095                   server response</para>
00096                </enum>
00097                <enum name="header">
00098                   <para>Include header information in the result
00099                   (boolean)</para>
00100                </enum>
00101                <enum name="httptimeout">
00102                   <para>For HTTP(S) URIs, number of seconds to wait for a
00103                   server response</para>
00104                </enum>
00105                <enum name="maxredirs">
00106                   <para>Maximum number of redirects to follow</para>
00107                </enum>
00108                <enum name="proxy">
00109                   <para>Hostname or IP address to use as a proxy server</para>
00110                </enum>
00111                <enum name="proxytype">
00112                   <para>Type of <literal>proxy</literal></para>
00113                   <enumlist>
00114                      <enum name="http" />
00115                      <enum name="socks4" />
00116                      <enum name="socks5" />
00117                   </enumlist>
00118                </enum>
00119                <enum name="proxyport">
00120                   <para>Port number of the <literal>proxy</literal></para>
00121                </enum>
00122                <enum name="proxyuserpwd">
00123                   <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
00124                   combination to use for authenticating requests through a
00125                   <literal>proxy</literal></para>
00126                </enum>
00127                <enum name="referer">
00128                   <para>Referer URL to use for the request</para>
00129                </enum>
00130                <enum name="useragent">
00131                   <para>UserAgent string to use for the request</para>
00132                </enum>
00133                <enum name="userpwd">
00134                   <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
00135                   to use for authentication when the server response to
00136                   an initial request indicates a 401 status code.</para>
00137                </enum>
00138                <enum name="ssl_verifypeer">
00139                   <para>Whether to verify the server certificate against
00140                   a list of known root certificate authorities (boolean).</para>
00141                </enum>
00142                <enum name="hashcompat">
00143                   <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
00144                   format, reformat the response such that it can be used
00145                   by the <literal>HASH</literal> function.</para>
00146                   <enumlist>
00147                      <enum name="yes" />
00148                      <enum name="no" />
00149                      <enum name="legacy">
00150                         <para>Also translate <literal>+</literal> to the
00151                         space character, in violation of current RFC
00152                         standards.</para>
00153                      </enum>
00154                   </enumlist>
00155                </enum>
00156             </enumlist>
00157          </parameter>
00158       </syntax>
00159       <description>
00160          <para>Options may be set globally or per channel.  Per-channel
00161          settings will override global settings.</para>
00162       </description>
00163       <see-also>
00164          <ref type="function">CURL</ref>
00165          <ref type="function">HASH</ref>
00166       </see-also>
00167    </function>
00168  ***/
00169 
00170 #define CURLVERSION_ATLEAST(a,b,c) \
00171    ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
00172 
00173 #define CURLOPT_SPECIAL_HASHCOMPAT -500
00174 
00175 static void curlds_free(void *data);
00176 
00177 static const struct ast_datastore_info curl_info = {
00178    .type = "CURL",
00179    .destroy = curlds_free,
00180 };
00181 
00182 struct curl_settings {
00183    AST_LIST_ENTRY(curl_settings) list;
00184    CURLoption key;
00185    void *value;
00186 };
00187 
00188 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
00189 
00190 static void curlds_free(void *data)
00191 {
00192    AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
00193    struct curl_settings *setting;
00194    if (!list) {
00195       return;
00196    }
00197    while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
00198       free(setting);
00199    }
00200    AST_LIST_HEAD_DESTROY(list);
00201 }
00202 
00203 enum optiontype {
00204    OT_BOOLEAN,
00205    OT_INTEGER,
00206    OT_INTEGER_MS,
00207    OT_STRING,
00208    OT_ENUM,
00209 };
00210 
00211 enum hashcompat {
00212    HASHCOMPAT_NO = 0,
00213    HASHCOMPAT_YES,
00214    HASHCOMPAT_LEGACY,
00215 };
00216 
00217 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
00218 {
00219    if (!strcasecmp(name, "header")) {
00220       *key = CURLOPT_HEADER;
00221       *ot = OT_BOOLEAN;
00222    } else if (!strcasecmp(name, "proxy")) {
00223       *key = CURLOPT_PROXY;
00224       *ot = OT_STRING;
00225    } else if (!strcasecmp(name, "proxyport")) {
00226       *key = CURLOPT_PROXYPORT;
00227       *ot = OT_INTEGER;
00228    } else if (!strcasecmp(name, "proxytype")) {
00229       *key = CURLOPT_PROXYTYPE;
00230       *ot = OT_ENUM;
00231    } else if (!strcasecmp(name, "dnstimeout")) {
00232       *key = CURLOPT_DNS_CACHE_TIMEOUT;
00233       *ot = OT_INTEGER;
00234    } else if (!strcasecmp(name, "userpwd")) {
00235       *key = CURLOPT_USERPWD;
00236       *ot = OT_STRING;
00237    } else if (!strcasecmp(name, "proxyuserpwd")) {
00238       *key = CURLOPT_PROXYUSERPWD;
00239       *ot = OT_STRING;
00240    } else if (!strcasecmp(name, "maxredirs")) {
00241       *key = CURLOPT_MAXREDIRS;
00242       *ot = OT_INTEGER;
00243    } else if (!strcasecmp(name, "referer")) {
00244       *key = CURLOPT_REFERER;
00245       *ot = OT_STRING;
00246    } else if (!strcasecmp(name, "useragent")) {
00247       *key = CURLOPT_USERAGENT;
00248       *ot = OT_STRING;
00249    } else if (!strcasecmp(name, "cookie")) {
00250       *key = CURLOPT_COOKIE;
00251       *ot = OT_STRING;
00252    } else if (!strcasecmp(name, "ftptimeout")) {
00253       *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
00254       *ot = OT_INTEGER;
00255    } else if (!strcasecmp(name, "httptimeout")) {
00256 #if CURLVERSION_ATLEAST(7,16,2)
00257       *key = CURLOPT_TIMEOUT_MS;
00258       *ot = OT_INTEGER_MS;
00259 #else
00260       *key = CURLOPT_TIMEOUT;
00261       *ot = OT_INTEGER;
00262 #endif
00263    } else if (!strcasecmp(name, "conntimeout")) {
00264 #if CURLVERSION_ATLEAST(7,16,2)
00265       *key = CURLOPT_CONNECTTIMEOUT_MS;
00266       *ot = OT_INTEGER_MS;
00267 #else
00268       *key = CURLOPT_CONNECTTIMEOUT;
00269       *ot = OT_INTEGER;
00270 #endif
00271    } else if (!strcasecmp(name, "ftptext")) {
00272       *key = CURLOPT_TRANSFERTEXT;
00273       *ot = OT_BOOLEAN;
00274    } else if (!strcasecmp(name, "ssl_verifypeer")) {
00275       *key = CURLOPT_SSL_VERIFYPEER;
00276       *ot = OT_BOOLEAN;
00277    } else if (!strcasecmp(name, "hashcompat")) {
00278       *key = CURLOPT_SPECIAL_HASHCOMPAT;
00279       *ot = OT_ENUM;
00280    } else {
00281       return -1;
00282    }
00283    return 0;
00284 }
00285 
00286 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
00287 {
00288    struct ast_datastore *store;
00289    struct global_curl_info *list;
00290    struct curl_settings *cur, *new = NULL;
00291    CURLoption key;
00292    enum optiontype ot;
00293 
00294    if (chan) {
00295       if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00296          /* Create a new datastore */
00297          if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
00298             ast_log(LOG_ERROR, "Unable to allocate new datastore.  Cannot set any CURL options\n");
00299             return -1;
00300          }
00301 
00302          if (!(list = ast_calloc(1, sizeof(*list)))) {
00303             ast_log(LOG_ERROR, "Unable to allocate list head.  Cannot set any CURL options\n");
00304             ast_datastore_free(store);
00305             return -1;
00306          }
00307 
00308          store->data = list;
00309          AST_LIST_HEAD_INIT(list);
00310          ast_channel_datastore_add(chan, store);
00311       } else {
00312          list = store->data;
00313       }
00314    } else {
00315       /* Populate the global structure */
00316       list = &global_curl_info;
00317    }
00318 
00319    if (!parse_curlopt_key(name, &key, &ot)) {
00320       if (ot == OT_BOOLEAN) {
00321          if ((new = ast_calloc(1, sizeof(*new)))) {
00322             new->value = (void *)((long) ast_true(value));
00323          }
00324       } else if (ot == OT_INTEGER) {
00325          long tmp = atol(value);
00326          if ((new = ast_calloc(1, sizeof(*new)))) {
00327             new->value = (void *)tmp;
00328          }
00329       } else if (ot == OT_INTEGER_MS) {
00330          long tmp = atof(value) * 1000.0;
00331          if ((new = ast_calloc(1, sizeof(*new)))) {
00332             new->value = (void *)tmp;
00333          }
00334       } else if (ot == OT_STRING) {
00335          if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
00336             new->value = (char *)new + sizeof(*new);
00337             strcpy(new->value, value);
00338          }
00339       } else if (ot == OT_ENUM) {
00340          if (key == CURLOPT_PROXYTYPE) {
00341             long ptype =
00342 #if CURLVERSION_ATLEAST(7,10,0)
00343                CURLPROXY_HTTP;
00344 #else
00345                CURLPROXY_SOCKS5;
00346 #endif
00347             if (0) {
00348 #if CURLVERSION_ATLEAST(7,15,2)
00349             } else if (!strcasecmp(value, "socks4")) {
00350                ptype = CURLPROXY_SOCKS4;
00351 #endif
00352 #if CURLVERSION_ATLEAST(7,18,0)
00353             } else if (!strcasecmp(value, "socks4a")) {
00354                ptype = CURLPROXY_SOCKS4A;
00355 #endif
00356 #if CURLVERSION_ATLEAST(7,18,0)
00357             } else if (!strcasecmp(value, "socks5")) {
00358                ptype = CURLPROXY_SOCKS5;
00359 #endif
00360 #if CURLVERSION_ATLEAST(7,18,0)
00361             } else if (!strncasecmp(value, "socks5", 6)) {
00362                ptype = CURLPROXY_SOCKS5_HOSTNAME;
00363 #endif
00364             }
00365 
00366             if ((new = ast_calloc(1, sizeof(*new)))) {
00367                new->value = (void *)ptype;
00368             }
00369          } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
00370             if ((new = ast_calloc(1, sizeof(*new)))) {
00371                new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
00372             }
00373          } else {
00374             /* Highly unlikely */
00375             goto yuck;
00376          }
00377       }
00378 
00379       /* Memory allocation error */
00380       if (!new) {
00381          return -1;
00382       }
00383 
00384       new->key = key;
00385    } else {
00386 yuck:
00387       ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
00388       return -1;
00389    }
00390 
00391    /* Remove any existing entry */
00392    AST_LIST_LOCK(list);
00393    AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
00394       if (cur->key == new->key) {
00395          AST_LIST_REMOVE_CURRENT(list);
00396          free(cur);
00397          break;
00398       }
00399    }
00400    AST_LIST_TRAVERSE_SAFE_END
00401 
00402    /* Insert new entry */
00403    ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
00404    AST_LIST_INSERT_TAIL(list, new, list);
00405    AST_LIST_UNLOCK(list);
00406 
00407    return 0;
00408 }
00409 
00410 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
00411 {
00412    struct ast_datastore *store;
00413    struct global_curl_info *list[2] = { &global_curl_info, NULL };
00414    struct curl_settings *cur = NULL;
00415    CURLoption key;
00416    enum optiontype ot;
00417    int i;
00418 
00419    if (parse_curlopt_key(data, &key, &ot)) {
00420       ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
00421       return -1;
00422    }
00423 
00424    if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00425       list[0] = store->data;
00426       list[1] = &global_curl_info;
00427    }
00428 
00429    for (i = 0; i < 2; i++) {
00430       if (!list[i]) {
00431          break;
00432       }
00433       AST_LIST_LOCK(list[i]);
00434       AST_LIST_TRAVERSE(list[i], cur, list) {
00435          if (cur->key == key) {
00436             if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
00437                if (buf) {
00438                   snprintf(buf, len, "%ld", (long) cur->value);
00439                } else {
00440                   ast_str_set(bufstr, len, "%ld", (long) cur->value);
00441                }
00442             } else if (ot == OT_INTEGER_MS) {
00443                if ((long) cur->value % 1000 == 0) {
00444                   if (buf) {
00445                      snprintf(buf, len, "%ld", (long)cur->value / 1000);
00446                   } else {
00447                      ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
00448                   }
00449                } else {
00450                   if (buf) {
00451                      snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00452                   } else {
00453                      ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00454                   }
00455                }
00456             } else if (ot == OT_STRING) {
00457                ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
00458                if (buf) {
00459                   ast_copy_string(buf, cur->value, len);
00460                } else {
00461                   ast_str_set(bufstr, 0, "%s", (char *) cur->value);
00462                }
00463             } else if (key == CURLOPT_PROXYTYPE) {
00464                const char *strval = "unknown";
00465                if (0) {
00466 #if CURLVERSION_ATLEAST(7,15,2)
00467                } else if ((long)cur->value == CURLPROXY_SOCKS4) {
00468                   strval = "socks4";
00469 #endif
00470 #if CURLVERSION_ATLEAST(7,18,0)
00471                } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
00472                   strval = "socks4a";
00473 #endif
00474                } else if ((long)cur->value == CURLPROXY_SOCKS5) {
00475                   strval = "socks5";
00476 #if CURLVERSION_ATLEAST(7,18,0)
00477                } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
00478                   strval = "socks5hostname";
00479 #endif
00480 #if CURLVERSION_ATLEAST(7,10,0)
00481                } else if ((long)cur->value == CURLPROXY_HTTP) {
00482                   strval = "http";
00483 #endif
00484                }
00485                if (buf) {
00486                   ast_copy_string(buf, strval, len);
00487                } else {
00488                   ast_str_set(bufstr, 0, "%s", strval);
00489                }
00490             } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
00491                const char *strval = "unknown";
00492                if ((long) cur->value == HASHCOMPAT_LEGACY) {
00493                   strval = "legacy";
00494                } else if ((long) cur->value == HASHCOMPAT_YES) {
00495                   strval = "yes";
00496                } else if ((long) cur->value == HASHCOMPAT_NO) {
00497                   strval = "no";
00498                }
00499                if (buf) {
00500                   ast_copy_string(buf, strval, len);
00501                } else {
00502                   ast_str_set(bufstr, 0, "%s", strval);
00503                }
00504             }
00505             break;
00506          }
00507       }
00508       AST_LIST_UNLOCK(list[i]);
00509       if (cur) {
00510          break;
00511       }
00512    }
00513 
00514    return cur ? 0 : -1;
00515 }
00516 
00517 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00518 {
00519    return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
00520 }
00521 
00522 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
00523 {
00524    return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
00525 }
00526 
00527 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
00528 {
00529    register int realsize = size * nmemb;
00530    struct ast_str **pstr = (struct ast_str **)data;
00531 
00532    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));
00533 
00534    ast_str_append_substr(pstr, 0, ptr, realsize);
00535 
00536    ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
00537 
00538    return realsize;
00539 }
00540 
00541 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
00542 
00543 static int curl_instance_init(void *data)
00544 {
00545    CURL **curl = data;
00546 
00547    if (!(*curl = curl_easy_init()))
00548       return -1;
00549 
00550    curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
00551    curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
00552    curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
00553    curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
00554 
00555    return 0;
00556 }
00557 
00558 static void curl_instance_cleanup(void *data)
00559 {
00560    CURL **curl = data;
00561 
00562    curl_easy_cleanup(*curl);
00563 
00564    ast_free(data);
00565 }
00566 
00567 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
00568 AST_THREADSTORAGE(thread_escapebuf);
00569 
00570 static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
00571 {
00572    struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
00573    struct ast_str *str = ast_str_create(16);
00574    int ret = -1;
00575    AST_DECLARE_APP_ARGS(args,
00576       AST_APP_ARG(url);
00577       AST_APP_ARG(postdata);
00578    );
00579    CURL **curl;
00580    struct curl_settings *cur;
00581    struct ast_datastore *store = NULL;
00582    int hashcompat = 0;
00583    AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
00584    char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
00585 
00586    if (buf) {
00587       *buf = '\0';
00588    }
00589 
00590    if (!str) {
00591       return -1;
00592    }
00593 
00594    if (!escapebuf) {
00595       ast_free(str);
00596       return -1;
00597    }
00598 
00599    if (ast_strlen_zero(info)) {
00600       ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
00601       ast_free(str);
00602       return -1;
00603    }
00604 
00605    AST_STANDARD_APP_ARGS(args, info);
00606 
00607    if (chan) {
00608       ast_autoservice_start(chan);
00609    }
00610 
00611    if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
00612       ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
00613       ast_free(str);
00614       return -1;
00615    }
00616 
00617    AST_LIST_LOCK(&global_curl_info);
00618    AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
00619       if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00620          hashcompat = (long) cur->value;
00621       } else {
00622          curl_easy_setopt(*curl, cur->key, cur->value);
00623       }
00624    }
00625 
00626    if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00627       list = store->data;
00628       AST_LIST_LOCK(list);
00629       AST_LIST_TRAVERSE(list, cur, list) {
00630          if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00631             hashcompat = (long) cur->value;
00632          } else {
00633             curl_easy_setopt(*curl, cur->key, cur->value);
00634          }
00635       }
00636    }
00637 
00638    curl_easy_setopt(*curl, CURLOPT_URL, args.url);
00639    curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
00640 
00641    if (args.postdata) {
00642       curl_easy_setopt(*curl, CURLOPT_POST, 1);
00643       curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
00644    }
00645 
00646    /* Temporarily assign a buffer for curl to write errors to. */
00647    curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
00648    curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
00649 
00650    if (curl_easy_perform(*curl) != 0) {
00651       ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args.url);
00652    }
00653 
00654    /* Reset buffer to NULL so curl doesn't try to write to it when the
00655     * buffer is deallocated. Documentation is vague about allowing NULL
00656     * here, but the source allows it. See: "typecheck: allow NULL to unset
00657     * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
00658    curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
00659 
00660    if (store) {
00661       AST_LIST_UNLOCK(list);
00662    }
00663    AST_LIST_UNLOCK(&global_curl_info);
00664 
00665    if (args.postdata) {
00666       curl_easy_setopt(*curl, CURLOPT_POST, 0);
00667    }
00668 
00669    if (ast_str_strlen(str)) {
00670       ast_str_trim_blanks(str);
00671 
00672       ast_debug(3, "str='%s'\n", ast_str_buffer(str));
00673       if (hashcompat) {
00674          char *remainder = ast_str_buffer(str);
00675          char *piece;
00676          struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
00677          struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
00678          int rowcount = 0;
00679          while (fields && values && (piece = strsep(&remainder, "&"))) {
00680             char *name = strsep(&piece, "=");
00681             struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
00682             if (piece) {
00683                ast_uri_decode(piece, mode);
00684             }
00685             ast_uri_decode(name, mode);
00686             ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
00687             ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
00688             rowcount++;
00689          }
00690          pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
00691          if (buf) {
00692             ast_copy_string(buf, ast_str_buffer(values), len);
00693          } else {
00694             ast_str_set(input_str, len, "%s", ast_str_buffer(values));
00695          }
00696          ast_free(fields);
00697          ast_free(values);
00698       } else {
00699          if (buf) {
00700             ast_copy_string(buf, ast_str_buffer(str), len);
00701          } else {
00702             ast_str_set(input_str, len, "%s", ast_str_buffer(str));
00703          }
00704       }
00705       ret = 0;
00706    }
00707    ast_free(str);
00708 
00709    if (chan)
00710       ast_autoservice_stop(chan);
00711 
00712    return ret;
00713 }
00714 
00715 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
00716 {
00717    return acf_curl_helper(chan, cmd, info, buf, NULL, len);
00718 }
00719 
00720 static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
00721 {
00722    return acf_curl_helper(chan, cmd, info, NULL, buf, len);
00723 }
00724 
00725 static struct ast_custom_function acf_curl = {
00726    .name = "CURL",
00727    .synopsis = "Retrieves the contents of a URL",
00728    .syntax = "CURL(url[,post-data])",
00729    .desc =
00730    "  url       - URL to retrieve\n"
00731    "  post-data - Optional data to send as a POST (GET is default action)\n",
00732    .read = acf_curl_exec,
00733    .read2 = acf_curl2_exec,
00734 };
00735 
00736 static struct ast_custom_function acf_curlopt = {
00737    .name = "CURLOPT",
00738    .synopsis = "Set options for use with the CURL() function",
00739    .syntax = "CURLOPT(<option>)",
00740    .desc =
00741 "  cookie         - Send cookie with request [none]\n"
00742 "  conntimeout    - Number of seconds to wait for connection\n"
00743 "  dnstimeout     - Number of seconds to wait for DNS response\n"
00744 "  ftptext        - For FTP, force a text transfer (boolean)\n"
00745 "  ftptimeout     - For FTP, the server response timeout\n"
00746 "  header         - Retrieve header information (boolean)\n"
00747 "  httptimeout    - Number of seconds to wait for HTTP response\n"
00748 "  maxredirs      - Maximum number of redirects to follow\n"
00749 "  proxy          - Hostname or IP to use as a proxy\n"
00750 "  proxytype      - http, socks4, or socks5\n"
00751 "  proxyport      - port number of the proxy\n"
00752 "  proxyuserpwd   - A <user>:<pass> to use for authentication\n"
00753 "  referer        - Referer URL to use for the request\n"
00754 "  useragent      - UserAgent string to use\n"
00755 "  userpwd        - A <user>:<pass> to use for authentication\n"
00756 "  ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
00757 "  hashcompat     - Result data will be compatible for use with HASH()\n"
00758 "                 - if value is \"legacy\", will translate '+' to ' '\n"
00759 "",
00760    .read = acf_curlopt_read,
00761    .read2 = acf_curlopt_read2,
00762    .write = acf_curlopt_write,
00763 };
00764 
00765 static int unload_module(void)
00766 {
00767    int res;
00768 
00769    res = ast_custom_function_unregister(&acf_curl);
00770    res |= ast_custom_function_unregister(&acf_curlopt);
00771 
00772    return res;
00773 }
00774 
00775 static int load_module(void)
00776 {
00777    int res;
00778 
00779    if (!ast_module_check("res_curl.so")) {
00780       if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
00781          ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
00782          return AST_MODULE_LOAD_DECLINE;
00783       }
00784    }
00785 
00786    res = ast_custom_function_register(&acf_curl);
00787    res |= ast_custom_function_register(&acf_curlopt);
00788 
00789    return res;
00790 }
00791 
00792 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
00793       .load = load_module,
00794       .unload = unload_module,
00795       .load_pri = AST_MODPRI_REALTIME_DEPEND2,
00796    );
00797