XML validation
[asterisk/asterisk.git] / funcs / func_curl.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C)  2004 - 2006, Tilghman Lesher
5  *
6  * Tilghman Lesher <curl-20050919@the-tilghman.com>
7  * and Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option)
8  *
9  * app_curl.c is distributed with no restrictions on usage or
10  * redistribution.
11  *
12  * See http://www.asterisk.org for more information about
13  * the Asterisk project. Please do not directly contact
14  * any of the maintainers of this project for assistance;
15  * the project provides a web site, mailing lists and IRC
16  * channels for your use.
17  *
18  */
19
20 /*! \file
21  * 
22  * \brief Curl - Load a URL
23  *
24  * \author Tilghman Lesher <curl-20050919@the-tilghman.com>
25  *
26  * \note Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option) 
27  *
28  * \extref Depends on the CURL library  - http://curl.haxx.se/
29  * 
30  * \ingroup functions
31  */
32  
33 /*** MODULEINFO
34         <depend>curl</depend>
35  ***/
36
37 #include "asterisk.h"
38
39 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
40
41 #include <curl/curl.h>
42
43 #include "asterisk/lock.h"
44 #include "asterisk/file.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/cli.h"
48 #include "asterisk/module.h"
49 #include "asterisk/app.h"
50 #include "asterisk/utils.h"
51 #include "asterisk/threadstorage.h"
52
53 /*** DOCUMENTATION
54         <function name="CURL" language="en_US">
55                 <synopsis>
56                         Retrieve content from a remote web or ftp server
57                 </synopsis>
58                 <syntax>
59                         <parameter name="url" required="true" />
60                         <parameter name="post-data">
61                                 <para>If specified, an <literal>HTTP POST</literal> will be
62                                 performed with the content of
63                                 <replaceable>post-data</replaceable>, instead of an
64                                 <literal>HTTP GET</literal> (default).</para>
65                         </parameter>
66                 </syntax>
67                 <description />
68                 <see-also>
69                         <ref type="function">CURLOPT</ref>
70                 </see-also>
71         </function>
72         <function name="CURLOPT" language="en_US">
73                 <synopsis>
74                         Sets various options for future invocations of CURL.
75                 </synopsis>
76                 <syntax>
77                         <parameter name="key" required="yes">
78                                 <enumlist>
79                                         <enum name="cookie">
80                                                 <para>A cookie to send with the request.  Multiple
81                                                 cookies are supported.</para>
82                                         </enum>
83                                         <enum name="conntimeout">
84                                                 <para>Number of seconds to wait for a connection to succeed</para>
85                                         </enum>
86                                         <enum name="dnstimeout">
87                                                 <para>Number of seconds to wait for DNS to be resolved</para>
88                                         </enum>
89                                         <enum name="ftptext">
90                                                 <para>For FTP URIs, force a text transfer (boolean)</para>
91                                         </enum>
92                                         <enum name="ftptimeout">
93                                                 <para>For FTP URIs, number of seconds to wait for a
94                                                 server response</para>
95                                         </enum>
96                                         <enum name="header">
97                                                 <para>Include header information in the result
98                                                 (boolean)</para>
99                                         </enum>
100                                         <enum name="httptimeout">
101                                                 <para>For HTTP(S) URIs, number of seconds to wait for a
102                                                 server response</para>
103                                         </enum>
104                                         <enum name="maxredirs">
105                                                 <para>Maximum number of redirects to follow</para>
106                                         </enum>
107                                         <enum name="proxy">
108                                                 <para>Hostname or IP address to use as a proxy server</para>
109                                         </enum>
110                                         <enum name="proxytype">
111                                                 <para>Type of <literal>proxy</literal></para>
112                                                 <enumlist>
113                                                         <enum name="http" />
114                                                         <enum name="socks4" />
115                                                         <enum name="socks5" />
116                                                 </enumlist>
117                                         </enum>
118                                         <enum name="proxyport">
119                                                 <para>Port number of the <literal>proxy</literal></para>
120                                         </enum>
121                                         <enum name="proxyuserpwd">
122                                                 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
123                                                 combination to use for authenticating requests through a
124                                                 <literal>proxy</literal></para>
125                                         </enum>
126                                         <enum name="referer">
127                                                 <para>Referer URL to use for the request</para>
128                                         </enum>
129                                         <enum name="useragent">
130                                                 <para>UserAgent string to use for the request</para>
131                                         </enum>
132                                         <enum name="userpwd">
133                                                 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
134                                                 to use for authentication when the server response to
135                                                 an initial request indicates a 401 status code.</para>
136                                         </enum>
137                                         <enum name="ssl_verifypeer">
138                                                 <para>Whether to verify the server certificate against
139                                                 a list of known root certificate authorities (boolean).</para>
140                                         </enum>
141                                         <enum name="hashcompat">
142                                                 <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
143                                                 format, reformat the response such that it can be used
144                                                 by the <literal>HASH</literal> function.</para>
145                                                 <enumlist>
146                                                         <enum name="yes" />
147                                                         <enum name="no" />
148                                                         <enum name="legacy">
149                                                                 <para>Also translate <literal>+</literal> to the
150                                                                 space character, in violation of current RFC
151                                                                 standards.</para>
152                                                         </enum>
153                                                 </enumlist>
154                                         </enum>
155                                 </enumlist>
156                         </parameter>
157                 </syntax>
158                 <description>
159                         <para>Options may be set globally or per channel.  Per-channel
160                         settings will override global settings.</para>
161                 </description>
162                 <see-also>
163                         <ref type="function">CURL</ref>
164                         <ref type="function">HASH</ref>
165                 </see-also>
166         </function>
167  ***/
168
169 #define CURLVERSION_ATLEAST(a,b,c) \
170         ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
171
172 #define CURLOPT_SPECIAL_HASHCOMPAT -500
173
174 static void curlds_free(void *data);
175
176 static struct ast_datastore_info curl_info = {
177         .type = "CURL",
178         .destroy = curlds_free,
179 };
180
181 struct curl_settings {
182         AST_LIST_ENTRY(curl_settings) list;
183         CURLoption key;
184         void *value;
185 };
186
187 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
188
189 static void curlds_free(void *data)
190 {
191         AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
192         struct curl_settings *setting;
193         if (!list) {
194                 return;
195         }
196         while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
197                 free(setting);
198         }
199         AST_LIST_HEAD_DESTROY(list);
200 }
201
202 enum optiontype {
203         OT_BOOLEAN,
204         OT_INTEGER,
205         OT_INTEGER_MS,
206         OT_STRING,
207         OT_ENUM,
208 };
209
210 enum hashcompat {
211         HASHCOMPAT_NO = 0,
212         HASHCOMPAT_YES,
213         HASHCOMPAT_LEGACY,
214 };
215
216 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
217 {
218         if (!strcasecmp(name, "header")) {
219                 *key = CURLOPT_HEADER;
220                 *ot = OT_BOOLEAN;
221         } else if (!strcasecmp(name, "proxy")) {
222                 *key = CURLOPT_PROXY;
223                 *ot = OT_STRING;
224         } else if (!strcasecmp(name, "proxyport")) {
225                 *key = CURLOPT_PROXYPORT;
226                 *ot = OT_INTEGER;
227         } else if (!strcasecmp(name, "proxytype")) {
228                 *key = CURLOPT_PROXYTYPE;
229                 *ot = OT_ENUM;
230         } else if (!strcasecmp(name, "dnstimeout")) {
231                 *key = CURLOPT_DNS_CACHE_TIMEOUT;
232                 *ot = OT_INTEGER;
233         } else if (!strcasecmp(name, "userpwd")) {
234                 *key = CURLOPT_USERPWD;
235                 *ot = OT_STRING;
236         } else if (!strcasecmp(name, "proxyuserpwd")) {
237                 *key = CURLOPT_PROXYUSERPWD;
238                 *ot = OT_STRING;
239         } else if (!strcasecmp(name, "maxredirs")) {
240                 *key = CURLOPT_MAXREDIRS;
241                 *ot = OT_INTEGER;
242         } else if (!strcasecmp(name, "referer")) {
243                 *key = CURLOPT_REFERER;
244                 *ot = OT_STRING;
245         } else if (!strcasecmp(name, "useragent")) {
246                 *key = CURLOPT_USERAGENT;
247                 *ot = OT_STRING;
248         } else if (!strcasecmp(name, "cookie")) {
249                 *key = CURLOPT_COOKIE;
250                 *ot = OT_STRING;
251         } else if (!strcasecmp(name, "ftptimeout")) {
252                 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
253                 *ot = OT_INTEGER;
254         } else if (!strcasecmp(name, "httptimeout")) {
255 #if CURLVERSION_ATLEAST(7,16,2)
256                 *key = CURLOPT_TIMEOUT_MS;
257                 *ot = OT_INTEGER_MS;
258 #else
259                 *key = CURLOPT_TIMEOUT;
260                 *ot = OT_INTEGER;
261 #endif
262         } else if (!strcasecmp(name, "conntimeout")) {
263 #if CURLVERSION_ATLEAST(7,16,2)
264                 *key = CURLOPT_CONNECTTIMEOUT_MS;
265                 *ot = OT_INTEGER_MS;
266 #else
267                 *key = CURLOPT_CONNECTTIMEOUT;
268                 *ot = OT_INTEGER;
269 #endif
270         } else if (!strcasecmp(name, "ftptext")) {
271                 *key = CURLOPT_TRANSFERTEXT;
272                 *ot = OT_BOOLEAN;
273         } else if (!strcasecmp(name, "ssl_verifypeer")) {
274                 *key = CURLOPT_SSL_VERIFYPEER;
275                 *ot = OT_BOOLEAN;
276         } else if (!strcasecmp(name, "hashcompat")) {
277                 *key = CURLOPT_SPECIAL_HASHCOMPAT;
278                 *ot = OT_ENUM;
279         } else {
280                 return -1;
281         }
282         return 0;
283 }
284
285 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
286 {
287         struct ast_datastore *store;
288         struct global_curl_info *list;
289         struct curl_settings *cur, *new = NULL;
290         CURLoption key;
291         enum optiontype ot;
292
293         if (chan) {
294                 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
295                         /* Create a new datastore */
296                         if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
297                                 ast_log(LOG_ERROR, "Unable to allocate new datastore.  Cannot set any CURL options\n");
298                                 return -1;
299                         }
300
301                         if (!(list = ast_calloc(1, sizeof(*list)))) {
302                                 ast_log(LOG_ERROR, "Unable to allocate list head.  Cannot set any CURL options\n");
303                                 ast_datastore_free(store);
304                         }
305
306                         store->data = list;
307                         AST_LIST_HEAD_INIT(list);
308                         ast_channel_datastore_add(chan, store);
309                 } else {
310                         list = store->data;
311                 }
312         } else {
313                 /* Populate the global structure */
314                 list = &global_curl_info;
315         }
316
317         if (!parse_curlopt_key(name, &key, &ot)) {
318                 if (ot == OT_BOOLEAN) {
319                         if ((new = ast_calloc(1, sizeof(*new)))) {
320                                 new->value = (void *)((long) ast_true(value));
321                         }
322                 } else if (ot == OT_INTEGER) {
323                         long tmp = atol(value);
324                         if ((new = ast_calloc(1, sizeof(*new)))) {
325                                 new->value = (void *)tmp;
326                         }
327                 } else if (ot == OT_INTEGER_MS) {
328                         long tmp = atof(value) * 1000.0;
329                         if ((new = ast_calloc(1, sizeof(*new)))) {
330                                 new->value = (void *)tmp;
331                         }
332                 } else if (ot == OT_STRING) {
333                         if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
334                                 new->value = (char *)new + sizeof(*new);
335                                 strcpy(new->value, value);
336                         }
337                 } else if (ot == OT_ENUM) {
338                         if (key == CURLOPT_PROXYTYPE) {
339                                 long ptype =
340 #if CURLVERSION_ATLEAST(7,10,0)
341                                         CURLPROXY_HTTP;
342 #else
343                                         CURLPROXY_SOCKS5;
344 #endif
345                                 if (0) {
346 #if CURLVERSION_ATLEAST(7,15,2)
347                                 } else if (!strcasecmp(value, "socks4")) {
348                                         ptype = CURLPROXY_SOCKS4;
349 #endif
350 #if CURLVERSION_ATLEAST(7,18,0)
351                                 } else if (!strcasecmp(value, "socks4a")) {
352                                         ptype = CURLPROXY_SOCKS4A;
353 #endif
354 #if CURLVERSION_ATLEAST(7,18,0)
355                                 } else if (!strcasecmp(value, "socks5")) {
356                                         ptype = CURLPROXY_SOCKS5;
357 #endif
358 #if CURLVERSION_ATLEAST(7,18,0)
359                                 } else if (!strncasecmp(value, "socks5", 6)) {
360                                         ptype = CURLPROXY_SOCKS5_HOSTNAME;
361 #endif
362                                 }
363
364                                 if ((new = ast_calloc(1, sizeof(*new)))) {
365                                         new->value = (void *)ptype;
366                                 }
367                         } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
368                                 if ((new = ast_calloc(1, sizeof(*new)))) {
369                                         new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
370                                 }
371                         } else {
372                                 /* Highly unlikely */
373                                 goto yuck;
374                         }
375                 }
376
377                 /* Memory allocation error */
378                 if (!new) {
379                         return -1;
380                 }
381
382                 new->key = key;
383         } else {
384 yuck:
385                 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
386                 return -1;
387         }
388
389         /* Remove any existing entry */
390         AST_LIST_LOCK(list);
391         AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
392                 if (cur->key == new->key) {
393                         AST_LIST_REMOVE_CURRENT(list);
394                         free(cur);
395                         break;
396                 }
397         }
398         AST_LIST_TRAVERSE_SAFE_END
399
400         /* Insert new entry */
401         ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
402         AST_LIST_INSERT_TAIL(list, new, list);
403         AST_LIST_UNLOCK(list);
404
405         return 0;
406 }
407
408 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
409 {
410         struct ast_datastore *store;
411         struct global_curl_info *list[2] = { &global_curl_info, NULL };
412         struct curl_settings *cur = NULL;
413         CURLoption key;
414         enum optiontype ot;
415         int i;
416
417         if (parse_curlopt_key(data, &key, &ot)) {
418                 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
419                 return -1;
420         }
421
422         if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
423                 list[0] = store->data;
424                 list[1] = &global_curl_info;
425         }
426
427         for (i = 0; i < 2; i++) {
428                 if (!list[i]) {
429                         break;
430                 }
431                 AST_LIST_LOCK(list[i]);
432                 AST_LIST_TRAVERSE(list[i], cur, list) {
433                         if (cur->key == key) {
434                                 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
435                                         if (buf) {
436                                                 snprintf(buf, len, "%ld", (long) cur->value);
437                                         } else {
438                                                 ast_str_set(bufstr, len, "%ld", (long) cur->value);
439                                         }
440                                 } else if (ot == OT_INTEGER_MS) {
441                                         if ((long) cur->value % 1000 == 0) {
442                                                 if (buf) {
443                                                         snprintf(buf, len, "%ld", (long)cur->value / 1000);
444                                                 } else {
445                                                         ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
446                                                 }
447                                         } else {
448                                                 if (buf) {
449                                                         snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
450                                                 } else {
451                                                         ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
452                                                 }
453                                         }
454                                 } else if (ot == OT_STRING) {
455                                         ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
456                                         if (buf) {
457                                                 ast_copy_string(buf, cur->value, len);
458                                         } else {
459                                                 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
460                                         }
461                                 } else if (key == CURLOPT_PROXYTYPE) {
462                                         const char *strval = "unknown";
463                                         if (0) {
464 #if CURLVERSION_ATLEAST(7,15,2)
465                                         } else if ((long)cur->value == CURLPROXY_SOCKS4) {
466                                                 strval = "socks4";
467 #endif
468 #if CURLVERSION_ATLEAST(7,18,0)
469                                         } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
470                                                 strval = "socks4a";
471 #endif
472                                         } else if ((long)cur->value == CURLPROXY_SOCKS5) {
473                                                 strval = "socks5";
474 #if CURLVERSION_ATLEAST(7,18,0)
475                                         } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
476                                                 strval = "socks5hostname";
477 #endif
478 #if CURLVERSION_ATLEAST(7,10,0)
479                                         } else if ((long)cur->value == CURLPROXY_HTTP) {
480                                                 strval = "http";
481 #endif
482                                         }
483                                         if (buf) {
484                                                 ast_copy_string(buf, strval, len);
485                                         } else {
486                                                 ast_str_set(bufstr, 0, "%s", strval);
487                                         }
488                                 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
489                                         const char *strval = "unknown";
490                                         if ((long) cur->value == HASHCOMPAT_LEGACY) {
491                                                 strval = "legacy";
492                                         } else if ((long) cur->value == HASHCOMPAT_YES) {
493                                                 strval = "yes";
494                                         } else if ((long) cur->value == HASHCOMPAT_NO) {
495                                                 strval = "no";
496                                         }
497                                         if (buf) {
498                                                 ast_copy_string(buf, strval, len);
499                                         } else {
500                                                 ast_str_set(bufstr, 0, "%s", strval);
501                                         }
502                                 }
503                                 break;
504                         }
505                 }
506                 AST_LIST_UNLOCK(list[i]);
507                 if (cur) {
508                         break;
509                 }
510         }
511
512         return cur ? 0 : -1;
513 }
514
515 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
516 {
517         return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
518 }
519
520 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
521 {
522         return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
523 }
524
525 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
526 {
527         register int realsize = size * nmemb;
528         struct ast_str **pstr = (struct ast_str **)data;
529
530         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));
531
532         ast_str_append_substr(pstr, 0, ptr, realsize);
533
534         ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
535
536         return realsize;
537 }
538
539 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
540
541 static int curl_instance_init(void *data)
542 {
543         CURL **curl = data;
544
545         if (!(*curl = curl_easy_init()))
546                 return -1;
547
548         curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
549         curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
550         curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
551         curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
552
553         return 0;
554 }
555
556 static void curl_instance_cleanup(void *data)
557 {
558         CURL **curl = data;
559
560         curl_easy_cleanup(*curl);
561
562         ast_free(data);
563 }
564
565 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
566
567 static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
568 {
569         struct ast_str *str = ast_str_create(16);
570         int ret = -1;
571         AST_DECLARE_APP_ARGS(args,
572                 AST_APP_ARG(url);
573                 AST_APP_ARG(postdata);
574         );
575         CURL **curl;
576         struct curl_settings *cur;
577         struct ast_datastore *store = NULL;
578         int hashcompat = 0;
579         AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
580
581         if (buf) {
582                 *buf = '\0';
583         }
584
585         if (ast_strlen_zero(info)) {
586                 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
587                 ast_free(str);
588                 return -1;
589         }
590
591         AST_STANDARD_APP_ARGS(args, info);
592
593         if (chan) {
594                 ast_autoservice_start(chan);
595         }
596
597         if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
598                 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
599                 return -1;
600         }
601
602         AST_LIST_LOCK(&global_curl_info);
603         AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
604                 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
605                         hashcompat = (long) cur->value;
606                 } else {
607                         curl_easy_setopt(*curl, cur->key, cur->value);
608                 }
609         }
610
611         if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
612                 list = store->data;
613                 AST_LIST_LOCK(list);
614                 AST_LIST_TRAVERSE(list, cur, list) {
615                         if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
616                                 hashcompat = (long) cur->value;
617                         } else {
618                                 curl_easy_setopt(*curl, cur->key, cur->value);
619                         }
620                 }
621         }
622
623         curl_easy_setopt(*curl, CURLOPT_URL, args.url);
624         curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
625
626         if (args.postdata) {
627                 curl_easy_setopt(*curl, CURLOPT_POST, 1);
628                 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
629         }
630
631         curl_easy_perform(*curl);
632
633         if (store) {
634                 AST_LIST_UNLOCK(list);
635         }
636         AST_LIST_UNLOCK(&global_curl_info);
637
638         if (args.postdata) {
639                 curl_easy_setopt(*curl, CURLOPT_POST, 0);
640         }
641
642         if (ast_str_strlen(str)) {
643                 ast_str_trim_blanks(str);
644
645                 ast_debug(3, "str='%s'\n", ast_str_buffer(str));
646                 if (hashcompat) {
647                         char *remainder = ast_str_buffer(str);
648                         char *piece;
649                         struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
650                         struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
651                         int rowcount = 0;
652                         while (fields && values && (piece = strsep(&remainder, "&"))) {
653                                 char *name = strsep(&piece, "=");
654                                 if (!piece) {
655                                         piece = "";
656                                 }
657                                 ast_uri_decode(piece);
658                                 ast_uri_decode(name);
659                                 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", name);
660                                 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", piece);
661                                 rowcount++;
662                         }
663                         pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
664                         if (buf) {
665                                 ast_copy_string(buf, ast_str_buffer(values), len);
666                         } else {
667                                 ast_str_set(input_str, len, "%s", ast_str_buffer(values));
668                         }
669                         ast_free(fields);
670                         ast_free(values);
671                 } else {
672                         if (buf) {
673                                 ast_copy_string(buf, ast_str_buffer(str), len);
674                         } else {
675                                 ast_str_set(input_str, len, "%s", ast_str_buffer(str));
676                         }
677                 }
678                 ret = 0;
679         }
680         ast_free(str);
681
682         if (chan)
683                 ast_autoservice_stop(chan);
684
685         return ret;
686 }
687
688 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
689 {
690         return acf_curl_helper(chan, cmd, info, buf, NULL, len);
691 }
692
693 static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
694 {
695         return acf_curl_helper(chan, cmd, info, NULL, buf, len);
696 }
697
698 static struct ast_custom_function acf_curl = {
699         .name = "CURL",
700         .synopsis = "Retrieves the contents of a URL",
701         .syntax = "CURL(url[,post-data])",
702         .desc =
703         "  url       - URL to retrieve\n"
704         "  post-data - Optional data to send as a POST (GET is default action)\n",
705         .read = acf_curl_exec,
706         .read2 = acf_curl2_exec,
707 };
708
709 static struct ast_custom_function acf_curlopt = {
710         .name = "CURLOPT",
711         .synopsis = "Set options for use with the CURL() function",
712         .syntax = "CURLOPT(<option>)",
713         .desc =
714 "  cookie         - Send cookie with request [none]\n"
715 "  conntimeout    - Number of seconds to wait for connection\n"
716 "  dnstimeout     - Number of seconds to wait for DNS response\n"
717 "  ftptext        - For FTP, force a text transfer (boolean)\n"
718 "  ftptimeout     - For FTP, the server response timeout\n"
719 "  header         - Retrieve header information (boolean)\n"
720 "  httptimeout    - Number of seconds to wait for HTTP response\n"
721 "  maxredirs      - Maximum number of redirects to follow\n"
722 "  proxy          - Hostname or IP to use as a proxy\n"
723 "  proxytype      - http, socks4, or socks5\n"
724 "  proxyport      - port number of the proxy\n"
725 "  proxyuserpwd   - A <user>:<pass> to use for authentication\n"
726 "  referer        - Referer URL to use for the request\n"
727 "  useragent      - UserAgent string to use\n"
728 "  userpwd        - A <user>:<pass> to use for authentication\n"
729 "  ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
730 "  hashcompat     - Result data will be compatible for use with HASH()\n"
731 "                 - if value is \"legacy\", will translate '+' to ' '\n"
732 "",
733         .read = acf_curlopt_read,
734         .read2 = acf_curlopt_read2,
735         .write = acf_curlopt_write,
736 };
737
738 static int unload_module(void)
739 {
740         int res;
741
742         res = ast_custom_function_unregister(&acf_curl);
743         res |= ast_custom_function_unregister(&acf_curlopt);
744
745         return res;
746 }
747
748 static int load_module(void)
749 {
750         int res;
751
752         if (!ast_module_check("res_curl.so")) {
753                 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
754                         ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
755                         return AST_MODULE_LOAD_DECLINE;
756                 }
757         }
758
759         res = ast_custom_function_register(&acf_curl);
760         res |= ast_custom_function_register(&acf_curlopt);
761
762         return res;
763 }
764
765 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
766                 .load = load_module,
767                 .unload = unload_module,
768                 .load_pri = AST_MODPRI_REALTIME_DEPEND2,
769         );
770