Asterisk - The Open Source Telephony Project GIT-master-754dea3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
func_curl.c
Go to the documentation of this file.
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>res_curl</depend>
35 <depend>curl</depend>
36 <support_level>core</support_level>
37 ***/
38
39#include "asterisk.h"
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"
52#include "asterisk/test.h"
53
54/*** DOCUMENTATION
55 <function name="CURL" language="en_US">
56 <since>
57 <version>10.0.0</version>
58 </since>
59 <synopsis>
60 Retrieve content from a remote web or ftp server
61 </synopsis>
62 <syntax>
63 <parameter name="url" required="true">
64 <para>The full URL for the resource to retrieve.</para>
65 </parameter>
66 <parameter name="post-data">
67 <para><emphasis>Read Only</emphasis></para>
68 <para>If specified, an <literal>HTTP POST</literal> will be
69 performed with the content of
70 <replaceable>post-data</replaceable>, instead of an
71 <literal>HTTP GET</literal> (default).</para>
72 </parameter>
73 </syntax>
74 <description>
75 <para>When this function is read, a <literal>HTTP GET</literal>
76 (by default) will be used to retrieve the contents of the provided
77 <replaceable>url</replaceable>. The contents are returned as the
78 result of the function.</para>
79 <example title="Displaying contents of a page" language="text">
80 exten => s,1,Verbose(0, ${CURL(http://localhost:8088/static/astman.css)})
81 </example>
82 <para>When this function is written to, a <literal>HTTP GET</literal>
83 will be used to retrieve the contents of the provided
84 <replaceable>url</replaceable>. The value written to the function
85 specifies the destination file of the cURL'd resource.</para>
86 <example title="Retrieving a file" language="text">
87 exten => s,1,Set(CURL(http://localhost:8088/static/astman.css)=/var/spool/asterisk/tmp/astman.css))
88 </example>
89 <note>
90 <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
91 is set to <literal>no</literal>, this function can only be written to from the
92 dialplan, and not directly from external protocols. Read operations are
93 unaffected.</para>
94 </note>
95 </description>
96 <see-also>
97 <ref type="function">CURLOPT</ref>
98 </see-also>
99 </function>
100 <function name="CURLOPT" language="en_US">
101 <since>
102 <version>10.0.0</version>
103 </since>
104 <synopsis>
105 Sets various options for future invocations of CURL.
106 </synopsis>
107 <syntax>
108 <parameter name="key" required="yes">
109 <enumlist>
110 <enum name="cookie">
111 <para>A cookie to send with the request. Multiple
112 cookies are supported.</para>
113 </enum>
114 <enum name="conntimeout">
115 <para>Number of seconds to wait for a connection to succeed</para>
116 </enum>
117 <enum name="dnstimeout">
118 <para>Number of seconds to wait for DNS to be resolved</para>
119 </enum>
120 <enum name="followlocation">
121 <para>Whether or not to follow HTTP 3xx redirects (boolean)</para>
122 </enum>
123 <enum name="ftptext">
124 <para>For FTP URIs, force a text transfer (boolean)</para>
125 </enum>
126 <enum name="ftptimeout">
127 <para>For FTP URIs, number of seconds to wait for a
128 server response</para>
129 </enum>
130 <enum name="header">
131 <para>Include header information in the result
132 (boolean)</para>
133 </enum>
134 <enum name="httpheader">
135 <para>Add HTTP header. Multiple calls add multiple headers.
136 Setting of any header will remove the default
137 "Content-Type application/x-www-form-urlencoded"</para>
138 </enum>
139 <enum name="httptimeout">
140 <para>For HTTP(S) URIs, number of seconds to wait for a
141 server response</para>
142 </enum>
143 <enum name="maxredirs">
144 <para>Maximum number of redirects to follow. The default is -1,
145 which allows for unlimited redirects. This only makes sense when
146 followlocation is also set.</para>
147 </enum>
148 <enum name="proxy">
149 <para>Hostname or IP address to use as a proxy server</para>
150 </enum>
151 <enum name="proxytype">
152 <para>Type of <literal>proxy</literal></para>
153 <enumlist>
154 <enum name="http" />
155 <enum name="socks4" />
156 <enum name="socks5" />
157 </enumlist>
158 </enum>
159 <enum name="proxyport">
160 <para>Port number of the <literal>proxy</literal></para>
161 </enum>
162 <enum name="proxyuserpwd">
163 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
164 combination to use for authenticating requests through a
165 <literal>proxy</literal></para>
166 </enum>
167 <enum name="referer">
168 <para>Referer URL to use for the request</para>
169 </enum>
170 <enum name="useragent">
171 <para>UserAgent string to use for the request</para>
172 </enum>
173 <enum name="userpwd">
174 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
175 to use for authentication when the server response to
176 an initial request indicates a 401 status code.</para>
177 </enum>
178 <enum name="ssl_verifypeer">
179 <para>Whether to verify the server certificate against
180 a list of known root certificate authorities (boolean).</para>
181 </enum>
182 <enum name="ssl_verifyhost">
183 <para>Whether to verify the host in the server's TLS certificate.
184 Set to 2 to verify the host, 0 to ignore the host.</para>
185 </enum>
186 <enum name="ssl_cainfo">
187 <para>Path to a file holding one or more certificates to verify
188 the peer's certificate with. Only used when <literal>ssl_verifypeer</literal>
189 is enabled.</para>
190 </enum>
191 <enum name="ssl_capath">
192 <para>Path to a directory holding multiple CA certificates to
193 verify the peer's certificate with. Only used when <literal>ssl_verifypeer</literal>
194 is enabled.</para>
195 </enum>
196 <enum name="ssl_cert">
197 <para>Path to a file containing a client certificate. Default format
198 is PEM, and can be changed with <literal>ssl_certtype</literal>.</para>
199 </enum>
200 <enum name="ssl_certtype">
201 <para>The format of the <literal>ssl_cert</literal> file.</para>
202 <enumlist>
203 <enum name="PEM" />
204 <enum name="DER" />
205 </enumlist>
206 </enum>
207 <enum name="ssl_key">
208 <para>Path to a file containing a client private key. Default format
209 is PEM, and can be changed with <literal>ssl_keytype</literal></para>
210 </enum>
211 <enum name="ssl_keytype">
212 <para>The format of the <literal>ssl_key</literal> file.</para>
213 <enumlist>
214 <enum name="PEM" />
215 <enum name="DER" />
216 <enum name="ENG" />
217 </enumlist>
218 </enum>
219 <enum name="ssl_keypasswd">
220 <para>The passphrase to use the <literal>ssl_key</literal> file.</para>
221 </enum>
222 <enum name="hashcompat">
223 <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
224 format, reformat the response such that it can be used
225 by the <literal>HASH</literal> function.</para>
226 <enumlist>
227 <enum name="yes" />
228 <enum name="no" />
229 <enum name="legacy">
230 <para>Also translate <literal>+</literal> to the
231 space character, in violation of current RFC
232 standards.</para>
233 </enum>
234 </enumlist>
235 </enum>
236 <enum name="failurecodes">
237 <para>A comma separated list of HTTP response codes to be treated as errors</para>
238 </enum>
239 </enumlist>
240 </parameter>
241 </syntax>
242 <description>
243 <para>Options may be set globally or per channel. Per-channel
244 settings will override global settings. Only HTTP headers are added instead of overriding</para>
245 </description>
246 <see-also>
247 <ref type="function">CURL</ref>
248 <ref type="function">HASH</ref>
249 </see-also>
250 </function>
251 ***/
252
253#define CURLVERSION_ATLEAST(a,b,c) \
254 ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
255
256#define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500)
257
258#define CURLOPT_SPECIAL_FAILURE_CODE 999
259
260static void curlds_free(void *data);
261
262static const struct ast_datastore_info curl_info = {
263 .type = "CURL",
264 .destroy = curlds_free,
265};
266
269 CURLoption key;
270 void *value;
271};
272
274
275static void curlds_free(void *data)
276{
278 struct curl_settings *setting;
279 if (!list) {
280 return;
281 }
282 while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
283 ast_free(setting);
284 }
286 ast_free(list);
287}
288
295};
296
301};
302
303static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
304{
305 if (!strcasecmp(name, "header")) {
306 *key = CURLOPT_HEADER;
307 *ot = OT_BOOLEAN;
308 } else if (!strcasecmp(name, "httpheader")) {
309 *key = CURLOPT_HTTPHEADER;
310 *ot = OT_STRING;
311 } else if (!strcasecmp(name, "proxy")) {
312 *key = CURLOPT_PROXY;
313 *ot = OT_STRING;
314 } else if (!strcasecmp(name, "proxyport")) {
315 *key = CURLOPT_PROXYPORT;
316 *ot = OT_INTEGER;
317 } else if (!strcasecmp(name, "proxytype")) {
318 *key = CURLOPT_PROXYTYPE;
319 *ot = OT_ENUM;
320 } else if (!strcasecmp(name, "dnstimeout")) {
321 *key = CURLOPT_DNS_CACHE_TIMEOUT;
322 *ot = OT_INTEGER;
323 } else if (!strcasecmp(name, "userpwd")) {
324 *key = CURLOPT_USERPWD;
325 *ot = OT_STRING;
326 } else if (!strcasecmp(name, "proxyuserpwd")) {
327 *key = CURLOPT_PROXYUSERPWD;
328 *ot = OT_STRING;
329 } else if (!strcasecmp(name, "followlocation")) {
330 *key = CURLOPT_FOLLOWLOCATION;
331 *ot = OT_BOOLEAN;
332 } else if (!strcasecmp(name, "maxredirs")) {
333 *key = CURLOPT_MAXREDIRS;
334 *ot = OT_INTEGER;
335 } else if (!strcasecmp(name, "referer")) {
336 *key = CURLOPT_REFERER;
337 *ot = OT_STRING;
338 } else if (!strcasecmp(name, "useragent")) {
339 *key = CURLOPT_USERAGENT;
340 *ot = OT_STRING;
341 } else if (!strcasecmp(name, "cookie")) {
342 *key = CURLOPT_COOKIE;
343 *ot = OT_STRING;
344 } else if (!strcasecmp(name, "ftptimeout")) {
345 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
346 *ot = OT_INTEGER;
347 } else if (!strcasecmp(name, "httptimeout")) {
348#if CURLVERSION_ATLEAST(7,16,2)
349 *key = CURLOPT_TIMEOUT_MS;
350 *ot = OT_INTEGER_MS;
351#else
352 *key = CURLOPT_TIMEOUT;
353 *ot = OT_INTEGER;
354#endif
355 } else if (!strcasecmp(name, "conntimeout")) {
356#if CURLVERSION_ATLEAST(7,16,2)
357 *key = CURLOPT_CONNECTTIMEOUT_MS;
358 *ot = OT_INTEGER_MS;
359#else
360 *key = CURLOPT_CONNECTTIMEOUT;
361 *ot = OT_INTEGER;
362#endif
363 } else if (!strcasecmp(name, "ftptext")) {
364 *key = CURLOPT_TRANSFERTEXT;
365 *ot = OT_BOOLEAN;
366 } else if (!strcasecmp(name, "ssl_verifypeer")) {
367 *key = CURLOPT_SSL_VERIFYPEER;
368 *ot = OT_BOOLEAN;
369 } else if (!strcasecmp(name, "ssl_verifyhost")) {
370 *key = CURLOPT_SSL_VERIFYHOST;
371 *ot = OT_INTEGER;
372 } else if (!strcasecmp(name, "ssl_cainfo")) {
373 *key = CURLOPT_CAINFO;
374 *ot = OT_STRING;
375 } else if (!strcasecmp(name, "ssl_capath")) {
376 *key = CURLOPT_CAPATH;
377 *ot = OT_STRING;
378 } else if (!strcasecmp(name, "ssl_cert")) {
379 *key = CURLOPT_SSLCERT;
380 *ot = OT_STRING;
381 } else if (!strcasecmp(name, "ssl_certtype")) {
382 *key = CURLOPT_SSLCERTTYPE;
383 *ot = OT_STRING;
384 } else if (!strcasecmp(name, "ssl_key")) {
385 *key = CURLOPT_SSLKEY;
386 *ot = OT_STRING;
387 } else if (!strcasecmp(name, "ssl_keytype")) {
388 *key = CURLOPT_SSLKEYTYPE;
389 *ot = OT_STRING;
390 } else if (!strcasecmp(name, "ssl_keypasswd")) {
391 *key = CURLOPT_KEYPASSWD;
392 *ot = OT_STRING;
393 } else if (!strcasecmp(name, "hashcompat")) {
395 *ot = OT_ENUM;
396 } else if (!strcasecmp(name, "failurecodes")) {
398 *ot = OT_STRING;
399 } else {
400 return -1;
401 }
402 return 0;
403}
404
405static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
406{
407 struct ast_datastore *store;
408 struct global_curl_info *list;
409 struct curl_settings *cur, *new = NULL;
410 CURLoption key;
411 enum optiontype ot;
412
413 if (chan) {
414 ast_channel_lock(chan);
415 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
416 /* Create a new datastore */
417 if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
418 ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n");
419 ast_channel_unlock(chan);
420 return -1;
421 }
422
423 if (!(list = ast_calloc(1, sizeof(*list)))) {
424 ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
425 ast_datastore_free(store);
426 ast_channel_unlock(chan);
427 return -1;
428 }
429
430 store->data = list;
432 ast_channel_datastore_add(chan, store);
433 } else {
434 list = store->data;
435 }
436 ast_channel_unlock(chan);
437 } else {
438 /* Populate the global structure */
440 }
441
442 if (!parse_curlopt_key(name, &key, &ot)) {
443 if (ot == OT_BOOLEAN) {
444 if ((new = ast_calloc(1, sizeof(*new)))) {
445 new->value = (void *)((long) ast_true(value));
446 }
447 } else if (ot == OT_INTEGER) {
448 long tmp = atol(value);
449 if ((new = ast_calloc(1, sizeof(*new)))) {
450 new->value = (void *)tmp;
451 }
452 } else if (ot == OT_INTEGER_MS) {
453 long tmp = atof(value) * 1000.0;
454 if ((new = ast_calloc(1, sizeof(*new)))) {
455 new->value = (void *)tmp;
456 }
457 } else if (ot == OT_STRING) {
458 if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
459 new->value = (char *)new + sizeof(*new);
460 strcpy(new->value, value);
461 }
462 } else if (ot == OT_ENUM) {
463 if (key == CURLOPT_PROXYTYPE) {
464 long ptype =
465#if CURLVERSION_ATLEAST(7,10,0)
466 CURLPROXY_HTTP;
467#else
468 CURLPROXY_SOCKS5;
469#endif
470 if (0) {
471#if CURLVERSION_ATLEAST(7,15,2)
472 } else if (!strcasecmp(value, "socks4")) {
473 ptype = CURLPROXY_SOCKS4;
474#endif
475#if CURLVERSION_ATLEAST(7,18,0)
476 } else if (!strcasecmp(value, "socks4a")) {
477 ptype = CURLPROXY_SOCKS4A;
478#endif
479#if CURLVERSION_ATLEAST(7,18,0)
480 } else if (!strcasecmp(value, "socks5")) {
481 ptype = CURLPROXY_SOCKS5;
482#endif
483#if CURLVERSION_ATLEAST(7,18,0)
484 } else if (!strncasecmp(value, "socks5", 6)) {
485 ptype = CURLPROXY_SOCKS5_HOSTNAME;
486#endif
487 }
488
489 if ((new = ast_calloc(1, sizeof(*new)))) {
490 new->value = (void *)ptype;
491 }
492 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
493 if ((new = ast_calloc(1, sizeof(*new)))) {
494 new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
495 }
496 } else {
497 /* Highly unlikely */
498 goto yuck;
499 }
500 }
501
502 /* Memory allocation error */
503 if (!new) {
504 return -1;
505 }
506
507 new->key = key;
508 } else {
509yuck:
510 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
511 return -1;
512 }
513
514 /* Remove any existing entry, only http headers are left */
516 if (new->key != CURLOPT_HTTPHEADER) {
518 if (cur->key == new->key) {
520 ast_free(cur);
521 break;
522 }
523 }
525 }
526
527 /* Insert new entry */
528 ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
531
532 return 0;
533}
534
535static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
536{
537 struct ast_datastore *store;
538 struct global_curl_info *list[2] = { &global_curl_info, NULL };
539 struct curl_settings *cur = NULL;
540 CURLoption key;
541 enum optiontype ot;
542 int i;
543
544 if (parse_curlopt_key(data, &key, &ot)) {
545 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
546 return -1;
547 }
548
549 if (chan) {
550 /* If we have a channel, we want to read the options set there before
551 falling back to the global settings */
552 ast_channel_lock(chan);
554 ast_channel_unlock(chan);
555
556 if (store) {
557 list[0] = store->data;
559 }
560 }
561
562 for (i = 0; i < 2; i++) {
563 if (!list[i]) {
564 break;
565 }
567 AST_LIST_TRAVERSE(list[i], cur, list) {
568 if (cur->key == key) {
569 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
570 if (buf) {
571 snprintf(buf, len, "%ld", (long) cur->value);
572 } else {
573 ast_str_set(bufstr, len, "%ld", (long) cur->value);
574 }
575 } else if (ot == OT_INTEGER_MS) {
576 if ((long) cur->value % 1000 == 0) {
577 if (buf) {
578 snprintf(buf, len, "%ld", (long)cur->value / 1000);
579 } else {
580 ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
581 }
582 } else {
583 if (buf) {
584 snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
585 } else {
586 ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
587 }
588 }
589 } else if (ot == OT_STRING) {
590 ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
591 if (buf) {
593 } else {
594 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
595 }
596 } else if (key == CURLOPT_PROXYTYPE) {
597 const char *strval = "unknown";
598 if (0) {
599#if CURLVERSION_ATLEAST(7,15,2)
600 } else if ((long)cur->value == CURLPROXY_SOCKS4) {
601 strval = "socks4";
602#endif
603#if CURLVERSION_ATLEAST(7,18,0)
604 } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
605 strval = "socks4a";
606#endif
607 } else if ((long)cur->value == CURLPROXY_SOCKS5) {
608 strval = "socks5";
609#if CURLVERSION_ATLEAST(7,18,0)
610 } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
611 strval = "socks5hostname";
612#endif
613#if CURLVERSION_ATLEAST(7,10,0)
614 } else if ((long)cur->value == CURLPROXY_HTTP) {
615 strval = "http";
616#endif
617 }
618 if (buf) {
619 ast_copy_string(buf, strval, len);
620 } else {
621 ast_str_set(bufstr, 0, "%s", strval);
622 }
623 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
624 const char *strval = "unknown";
625 if ((long) cur->value == HASHCOMPAT_LEGACY) {
626 strval = "legacy";
627 } else if ((long) cur->value == HASHCOMPAT_YES) {
628 strval = "yes";
629 } else if ((long) cur->value == HASHCOMPAT_NO) {
630 strval = "no";
631 }
632 if (buf) {
633 ast_copy_string(buf, strval, len);
634 } else {
635 ast_str_set(bufstr, 0, "%s", strval);
636 }
637 }
638 break;
639 }
640 }
642 if (cur) {
643 break;
644 }
645 }
646
647 return cur ? 0 : -1;
648}
649
650static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
651{
652 return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
653}
654
655static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
656{
657 return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
658}
659
660/*! \brief Callback data passed to \ref WriteMemoryCallback */
662 /*! \brief If a string is being built, the string buffer */
663 struct ast_str *str;
664 /*! \brief The max size of \ref str */
665 ssize_t len;
666 /*! \brief If a file is being retrieved, the file to write to */
667 FILE *out_file;
668};
669
670static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
671{
672 register int realsize = 0;
673 struct curl_write_callback_data *cb_data = data;
674
675 if (cb_data->str) {
676 realsize = size * nmemb;
677 ast_str_append_substr(&cb_data->str, 0, ptr, realsize);
678 } else if (cb_data->out_file) {
679 realsize = fwrite(ptr, size, nmemb, cb_data->out_file);
680 }
681
682 return realsize;
683}
684
685static int curl_instance_init(void *data)
686{
687 CURL **curl = data;
688
689 if (!(*curl = curl_easy_init()))
690 return -1;
691
692 curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
693 curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
694 curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
695 curl_easy_setopt(*curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
696
697 return 0;
698}
699
700static void curl_instance_cleanup(void *data)
701{
702 CURL **curl = data;
703
704 curl_easy_cleanup(*curl);
705
706 ast_free(data);
707}
708
711
712/*!
713 * \brief Check for potential HTTP injection risk.
714 *
715 * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
716 * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
717 * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
718 * requests rather than as a malformed URL.
719 *
720 * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
721 * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
722 * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
723 *
724 * \param url The URL to check for vulnerability
725 * \retval 0 The URL is not vulnerable
726 * \retval 1 The URL is vulnerable.
727 */
728static int url_is_vulnerable(const char *url)
729{
730 if (strpbrk(url, "\r\n")) {
731 return 1;
732 }
733
734 return 0;
735}
736
737struct curl_args {
738 const char *url;
739 const char *postdata;
741};
742
743static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
744{
745 struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
746 int ret = 0;
747 long http_code = 0; /* read curl response */
748 size_t i;
749 struct ast_vector_int hasfailurecode = { NULL };
750 char *failurecodestrings,*found;
751 CURL **curl;
752 struct curl_settings *cur;
753 struct curl_slist *headers = NULL;
754 struct ast_datastore *store = NULL;
755 int hashcompat = 0;
757 char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
758
759 if (!escapebuf) {
760 return -1;
761 }
762
763 if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
764 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
765 return -1;
766 }
767
768 if (url_is_vulnerable(args->url)) {
769 ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args->url);
770 return -1;
771 }
772
773 if (chan) {
775 }
776
777 AST_VECTOR_INIT(&hasfailurecode, 0); /*Initialize vector*/
780 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
781 hashcompat = (long) cur->value;
782 } else if (cur->key == CURLOPT_HTTPHEADER) {
783 headers = curl_slist_append(headers, (char*) cur->value);
784 } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) {
785 failurecodestrings = (char*) cur->value;
786 while( (found = strsep(&failurecodestrings, ",")) != NULL) {
787 AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
788 }
789 } else {
790 curl_easy_setopt(*curl, cur->key, cur->value);
791 }
792 }
794
795 if (chan) {
796 ast_channel_lock(chan);
798 ast_channel_unlock(chan);
799 if (store) {
800 list = store->data;
801 AST_LIST_LOCK(list);
802 AST_LIST_TRAVERSE(list, cur, list) {
803 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
804 hashcompat = (long) cur->value;
805 } else if (cur->key == CURLOPT_HTTPHEADER) {
806 headers = curl_slist_append(headers, (char*) cur->value);
807 } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) {
808 failurecodestrings = (char*) cur->value;
809 while( (found = strsep(&failurecodestrings, ",")) != NULL) {
810 AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
811 }
812 } else {
813 curl_easy_setopt(*curl, cur->key, cur->value);
814 }
815 }
816 }
817 }
818
819 curl_easy_setopt(*curl, CURLOPT_URL, args->url);
820 curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &args->cb_data);
821
822 if (args->postdata) {
823 curl_easy_setopt(*curl, CURLOPT_POST, 1);
824 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
825 }
826
827 /* Always assign the headers - even when NULL - in case we had
828 * custom headers the last time we used this shared cURL
829 * instance */
830 curl_easy_setopt(*curl, CURLOPT_HTTPHEADER, headers);
831
832 /* Temporarily assign a buffer for curl to write errors to. */
833 curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
834 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
835
836 if (curl_easy_perform(*curl) != 0) {
837 ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args->url);
838 }
839
840 /* Reset buffer to NULL so curl doesn't try to write to it when the
841 * buffer is deallocated. Documentation is vague about allowing NULL
842 * here, but the source allows it. See: "typecheck: allow NULL to unset
843 * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
844 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
845 curl_easy_getinfo (*curl, CURLINFO_RESPONSE_CODE, &http_code);
846
847 for (i = 0; i < AST_VECTOR_SIZE(&hasfailurecode); ++i) {
848 if (http_code == AST_VECTOR_GET(&hasfailurecode,i)){
849 ast_log(LOG_NOTICE, "%s%sCURL '%s' returned response code (%ld).\n",
850 chan ? ast_channel_name(chan) : "",
851 chan ? ast_channel_name(chan) : ": ",
852 args->url,
853 http_code);
854 ret=-1;
855 break;
856 }
857 }
858 AST_VECTOR_FREE(&hasfailurecode); /* Release the vector*/
859
860 if (store) {
861 AST_LIST_UNLOCK(list);
862 }
863 curl_slist_free_all(headers);
864
865 if (args->postdata) {
866 curl_easy_setopt(*curl, CURLOPT_POST, 0);
867 }
868
869 if (args->cb_data.str && ast_str_strlen(args->cb_data.str)) {
870 ast_str_trim_blanks(args->cb_data.str);
871
872 ast_debug(3, "CURL returned str='%s'\n", ast_str_buffer(args->cb_data.str));
873 if (hashcompat) {
874 char *remainder = ast_str_buffer(args->cb_data.str);
875 char *piece;
876 struct ast_str *fields = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
877 struct ast_str *values = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
878 int rowcount = 0;
879 while (fields && values && (piece = strsep(&remainder, "&"))) {
880 char *name = strsep(&piece, "=");
882 if (piece) {
883 ast_uri_decode(piece, mode);
884 }
885 ast_uri_decode(name, mode);
886 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
887 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
888 rowcount++;
889 }
890 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
891 ast_str_set(&args->cb_data.str, 0, "%s", ast_str_buffer(values));
892 ast_free(fields);
894 }
895 }
896
897 if (chan) {
899 }
900
901 return ret;
902}
903
904static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
905{
906 struct curl_args curl_params = { 0, };
907 int res;
908
912 );
913
915
916 if (ast_strlen_zero(info)) {
917 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
918 return -1;
919 }
920
921 curl_params.url = args.url;
922 curl_params.postdata = args.postdata;
923 curl_params.cb_data.str = ast_str_create(16);
924 if (!curl_params.cb_data.str) {
925 return -1;
926 }
927
928 res = acf_curl_helper(chan, &curl_params);
929 ast_str_set(buf, len, "%s", ast_str_buffer(curl_params.cb_data.str));
930 ast_free(curl_params.cb_data.str);
931
932 return res;
933}
934
935static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
936{
937 struct curl_args curl_params = { 0, };
938 int res;
939 char *args_value = ast_strdupa(value);
941 AST_APP_ARG(file_path);
942 );
943
944 AST_STANDARD_APP_ARGS(args, args_value);
945
946 if (ast_strlen_zero(name)) {
947 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
948 return -1;
949 }
950
951 if (ast_strlen_zero(args.file_path)) {
952 ast_log(LOG_WARNING, "CURL requires a file to write\n");
953 return -1;
954 }
955
956 curl_params.url = name;
957 curl_params.cb_data.out_file = fopen(args.file_path, "w");
958 if (!curl_params.cb_data.out_file) {
959 ast_log(LOG_WARNING, "Failed to open file %s: %s (%d)\n",
960 args.file_path,
961 strerror(errno),
962 errno);
963 return -1;
964 }
965
966 res = acf_curl_helper(chan, &curl_params);
967
968 fclose(curl_params.cb_data.out_file);
969
970 return res;
971}
972
974 .name = "CURL",
975 .read2 = acf_curl_exec,
976 .write = acf_curl_write,
977};
978
980 .name = "CURLOPT",
981 .read = acf_curlopt_read,
982 .read2 = acf_curlopt_read2,
983 .write = acf_curlopt_write,
984};
985
986#ifdef TEST_FRAMEWORK
987AST_TEST_DEFINE(vulnerable_url)
988{
989 const char *bad_urls [] = {
990 "http://example.com\r\nDELETE http://example.com/everything",
991 "http://example.com\rDELETE http://example.com/everything",
992 "http://example.com\nDELETE http://example.com/everything",
993 "\r\nhttp://example.com",
994 "\rhttp://example.com",
995 "\nhttp://example.com",
996 "http://example.com\r\n",
997 "http://example.com\r",
998 "http://example.com\n",
999 };
1000 const char *good_urls [] = {
1001 "http://example.com",
1002 "http://example.com/%5Cr%5Cn",
1003 };
1004 int i;
1006
1007 switch (cmd) {
1008 case TEST_INIT:
1009 info->name = "vulnerable_url";
1010 info->category = "/funcs/func_curl/";
1011 info->summary = "cURL vulnerable URL test";
1012 info->description =
1013 "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
1014 case TEST_EXECUTE:
1015 break;
1016 }
1017
1018 for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
1019 if (!url_is_vulnerable(bad_urls[i])) {
1020 ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
1021 res = AST_TEST_FAIL;
1022 }
1023 }
1024
1025 for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
1026 if (url_is_vulnerable(good_urls[i])) {
1027 ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
1028 res = AST_TEST_FAIL;
1029 }
1030 }
1031
1032 return res;
1033}
1034#endif
1035
1036static int unload_module(void)
1037{
1038 int res;
1039
1042
1043 AST_TEST_UNREGISTER(vulnerable_url);
1044
1045 return res;
1046}
1047
1048static int load_module(void)
1049{
1050 int res;
1051
1054
1055 AST_TEST_REGISTER(vulnerable_url);
1056
1057 return res;
1058}
1059
1061 .support_level = AST_MODULE_SUPPORT_CORE,
1062 .load = load_module,
1063 .unload = unload_module,
1064 .load_pri = AST_MODPRI_REALTIME_DEPEND2,
1065 .requires = "res_curl",
char * strsep(char **str, const char *delims)
Asterisk main include file. File version handling, generic pbx functions.
#define AST_CURL_USER_AGENT
Definition: asterisk.h:44
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:266
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2414
#define ast_channel_lock(chan)
Definition: channel.h:2970
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
Definition: autoservice.c:200
#define ast_channel_unlock(chan)
Definition: channel.h:2971
struct ast_datastore * ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, const char *uid)
Find a datastore on a channel.
Definition: channel.c:2428
Standard Command Line Interface.
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:85
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
static const char name[]
Definition: format_mp3.c:68
#define CURLOPT_SPECIAL_HASHCOMPAT
Definition: func_curl.c:256
static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
Definition: func_curl.c:743
static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
Definition: func_curl.c:655
static void curl_instance_cleanup(void *data)
Definition: func_curl.c:700
static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
Definition: func_curl.c:904
static const struct ast_datastore_info curl_info
Definition: func_curl.c:262
static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:405
static int curl_instance_init(void *data)
Definition: func_curl.c:685
static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_curl.c:650
#define CURLOPT_SPECIAL_FAILURE_CODE
Definition: func_curl.c:258
static struct ast_threadstorage thread_escapebuf
Definition: func_curl.c:710
static void curlds_free(void *data)
Definition: func_curl.c:275
static int url_is_vulnerable(const char *url)
Check for potential HTTP injection risk.
Definition: func_curl.c:728
optiontype
Definition: func_curl.c:289
@ OT_BOOLEAN
Definition: func_curl.c:290
@ OT_ENUM
Definition: func_curl.c:294
@ OT_STRING
Definition: func_curl.c:293
@ OT_INTEGER
Definition: func_curl.c:291
@ OT_INTEGER_MS
Definition: func_curl.c:292
static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
Definition: func_curl.c:535
static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:935
static struct ast_custom_function acf_curlopt
Definition: func_curl.c:979
static struct ast_threadstorage curl_instance
Definition: func_curl.c:709
struct global_curl_info global_curl_info
static int load_module(void)
Definition: func_curl.c:1048
static int unload_module(void)
Definition: func_curl.c:1036
static struct ast_custom_function acf_curl
Definition: func_curl.c:973
static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
Definition: func_curl.c:303
hashcompat
Definition: func_curl.c:297
@ HASHCOMPAT_NO
Definition: func_curl.c:298
@ HASHCOMPAT_LEGACY
Definition: func_curl.c:300
@ HASHCOMPAT_YES
Definition: func_curl.c:299
static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
Definition: func_curl.c:670
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
Definition: linkedlists.h:291
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
Definition: linkedlists.h:653
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:626
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
Definition: linkedlists.h:173
Asterisk locking-related definitions:
int errno
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:331
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODPRI_REALTIME_DEPEND2
Definition: module.h:336
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
def info(msg)
Core PBX routines and definitions.
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name.
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1568
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1559
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
@ AST_CFE_WRITE
Definition: pbx.h:1552
static char url[512]
#define NULL
Definition: resample.c:96
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: utils.c:2199
char * ast_str_set_escapecommas(struct ast_str **buf, ssize_t maxlen, const char *src, size_t maxsrc)
Set a dynamic string to a non-NULL terminated substring, with escaping of commas.
Definition: strings.h:1069
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
char * ast_str_append_substr(struct ast_str **buf, ssize_t maxlen, const char *src, size_t maxsrc)
Append a non-NULL terminated substring to the end of a dynamic string.
Definition: strings.h:1062
void ast_str_trim_blanks(struct ast_str *buf)
Trims trailing whitespace characters from an ast_str string.
Definition: strings.h:719
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
Main Channel structure associated with a channel.
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
const char * name
Definition: pbx.h:119
Structure for a data store type.
Definition: datastore.h:31
const char * type
Definition: datastore.h:32
Structure for a data store object.
Definition: datastore.h:64
void * data
Definition: datastore.h:66
Structure used to handle boolean flags.
Definition: utils.h:199
Support for dynamic strings.
Definition: strings.h:623
Integer vector definition.
Definition: vector.h:52
const char * postdata
Definition: func_curl.c:739
struct curl_write_callback_data cb_data
Definition: func_curl.c:740
const char * url
Definition: func_curl.c:738
void * value
Definition: func_curl.c:270
struct curl_settings::@168 list
CURLoption key
Definition: func_curl.c:269
Callback data passed to WriteMemoryCallback.
Definition: func_curl.c:661
struct ast_str * str
If a string is being built, the string buffer.
Definition: func_curl.c:663
ssize_t len
The max size of str.
Definition: func_curl.c:665
FILE * out_file
If a file is being retrieved, the file to write to.
Definition: func_curl.c:667
int value
Definition: syslog.c:37
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
ast_test_result_state
Definition: test.h:193
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_FAIL
Definition: test.h:196
const char * args
Definitions to aid in the use of thread local storage.
#define AST_THREADSTORAGE_CUSTOM(a, b, c)
Define a thread storage variable, with custom initialization and cleanup.
void * ast_threadstorage_get(struct ast_threadstorage *ts, size_t init_size)
Retrieve thread storage.
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
Utility functions.
const struct ast_flags ast_uri_http
Definition: utils.c:719
#define ARRAY_LEN(a)
Definition: utils.h:666
void ast_uri_decode(char *s, struct ast_flags spec)
Decode URI, URN, URL (overwrite string)
Definition: utils.c:762
const struct ast_flags ast_uri_http_legacy
Definition: utils.c:720
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680