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.
		
		
		
		
		
			
		
			
				
					
					
						
							490 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
	
	
							490 lines
						
					
					
						
							14 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.
 | |
|  */
 | |
| 
 | |
| #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 */
 |