mirror of https://github.com/asterisk/asterisk
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							345 lines
						
					
					
						
							9.0 KiB
						
					
					
				
			
		
		
	
	
							345 lines
						
					
					
						
							9.0 KiB
						
					
					
				| /*
 | |
|  * 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;
 | |
| }
 | |
| 
 |