Asterisk - The Open Source Telephony Project GIT-master-3dae2cf
curl_utils.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2023, Sangoma Technologies Corporation
5 *
6 * George Joseph <gjoseph@sangoma.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19#include <curl/curl.h>
20
21#include "asterisk.h"
22#include "asterisk/config.h"
23
24#include "curl_utils.h"
25
26void curl_header_data_free(void *obj)
27{
28 struct curl_header_data *cb_data = obj;
29 if (!cb_data) {
30 return;
31 }
33 if (cb_data->debug_info) {
34 ast_free(cb_data->debug_info);
35 }
36 ast_free(cb_data);
37}
38
39size_t curl_header_cb(char *data, size_t size,
40 size_t nitems, void *client_data)
41{
42 struct curl_header_data *cb_data = client_data;
43 size_t realsize = size * nitems;
44 size_t adjusted_size = realsize;
45 char *debug_info = S_OR(cb_data->debug_info, "");
46 char *start = data;
47 char *colon = NULL;
48 struct ast_variable *h;
49 char *header;
50 char *value;
51 SCOPE_ENTER(5, "'%s': Header received with %zu bytes\n",
52 debug_info, realsize);
53
54 if (cb_data->max_header_len == 0) {
56 }
57
58 if (realsize > cb_data->max_header_len) {
59 /*
60 * Silently ignore any header over the length limit.
61 */
62 SCOPE_EXIT_RTN_VALUE(realsize, "oversize header: %zu > %zu\n",
63 realsize, cb_data->max_header_len);
64 }
65
66 /* Per CURL: buffer may not be NULL terminated. */
67
68 /* Skip blanks */
69 while (*start && ((unsigned char) *start) < 33 && start < data + realsize) {
70 start++;
71 adjusted_size--;
72 }
73
74 if (adjusted_size < strlen("HTTP/") + 1) {
75 /* this is probably the \r\n\r\n sequence that ends the headers */
76 cb_data->_capture = 0;
77 SCOPE_EXIT_RTN_VALUE(realsize, "undersized header. probably end-of-headers marker: %zu\n",
78 adjusted_size);
79 }
80
81 /*
82 * We only want headers from a 2XX response
83 * so don't start capturing until we see
84 * the 2XX.
85 */
86 if (ast_begins_with(start, "HTTP/")) {
87 int code;
88 /*
89 * HTTP/1.1 200 OK
90 * We want there to be a version after the HTTP/
91 * and reason text after the code but we don't care
92 * what they are.
93 */
94 int rc = sscanf(start, "HTTP/%*s %d %*s", &code);
95 if (rc == 1) {
96 if (code / 100 == 2) {
97 cb_data->_capture = 1;
98 }
99 }
100 SCOPE_EXIT_RTN_VALUE(realsize, "HTTP response code: %d\n",
101 code);
102 }
103
104 if (!cb_data->_capture) {
105 SCOPE_EXIT_RTN_VALUE(realsize, "not capturing\n");
106 }
107
108 header = ast_alloca(adjusted_size + 1);
109 ast_copy_string(header, start, adjusted_size + 1);
110
111 /* We have a NULL terminated string now */
112
113 colon = strchr(header, ':');
114 if (!colon) {
115 SCOPE_EXIT_RTN_VALUE(realsize, "No colon in the header. Weird\n");
116 }
117
118 *colon++ = '\0';
119 value = colon;
121
122 h = ast_variable_new(header, value, __FILE__);
123 if (!h) {
125 "'%s': Unable to allocate memory for header '%s'\n",
126 debug_info, header);
127 }
128 ast_variable_list_append(&cb_data->headers, h);
129
130 SCOPE_EXIT_RTN_VALUE(realsize, "header: <%s> value: <%s>",
131 header, value);
132}
133
134void curl_write_data_free(void *obj)
135{
136 struct curl_write_data *cb_data = obj;
137 if (!cb_data) {
138 return;
139 }
140 if (cb_data->output) {
141 fclose(cb_data->output);
142 }
143 if (cb_data->debug_info) {
144 ast_free(cb_data->debug_info);
145 }
146 ast_std_free(cb_data->stream_buffer);
147 ast_free(cb_data);
148}
149
150size_t curl_write_cb(char *data, size_t size,
151 size_t nmemb, void *client_data)
152{
153 struct curl_write_data *cb_data = client_data;
154 size_t realsize = size * nmemb;
155 size_t bytes_written = 0;
156 char *debug_info = S_OR(cb_data->debug_info, "");
157 SCOPE_ENTER(5, "'%s': Writing data chunk of %zu bytes\n",
158 debug_info, realsize);
159
160 if (!cb_data->output) {
161 cb_data->output = open_memstream(
162 &cb_data->stream_buffer,
163 &cb_data->stream_bytes_downloaded);
164 if (!cb_data->output) {
166 "'%s': Xfer failed. "
167 "open_memstream failed: %s\n", debug_info, strerror(errno));
168 }
169 cb_data->_internal_memstream = 1;
170 }
171
172 if (cb_data->max_download_bytes > 0 &&
173 cb_data->stream_bytes_downloaded + realsize >
174 cb_data->max_download_bytes) {
176 "'%s': Xfer failed. "
177 "Exceeded maximum %zu bytes transferred\n", debug_info,
178 cb_data->max_download_bytes);
179 }
180
181 bytes_written = fwrite(data, 1, realsize, cb_data->output);
182 cb_data->bytes_downloaded += bytes_written;
183 if (bytes_written != realsize) {
185 "'%s': Xfer failed. "
186 "Expected to write %zu bytes but wrote %zu\n",
187 debug_info, realsize, bytes_written);
188 }
189
190 SCOPE_EXIT_RTN_VALUE(realsize, "Wrote %zu bytes\n", bytes_written);
191}
192
194{
195 struct curl_open_socket_data *cb_data = obj;
196 if (!cb_data) {
197 return;
198 }
199 if (cb_data->debug_info) {
200 ast_free(cb_data->debug_info);
201 }
202 ast_free(cb_data);
203}
204
205curl_socket_t curl_open_socket_cb(void *client_data,
206 curlsocktype purpose, struct curl_sockaddr *address)
207{
208 struct curl_open_socket_data *cb_data = client_data;
209 char *debug_info = S_OR(cb_data->debug_info, "");
210 SCOPE_ENTER(5, "'%s': Opening socket\n", debug_info);
211
212 if (!ast_acl_list_is_empty((struct ast_acl_list *)cb_data->acl)) {
213 struct ast_sockaddr ast_address = { {0,} };
214
215 ast_sockaddr_copy_sockaddr(&ast_address, &address->addr, address->addrlen);
216
217 if (ast_apply_acl((struct ast_acl_list *)cb_data->acl, &ast_address, NULL) != AST_SENSE_ALLOW) {
218 SCOPE_EXIT_LOG_RTN_VALUE(CURL_SOCKET_BAD, LOG_WARNING,
219 "'%s': Unable to apply acl\n", debug_info);
220 }
221 }
222
223 cb_data->sockfd = socket(address->family, address->socktype, address->protocol);
224 if (cb_data->sockfd < 0) {
225 SCOPE_EXIT_LOG_RTN_VALUE(CURL_SOCKET_BAD, LOG_WARNING,
226 "'%s': Failed to open socket: %s\n", debug_info, strerror(errno));
227 }
228
229 SCOPE_EXIT_RTN_VALUE(cb_data->sockfd, "Success");
230}
231
232long curler(const char *url, int request_timeout,
233 struct curl_write_data *write_data,
235 struct curl_open_socket_data *open_socket_data)
236{
237 RAII_VAR(CURL *, curl, NULL, curl_easy_cleanup);
238 long http_code = 0;
239 CURLcode rc;
240
241 SCOPE_ENTER(1, "'%s': Retrieving\n", url);
242
243 if (ast_strlen_zero(url)) {
244 SCOPE_EXIT_LOG_RTN_VALUE(500, LOG_ERROR, "'missing': url is missing\n");
245 }
246
247 if (!write_data) {
248 SCOPE_EXIT_LOG_RTN_VALUE(500, LOG_ERROR, "'%s': Either wite_cb and write_data are missing\n", url);
249 }
250
251 curl = curl_easy_init();
252 if (!curl) {
253 SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "'%s': Failed to set up CURL instance\n", url);
254 }
255
256 curl_easy_setopt(curl, CURLOPT_URL, url);
257 if (request_timeout) {
258 curl_easy_setopt(curl, CURLOPT_TIMEOUT, request_timeout);
259 }
260 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
261 curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_data);
262
263 if (header_data) {
264 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb);
265 curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_data);
266 }
267
268 curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
269
270 if (open_socket_data) {
271 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, curl_open_socket_cb);
272 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, open_socket_data);
273 }
274
275 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
276 /*
277 * ATIS-1000074 specifically says to NOT follow redirections.
278 */
279 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
280
281 rc = curl_easy_perform(curl);
282 if (rc != CURLE_OK) {
283 char *err = ast_strdupa(curl_easy_strerror(rc));
284 SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "'%s': %s\n", url, err);
285 }
286
287 fflush(write_data->output);
288 if (write_data->_internal_memstream) {
289 fclose(write_data->output);
290 write_data->output = NULL;
291 }
292
293 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
294 curl_easy_cleanup(curl);
295 curl = NULL;
296
297 SCOPE_EXIT_RTN_VALUE(http_code, "'%s': Done: %ld\n", url, http_code);
298}
299
300long curl_download_to_memory(const char *url, size_t *returned_length,
301 char **returned_data, struct ast_variable **headers)
302{
303 struct curl_write_data data = {
305 };
306 struct curl_header_data hdata = {
308 };
309
310 long rc = curler(url, 0, &data, headers ? &hdata : NULL, NULL);
311
312 *returned_length = data.stream_bytes_downloaded;
313 *returned_data = data.stream_buffer;
314 if (headers) {
315 *headers = hdata.headers;
316 }
317
318 return rc;
319}
320
321long curl_download_to_file(const char *url, char *filename)
322{
323 FILE *fp = NULL;
324 long rc = 0;
325 struct curl_write_data data = {
327 };
328
329 if (ast_strlen_zero(url) || ast_strlen_zero(filename)) {
330 ast_log(LOG_ERROR,"url or filename was NULL\n");
331 return -1;
332 }
333 data.output = fopen(filename, "w");
334 if (!fp) {
335 ast_log(LOG_ERROR,"Unable to open file '%s': %s\n", filename,
336 strerror(errno));
337 return -1;
338 }
339 rc = curler(url, 0, &data, NULL, NULL);
340 fclose(data.output);
341 ast_free(data.debug_info);
342 return rc;
343}
344
enum ast_acl_sense ast_apply_acl(struct ast_acl_list *acl_list, const struct ast_sockaddr *addr, const char *purpose)
Apply a set of rules to a given IP address.
Definition: acl.c:799
@ AST_SENSE_ALLOW
Definition: acl.h:38
int ast_acl_list_is_empty(struct ast_acl_list *acl_list)
Determines if an ACL is empty or if it contains entries.
Definition: acl.c:540
Asterisk main include file. File version handling, generic pbx functions.
#define AST_CURL_USER_AGENT
Definition: asterisk.h:44
void ast_std_free(void *ptr)
Definition: astmm.c:1734
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_log
Definition: astobj2.c:42
#define CURL_WRITEFUNC_ERROR
Definition: curl_utils.h:28
#define AST_CURL_DEFAULT_MAX_HEADER_LEN
Definition: curl_utils.h:25
char * address
Definition: f2c.h:59
void curl_write_data_free(void *obj)
Definition: curl_utils.c:134
size_t curl_write_cb(char *data, size_t size, size_t nmemb, void *client_data)
A default implementation of a write data callback.
Definition: curl_utils.c:150
void curl_header_data_free(void *obj)
Definition: curl_utils.c:26
size_t curl_header_cb(char *data, size_t size, size_t nitems, void *client_data)
A default implementation of a header callback.
Definition: curl_utils.c:39
curl_socket_t curl_open_socket_cb(void *client_data, curlsocktype purpose, struct curl_sockaddr *address)
A default implementation of an open socket callback.
Definition: curl_utils.c:205
void curl_open_socket_data_free(void *obj)
Definition: curl_utils.c:193
#define SCOPE_EXIT_RTN_VALUE(__return_value,...)
#define SCOPE_EXIT_LOG_RTN_VALUE(__value, __log_level,...)
#define SCOPE_ENTER(level,...)
long curl_download_to_file(const char *url, char *filename)
Really simple document retrieval to file.
Definition: curl_utils.c:321
long curl_download_to_memory(const char *url, size_t *returned_length, char **returned_data, struct ast_variable **headers)
Really simple document retrieval to memory.
Definition: curl_utils.c:300
long curler(const char *url, int request_timeout, struct curl_write_data *write_data, struct curl_header_data *header_data, struct curl_open_socket_data *open_socket_data)
Perform a curl request.
Definition: curl_utils.c:232
Configuration File Parser.
#define ast_variable_new(name, value, filename)
#define ast_variable_list_append(head, new_var)
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
#define LOG_ERROR
#define LOG_WARNING
int errno
static void ast_sockaddr_copy_sockaddr(struct ast_sockaddr *dst, struct sockaddr *src, socklen_t len)
Copies the data from a sockaddr to an ast_sockaddr.
Definition: netsock2.h:151
static char url[512]
#define NULL
Definition: resample.c:96
#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
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
Definition: strings.h:186
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
char * ast_skip_blanks(const char *str)
Gets a pointer to the first non-whitespace character in a string.
Definition: strings.h:161
Wrapper for an ast_acl linked list.
Definition: acl.h:76
Socket address structure.
Definition: netsock2.h:97
Structure for variables, used for configurations and for channel variables.
Context structure passed to ast_curl_header_default_cb.
Definition: curl_utils.h:163
struct ast_variable * headers
Definition: curl_utils.h:182
size_t max_header_len
Definition: curl_utils.h:173
Context structure passed to ast_curl_open_socket_default_cb.
Definition: curl_utils.h:341
const struct ast_acl_list * acl
Definition: curl_utils.h:346
curl_socket_t sockfd
Definition: curl_utils.h:355
Context structure passed to ast_curl_write_default_cb.
Definition: curl_utils.h:245
size_t bytes_downloaded
Definition: curl_utils.h:267
char * stream_buffer
Definition: curl_utils.h:276
size_t max_download_bytes
Definition: curl_utils.h:250
int _internal_memstream
Definition: curl_utils.h:286
char * debug_info
Definition: curl_utils.h:260
size_t stream_bytes_downloaded
Definition: curl_utils.h:281
Data structure used for ast_sip_push_task_wait_serializer
int value
Definition: syslog.c:37
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941