mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	
		
			
	
	
		
			490 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			490 lines
		
	
	
		
			14 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. | ||
|  |  */ | ||
|  | 
 | ||
|  | #ifndef _CURL_UTILS_H
 | ||
|  | #define _CURL_UTILS_H
 | ||
|  | 
 | ||
|  | #include <curl/curl.h>
 | ||
|  | #include "asterisk/acl.h"
 | ||
|  | 
 | ||
|  | #define AST_CURL_DEFAULT_MAX_HEADER_LEN 2048
 | ||
|  | 
 | ||
|  | #ifndef CURL_WRITEFUNC_ERROR
 | ||
|  | #define CURL_WRITEFUNC_ERROR 0
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | /*! \defgroup curl_wrappers CURL Convenience Wrappers
 | ||
|  |  * @{ | ||
|  | 
 | ||
|  | \section Overwiew Overview | ||
|  | 
 | ||
|  | While libcurl is extremely flexible in what it allows you to do, | ||
|  | that flexibility comes at complexity price. The convenience wrappers | ||
|  | defined here aim to take away some of that complexity for run-of-the-mill | ||
|  | requests. | ||
|  | 
 | ||
|  | \par A Basic Example | ||
|  | 
 | ||
|  | If all you need to do is receive a document into a buffer... | ||
|  | 
 | ||
|  | \code | ||
|  | 	char *url = "https://someurl"; | ||
|  | 	size_t returned_length; | ||
|  | 	char *returned_data = NULL; | ||
|  | 
 | ||
|  | 	long rc = ast_curler_simple(url, &returned_length, &returned_data, NULL); | ||
|  | 
 | ||
|  | 	ast_log(LOG_ERROR, "rc: %ld  size: %zu  doc: %.*s \n", | ||
|  | 		rc, returned_length, | ||
|  | 		(int)returned_length, returned_data); | ||
|  | 	ast_free(returned_data); | ||
|  | \endcode | ||
|  | 
 | ||
|  | If you need the headers as well... | ||
|  | 
 | ||
|  | \code | ||
|  | 	char *url = "https://someurl"; | ||
|  | 	size_t returned_length; | ||
|  | 	char *returned_data = NULL; | ||
|  | 	struct ast_variable *headers; | ||
|  | 
 | ||
|  | 	long rc = ast_curler_simple(url, &returned_length, &returned_data, | ||
|  | 		&headers); | ||
|  | 
 | ||
|  | 	ast_log(LOG_ERROR, "rc: %ld  size: %zu  doc: %.*s \n", | ||
|  | 		rc, returned_length, | ||
|  | 		(int)returned_length, returned_data); | ||
|  | 
 | ||
|  | 	ast_free(returned_data); | ||
|  | 	ast_variables_destroy(headers); | ||
|  | \endcode | ||
|  | 
 | ||
|  | \par A More Complex Example | ||
|  | 
 | ||
|  | If you need more control, you can specify callbacks to capture | ||
|  | the response headers, do something other than write the data | ||
|  | to a memory buffer, or do some special socket manipulation like | ||
|  | check that the server's IP address matched an acl. | ||
|  | 
 | ||
|  | Let's write the data to a file, capture the headers, | ||
|  | and make sure the server's IP address is whitelisted. | ||
|  | 
 | ||
|  | The default callbacks can do that so all we need to do is | ||
|  | supply the data. | ||
|  | 
 | ||
|  | \code | ||
|  | 	char *url = "http://something"; | ||
|  | 
 | ||
|  | 	struct ast_curl_write_data data = { | ||
|  | 		.output = fopen("myfile.txt", "w"); | ||
|  | 		.debug_info = url, | ||
|  | 	}; | ||
|  | 	struct ast_curl_header_data hdata = { | ||
|  | 		.debug_info = url, | ||
|  | 	}; | ||
|  | 	struct ast_curl_open_socket_data osdata = { | ||
|  | 		.acl = my_acl_list, | ||
|  | 		.debug_info = url, | ||
|  | 	}; | ||
|  | 	struct ast_curl_optional_data opdata = { | ||
|  | 		.open_socket_cb = ast_curl_open_socket_cb, | ||
|  | 		.open_socket_data = &osdata, | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	long rc = ast_curler(url, 0, ast_curl_write_default_cb, &data, | ||
|  | 		ast_curl_header_default_cb, &hdata, &opdata); | ||
|  | 
 | ||
|  | 	fclose(data.output); | ||
|  | 	ast_variables_destroy(hdata.headers); | ||
|  | 
 | ||
|  | \endcode | ||
|  | 
 | ||
|  | If you need even more control, you can supply your own | ||
|  | callbacks as well.  This is a silly example of providing | ||
|  | your own write callback.  It's basically what | ||
|  | ast_curler_write_to_file() does. | ||
|  | 
 | ||
|  | \code | ||
|  | static size_t my_write_cb(char *data, size_t size, | ||
|  | 	size_t nmemb, void *client_data) | ||
|  | { | ||
|  | 	FILE *fp = (FILE *)client_data; | ||
|  | 	return fwrite(data, size, nmemb, fp); | ||
|  | } | ||
|  | 
 | ||
|  | static long myfunc(char *url, char *file) | ||
|  | { | ||
|  | 	FILE *fp = fopen(file, "w"); | ||
|  | 	long rc = ast_curler(url, 0, my_write_cb, fp, NULL, NULL, NULL); | ||
|  | 	fclose(fp); | ||
|  | 	return rc; | ||
|  | } | ||
|  | \endcode | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \defgroup HeaderCallback  Header Callback | ||
|  |  * \ingroup curl_wrappers | ||
|  |  * @{ | ||
|  |  * | ||
|  |  * If you need to access the headers returned on the response, | ||
|  |  * you can define a callback that curl will call for every | ||
|  |  * header it receives. | ||
|  |  * | ||
|  |  * Your callback must follow the specification defined for | ||
|  |  * CURLOPT_HEADERFUNCTION and implement the curl_write_callback | ||
|  |  * prototype. | ||
|  |  * | ||
|  |  * The following ast_curl_headers objects compose a default | ||
|  |  * implementation that will accumulate the headers in an | ||
|  |  * ast_variable list. | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * | ||
|  |  * \brief Context structure passed to \ref ast_curl_header_default_cb | ||
|  |  * | ||
|  |  */ | ||
|  | struct curl_header_data { | ||
|  | 	/*!
 | ||
|  | 	 * curl's default max header length is 100k but we rarely | ||
|  | 	 * need that much. It's also possible that a malicious remote | ||
|  | 	 * server could send tons of 100k headers in an attempt to | ||
|  | 	 * cause an out-of-memory condition.  Setting this value | ||
|  | 	 * will cause us to simply ignore any header with a length | ||
|  | 	 * that exceeds it.  If not set, the length defined in | ||
|  | 	 * #AST_CURL_DEFAULT_MAX_HEADER_LEN will be used. | ||
|  | 	 */ | ||
|  | 	size_t max_header_len; | ||
|  | 	/*!
 | ||
|  | 	 * Identifying info placed at the start of log and trace messages. | ||
|  | 	 */ | ||
|  | 	char *debug_info; | ||
|  | 	/*!
 | ||
|  | 	 * This list will contain all the headers received. | ||
|  | 	 * \note curl converts all header names to lower case. | ||
|  | 	 */ | ||
|  | 	struct ast_variable *headers; | ||
|  | 	/*!
 | ||
|  | 	 * \internal | ||
|  | 	 * Private flag used to keep track of whether we're | ||
|  | 	 * capturing headers or not.  We only want them after | ||
|  | 	 * we've seen an HTTP response code in the 2XX range | ||
|  | 	 * and before the blank line that separaes the headers | ||
|  | 	 * from the body. | ||
|  | 	 */ | ||
|  | 	int _capture; | ||
|  | }; | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief A default implementation of a header callback. | ||
|  |  * | ||
|  |  * This is an implementation of #CURLOPT_HEADERFUNCTION that performs | ||
|  |  * basic sanity checks and saves headers in the | ||
|  |  * ast_curl_header_data.headers ast_variable list. | ||
|  |  * | ||
|  |  * The curl prototype for this function is \ref curl_write_callback | ||
|  |  * | ||
|  |  * \warning If you decide to write your own callback, curl doesn't | ||
|  |  * guarantee a terminating NULL in data passed to the callbacks! | ||
|  |  * | ||
|  |  * \param data Will contain a header line that may not be NULL terminated. | ||
|  |  * \param size Always 1. | ||
|  |  * \param nitems The number of bytes in data. | ||
|  |  * \param client_data A pointer to whatever structure you passed to | ||
|  |  *        \ref ast_curler in the \p curl_header_data parameter. | ||
|  |  * | ||
|  |  * \return Number of bytes handled.  Must be (size * nitems) or an | ||
|  |  *         error is signalled. | ||
|  |  */ | ||
|  | size_t curl_header_cb(char *data, size_t size, | ||
|  | 	size_t nitems, void *client_data); | ||
|  | 
 | ||
|  | void curl_header_data_free(void *obj); | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  *  @} | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \defgroup DataCallback  Received Data Callback | ||
|  |  * \ingroup curl_wrappers | ||
|  |  * @{ | ||
|  |  * | ||
|  |  * If you need to do something with the data received other than | ||
|  |  * save it in a memory buffer, you can define a callback that curl | ||
|  |  * will call for each "chunk" of data it receives from the server. | ||
|  |  * | ||
|  |  * Your callback must follow the specification defined for | ||
|  |  * CURLOPT_WRITEFUNCTION and implement the 'curl_write_callback' | ||
|  |  * prototype. | ||
|  |  * | ||
|  |  * The following ast_curl_write objects compose a default | ||
|  |  * implementation that will write the data to any FILE * | ||
|  |  * descriptor you choose. | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief Context structure passed to \ref ast_curl_write_default_cb. | ||
|  |  */ | ||
|  | struct curl_write_data { | ||
|  | 	/*!
 | ||
|  | 	 * If this value is > 0, the request will be cancelled when | ||
|  | 	 * \a bytes_downloaded exceeds it. | ||
|  | 	 */ | ||
|  | 	size_t max_download_bytes; | ||
|  | 	/*!
 | ||
|  | 	 * Where to write to.  Could be anything you can get a FILE* for. | ||
|  | 	 * A file opened with fopen, a buffer opened with open_memstream(), etc. | ||
|  | 	 * Required by \ref ast_curl_write_default_cb. | ||
|  | 	 */ | ||
|  | 	FILE *output; | ||
|  | 	/*!
 | ||
|  | 	 * Identifying info placed at the start of log and trace messages. | ||
|  | 	 */ | ||
|  | 	char *debug_info; | ||
|  | 	/*!
 | ||
|  | 	 * Keeps track of the number of bytes read so far. | ||
|  | 	 * This is updated by the callback regardless of | ||
|  | 	 * whether the output stream is updating | ||
|  | 	 * \ref stream_bytes_downloaded. | ||
|  | 	 */ | ||
|  | 	size_t bytes_downloaded; | ||
|  | 	/*!
 | ||
|  | 	 * A buffer to be used for anything the output stream needs. | ||
|  | 	 * For instance, the address of this member can be passed to | ||
|  | 	 * open_memstream which will update it as it reads data. When | ||
|  | 	 * the memstream is flushed/closed, this will contain all of | ||
|  | 	 * the data read so far.  You must free this yourself with | ||
|  | 	 * ast_std_free(). | ||
|  | 	 */ | ||
|  | 	char *stream_buffer; | ||
|  | 	/*!
 | ||
|  | 	 * Keeps track of the number of bytes read so far. | ||
|  | 	 * Can be used by memstream. | ||
|  | 	 */ | ||
|  | 	size_t stream_bytes_downloaded; | ||
|  | 	/*!
 | ||
|  | 	 * \internal | ||
|  | 	 * Set if we automatically opened a memstream | ||
|  | 	 */ | ||
|  | 	int _internal_memstream; | ||
|  | }; | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief A default implementation of a write data callback. | ||
|  | 
 | ||
|  |  * This is a default implementation of the function described | ||
|  |  * by CURLOPT_WRITEFUNCTION that writes data received to a | ||
|  |  * user-provided FILE *.  This function is called by curl itself | ||
|  |  * when it determines it has enough data to warrant a write. | ||
|  |  * This may be influenced by the value of | ||
|  |  * ast_curl_optional_data.per_write_buffer_size. | ||
|  |  * See the CURLOPT_WRITEFUNCTION documentation for more info. | ||
|  |  * | ||
|  |  * The curl prototype for this function is 'curl_write_callback' | ||
|  |  * | ||
|  |  * \param data Data read by curl. | ||
|  |  * \param size Always 1. | ||
|  |  * \param nitems The number of bytes read. | ||
|  |  * \param client_data A pointer to whatever structure you passed to | ||
|  |  *        \ref ast_curler in the \p curl_write_data parameter. | ||
|  |  * | ||
|  |  * \return Number of bytes handled.  Must be (size * nitems) or an | ||
|  |  *         error is signalled. | ||
|  |  */ | ||
|  | size_t curl_write_cb(char *data, size_t size, size_t nmemb, void *clientp); | ||
|  | 
 | ||
|  | void curl_write_data_free(void *obj); | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  *  @} | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \defgroup OpenSocket  Open Socket Callback | ||
|  |  * \ingroup curl_wrappers | ||
|  |  * @{ | ||
|  |  * | ||
|  |  * If you need to allocate the socket curl uses to make the | ||
|  |  * request yourself or you need to do some checking on the | ||
|  |  * request's resolved IP address, this is the callback for you. | ||
|  |  * | ||
|  |  * Your callback must follow the specification defined for | ||
|  |  * CURLOPT_OPENSOCKETFUNCTION and implement the | ||
|  |  * 'curl_opensocket_callback' prototype. | ||
|  |  * | ||
|  |  * The following ast_open_socket objects compose a default | ||
|  |  * implementation that will not allow requests to servers | ||
|  |  * not whitelisted in the provided ast_acl_list. | ||
|  |  * | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief Context structure passed to \ref ast_curl_open_socket_default_cb | ||
|  |  */ | ||
|  | struct curl_open_socket_data { | ||
|  | 	/*!
 | ||
|  | 	 * The acl should provide a whitelist.  Request to servers | ||
|  | 	 * with addresses not allowed by the acl will be rejected. | ||
|  | 	 */ | ||
|  | 	const struct ast_acl_list *acl; | ||
|  | 	/*!
 | ||
|  | 	 * Identifying info placed at the start of log and trace messages. | ||
|  | 	 */ | ||
|  | 	char *debug_info; | ||
|  | 	/*!
 | ||
|  | 	 * \internal | ||
|  | 	 * Set by the callback and passed to curl. | ||
|  | 	 */ | ||
|  | 	curl_socket_t sockfd; | ||
|  | }; | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief A default implementation of an open socket callback. | ||
|  | 
 | ||
|  |  * This is an implementation of the function described | ||
|  |  * by CURLOPT_OPENSOCKETFUNCTION that checks the request's IP | ||
|  |  * address against a user-supplied ast_acl_list and either rejects | ||
|  |  * the request if the IP address isn't allowed, or opens a socket | ||
|  |  * and returns it to curl. | ||
|  |  * See the CURLOPT_OPENSOCKETFUNCTION documentation for more info. | ||
|  |  * | ||
|  |  * \param client_data A pointer to whatever structure you passed to | ||
|  |  *        \ref ast_curler in the \p curl_write_data parameter. | ||
|  |  * \param purpose Will always be CURLSOCKTYPE_IPCXN | ||
|  |  * \param address The request server's resolved IP address | ||
|  |  * | ||
|  |  * \return A socket opened by socket() or -1 to signal an error. | ||
|  |  */ | ||
|  | curl_socket_t curl_open_socket_cb(void *client_data, | ||
|  | 	curlsocktype purpose, struct curl_sockaddr *address); | ||
|  | 
 | ||
|  | void curl_open_socket_data_free(void *obj); | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  *  @} | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \defgroup OptionalData  Optional Data | ||
|  |  * \ingroup curl_wrappers | ||
|  |  * @{ | ||
|  | 
 | ||
|  |  * \brief Structure pased to \ref ast_curler with infrequenty used | ||
|  |  * control data. | ||
|  |  */ | ||
|  | struct curl_optional_data { | ||
|  | 	/*!
 | ||
|  | 	 * If not set, AST_CURL_USER_AGENT | ||
|  | 	 * (defined in asterisk.h) will be used. | ||
|  | 	 */ | ||
|  | 	const char *user_agent; | ||
|  | 	/*!
 | ||
|  | 	 * Set this to limit the amount of data in each call to | ||
|  | 	 * ast_curl_write_cb_t. | ||
|  | 	 */ | ||
|  | 	size_t per_write_buffer_size; | ||
|  | 	/*!
 | ||
|  | 	 * Set this to a custom function that has a matching | ||
|  | 	 * prototype, set it to \ref ast_curl_open_socket_default_cb | ||
|  | 	 * to use the default callback, or leave it at NULL | ||
|  | 	 * to not use any callback. | ||
|  | 	 * \note Will not be called if open_socket_data is NULL. | ||
|  | 	 */ | ||
|  | 	curl_opensocket_callback curl_open_socket_cb; | ||
|  | 	/*!
 | ||
|  | 	 * Set this to whatever your curl_open_socket_cb needs. | ||
|  | 	 * If using \ref ast_curl_open_socket_default_cb, this MUST | ||
|  | 	 * be set to an \ref ast_curl_open_socket_data structure. | ||
|  | 	 * If set to NULL, curl_open_socket_cb will not be called. | ||
|  | 	 */ | ||
|  | 	void *curl_open_socket_data; | ||
|  | }; | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  *  @} | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \defgroup requests Making Requests | ||
|  |  * \ingroup curl_wrappers | ||
|  |  * @{ | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief Perform a curl request. | ||
|  |  * | ||
|  |  * \param url The URL to request. | ||
|  |  * \param request_timeout If > 0, timeout after this number of seconds. | ||
|  |  * \param curl_write_data A pointer to a \ref curl_write_data structure. If | ||
|  |  *        curl_write_data.output is NULL, open_memstream will be called to | ||
|  |  *        provide one and the resulting data will be available in | ||
|  |  *        curl_write_data.stream_buffer with the number of bytes | ||
|  |  *        retrieved in curl_write_data.stream_bytes_downloaded. | ||
|  |  *        You must free curl_write_data.stream_buffer yourself with | ||
|  |  *        ast_std_free() when you no longer need it. | ||
|  |  * \param curl_header_data A pointer to a \ref ast_curl_header_data structure. | ||
|  |  *        The headers read will be in the curl_header_data.headers | ||
|  |  *        ast_variable list which you must free with ast_variables_destroy() | ||
|  |  *        when you're done with them. | ||
|  |  * \param curl_open_socket_data A pointer to an \ref curl_open_socket_data | ||
|  |  *        structure or NULL if you don't need it. | ||
|  |  * \retval An HTTP response code. | ||
|  |  * \retval -1 for internal error. | ||
|  |  */ | ||
|  | 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); | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief Really simple document retrieval to memory | ||
|  |  * | ||
|  |  * \param url The URL to retrieve | ||
|  |  * \param returned_length Pointer to a size_t to hold document length. | ||
|  |  * \param returned_data Pointer to a buffer which will be updated to | ||
|  |  *        point to the data.  Must be freed with ast_std_free() after use. | ||
|  |  * \param headers Pointer to an ast_variable * that will contain | ||
|  |  *        the response headers. Must be freed with ast_variables_destroy() | ||
|  |  *        Set to NULL if you don't need the headers. | ||
|  |  * \retval An HTTP response code. | ||
|  |  * \retval -1 for internal error. | ||
|  |  */ | ||
|  | long curl_download_to_memory(const char *url, size_t *returned_length, | ||
|  | 	char **returned_data, struct ast_variable **headers); | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  * \brief Really simple document retrieval to file | ||
|  |  * | ||
|  |  * \param url The URL to retrieve. | ||
|  |  * \param filename The filename to save it to. | ||
|  |  * \retval An HTTP response code. | ||
|  |  * \retval -1 for internal error. | ||
|  |  */ | ||
|  | long curl_download_to_file(const char *url, char *filename); | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  *  @} | ||
|  |  */ | ||
|  | 
 | ||
|  | /*!
 | ||
|  |  *  @} | ||
|  |  */ | ||
|  | #endif /* _CURL_UTILS_H */
 |