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