asterisk/res/res_stir_shaken/curl_utils.c

345 lines
9.0 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2023, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@sangoma.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
#include <curl/curl.h>
#include "asterisk.h"
#include "asterisk/config.h"
#include "curl_utils.h"
void curl_header_data_free(void *obj)
{
struct curl_header_data *cb_data = obj;
if (!cb_data) {
return;
}
ast_variables_destroy(cb_data->headers);
if (cb_data->debug_info) {
ast_free(cb_data->debug_info);
}
ast_free(cb_data);
}
size_t curl_header_cb(char *data, size_t size,
size_t nitems, void *client_data)
{
struct curl_header_data *cb_data = client_data;
size_t realsize = size * nitems;
size_t adjusted_size = realsize;
char *debug_info = S_OR(cb_data->debug_info, "");
char *start = data;
char *colon = NULL;
struct ast_variable *h;
char *header;
char *value;
SCOPE_ENTER(5, "'%s': Header received with %zu bytes\n",
debug_info, realsize);
if (cb_data->max_header_len == 0) {
cb_data->max_header_len = AST_CURL_DEFAULT_MAX_HEADER_LEN;
}
if (realsize > cb_data->max_header_len) {
/*
* Silently ignore any header over the length limit.
*/
SCOPE_EXIT_RTN_VALUE(realsize, "oversize header: %zu > %zu\n",
realsize, cb_data->max_header_len);
}
/* Per CURL: buffer may not be NULL terminated. */
/* Skip blanks */
while (*start && ((unsigned char) *start) < 33 && start < data + realsize) {
start++;
adjusted_size--;
}
if (adjusted_size < strlen("HTTP/") + 1) {
/* this is probably the \r\n\r\n sequence that ends the headers */
cb_data->_capture = 0;
SCOPE_EXIT_RTN_VALUE(realsize, "undersized header. probably end-of-headers marker: %zu\n",
adjusted_size);
}
/*
* We only want headers from a 2XX response
* so don't start capturing until we see
* the 2XX.
*/
if (ast_begins_with(start, "HTTP/")) {
int code;
/*
* HTTP/1.1 200 OK
* We want there to be a version after the HTTP/
* and reason text after the code but we don't care
* what they are.
*/
int rc = sscanf(start, "HTTP/%*s %d %*s", &code);
if (rc == 1) {
if (code / 100 == 2) {
cb_data->_capture = 1;
}
}
SCOPE_EXIT_RTN_VALUE(realsize, "HTTP response code: %d\n",
code);
}
if (!cb_data->_capture) {
SCOPE_EXIT_RTN_VALUE(realsize, "not capturing\n");
}
header = ast_alloca(adjusted_size + 1);
ast_copy_string(header, start, adjusted_size + 1);
/* We have a NULL terminated string now */
colon = strchr(header, ':');
if (!colon) {
SCOPE_EXIT_RTN_VALUE(realsize, "No colon in the header. Weird\n");
}
*colon++ = '\0';
value = colon;
value = ast_skip_blanks(ast_trim_blanks(value));
h = ast_variable_new(header, value, __FILE__);
if (!h) {
SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
"'%s': Unable to allocate memory for header '%s'\n",
debug_info, header);
}
ast_variable_list_append(&cb_data->headers, h);
SCOPE_EXIT_RTN_VALUE(realsize, "header: <%s> value: <%s>",
header, value);
}
void curl_write_data_free(void *obj)
{
struct curl_write_data *cb_data = obj;
if (!cb_data) {
return;
}
if (cb_data->output) {
fclose(cb_data->output);
}
if (cb_data->debug_info) {
ast_free(cb_data->debug_info);
}
ast_std_free(cb_data->stream_buffer);
ast_free(cb_data);
}
size_t curl_write_cb(char *data, size_t size,
size_t nmemb, void *client_data)
{
struct curl_write_data *cb_data = client_data;
size_t realsize = size * nmemb;
size_t bytes_written = 0;
char *debug_info = S_OR(cb_data->debug_info, "");
SCOPE_ENTER(5, "'%s': Writing data chunk of %zu bytes\n",
debug_info, realsize);
if (!cb_data->output) {
cb_data->output = open_memstream(
&cb_data->stream_buffer,
&cb_data->stream_bytes_downloaded);
if (!cb_data->output) {
SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
"'%s': Xfer failed. "
"open_memstream failed: %s\n", debug_info, strerror(errno));
}
cb_data->_internal_memstream = 1;
}
if (cb_data->max_download_bytes > 0 &&
cb_data->stream_bytes_downloaded + realsize >
cb_data->max_download_bytes) {
SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
"'%s': Xfer failed. "
"Exceeded maximum %zu bytes transferred\n", debug_info,
cb_data->max_download_bytes);
}
bytes_written = fwrite(data, 1, realsize, cb_data->output);
cb_data->bytes_downloaded += bytes_written;
if (bytes_written != realsize) {
SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
"'%s': Xfer failed. "
"Expected to write %zu bytes but wrote %zu\n",
debug_info, realsize, bytes_written);
}
SCOPE_EXIT_RTN_VALUE(realsize, "Wrote %zu bytes\n", bytes_written);
}
void curl_open_socket_data_free(void *obj)
{
struct curl_open_socket_data *cb_data = obj;
if (!cb_data) {
return;
}
if (cb_data->debug_info) {
ast_free(cb_data->debug_info);
}
ast_free(cb_data);
}
curl_socket_t curl_open_socket_cb(void *client_data,
curlsocktype purpose, struct curl_sockaddr *address)
{
struct curl_open_socket_data *cb_data = client_data;
char *debug_info = S_OR(cb_data->debug_info, "");
SCOPE_ENTER(5, "'%s': Opening socket\n", debug_info);
if (!ast_acl_list_is_empty((struct ast_acl_list *)cb_data->acl)) {
struct ast_sockaddr ast_address = { {0,} };
ast_sockaddr_copy_sockaddr(&ast_address, &address->addr, address->addrlen);
if (ast_apply_acl((struct ast_acl_list *)cb_data->acl, &ast_address, NULL) != AST_SENSE_ALLOW) {
SCOPE_EXIT_LOG_RTN_VALUE(CURL_SOCKET_BAD, LOG_WARNING,
"'%s': Unable to apply acl\n", debug_info);
}
}
cb_data->sockfd = socket(address->family, address->socktype, address->protocol);
if (cb_data->sockfd < 0) {
SCOPE_EXIT_LOG_RTN_VALUE(CURL_SOCKET_BAD, LOG_WARNING,
"'%s': Failed to open socket: %s\n", debug_info, strerror(errno));
}
SCOPE_EXIT_RTN_VALUE(cb_data->sockfd, "Success");
}
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)
{
RAII_VAR(CURL *, curl, NULL, curl_easy_cleanup);
long http_code = 0;
CURLcode rc;
SCOPE_ENTER(1, "'%s': Retrieving\n", url);
if (ast_strlen_zero(url)) {
SCOPE_EXIT_LOG_RTN_VALUE(500, LOG_ERROR, "'missing': url is missing\n");
}
if (!write_data) {
SCOPE_EXIT_LOG_RTN_VALUE(500, LOG_ERROR, "'%s': Either wite_cb and write_data are missing\n", url);
}
curl = curl_easy_init();
if (!curl) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "'%s': Failed to set up CURL instance\n", url);
}
curl_easy_setopt(curl, CURLOPT_URL, url);
if (request_timeout) {
curl_easy_setopt(curl, CURLOPT_TIMEOUT, request_timeout);
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_data);
if (header_data) {
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_data);
}
curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
if (open_socket_data) {
curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, curl_open_socket_cb);
curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, open_socket_data);
}
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
/*
* ATIS-1000074 specifically says to NOT follow redirections.
*/
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
rc = curl_easy_perform(curl);
if (rc != CURLE_OK) {
char *err = ast_strdupa(curl_easy_strerror(rc));
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "'%s': %s\n", url, err);
}
fflush(write_data->output);
if (write_data->_internal_memstream) {
fclose(write_data->output);
write_data->output = NULL;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
curl = NULL;
SCOPE_EXIT_RTN_VALUE(http_code, "'%s': Done: %ld\n", url, http_code);
}
long curl_download_to_memory(const char *url, size_t *returned_length,
char **returned_data, struct ast_variable **headers)
{
struct curl_write_data data = {
.debug_info = ast_strdupa(url),
};
struct curl_header_data hdata = {
.debug_info = ast_strdupa(url),
};
long rc = curler(url, 0, &data, headers ? &hdata : NULL, NULL);
*returned_length = data.stream_bytes_downloaded;
*returned_data = data.stream_buffer;
if (headers) {
*headers = hdata.headers;
}
return rc;
}
long curl_download_to_file(const char *url, char *filename)
{
FILE *fp = NULL;
long rc = 0;
struct curl_write_data data = {
.debug_info = ast_strdup(url),
};
if (ast_strlen_zero(url) || ast_strlen_zero(filename)) {
ast_log(LOG_ERROR,"url or filename was NULL\n");
return -1;
}
data.output = fopen(filename, "w");
if (!fp) {
ast_log(LOG_ERROR,"Unable to open file '%s': %s\n", filename,
strerror(errno));
return -1;
}
rc = curler(url, 0, &data, NULL, NULL);
fclose(data.output);
ast_free(data.debug_info);
return rc;
}