2015-01-29 14:38:23 +00:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2015, Matt Jordan
*
* Matt Jordan <mjordan@digium.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.
*/
/*!
* \file
* \brief
*
* \author \verbatim Matt Jordan <mjordan@digium.com> \endverbatim
*
* HTTP backend for the core media cache
*/
/*** MODULEINFO
<depend>curl</depend>
<depend>res_curl</depend>
<support_level>core</support_level>
***/
2022-10-16 17:03:53 +08:00
/*** DOCUMENTATION
<configInfo name="res_http_media_cache" language="en_US">
<synopsis>HTTP media cache</synopsis>
<configFile name="http_media_cache.conf">
<configObject name="general">
<synopsis>General configuration</synopsis>
<configOption name="timeout_secs" default="180">
<synopsis>The maximum time the transfer is allowed to complete in seconds. See https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html for details.</synopsis>
</configOption>
<configOption name="user_agent">
<synopsis>The HTTP User-Agent to use for requests. See https://curl.se/libcurl/c/CURLOPT_USERAGENT.html for details.</synopsis>
</configOption>
<configOption name="follow_location" default="1">
<synopsis>Follow HTTP 3xx redirects on requests. See https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html for details.</synopsis>
</configOption>
<configOption name="max_redirects" default="8">
<synopsis>The maximum number of redirects to follow. See https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html for details.</synopsis>
</configOption>
<configOption name="proxy">
<synopsis>The proxy to use for requests. See https://curl.se/libcurl/c/CURLOPT_PROXY.html for details.</synopsis>
</configOption>
<configOption name="protocols">
<synopsis>The comma separated list of allowed protocols for the request. Available with cURL 7.85.0 or later. See https://curl.se/libcurl/c/CURLOPT_PROTOCOLS_STR.html for details.</synopsis>
</configOption>
<configOption name="redirect_protocols">
<synopsis>The comma separated list of allowed protocols for redirects. Available with cURL 7.85.0 or later. See https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html for details.</synopsis>
</configOption>
<configOption name="dns_cache_timeout_secs" default="60">
<synopsis>The life-time for DNS cache entries. See https://curl.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html for details.</synopsis>
</configOption>
</configObject>
</configFile>
</configInfo>
***/
2015-01-29 14:38:23 +00:00
#include "asterisk.h"
#include <curl/curl.h>
2021-07-02 11:15:05 -04:00
#include "asterisk/file.h"
2015-01-29 14:38:23 +00:00
#include "asterisk/module.h"
#include "asterisk/bucket.h"
#include "asterisk/sorcery.h"
#include "asterisk/threadstorage.h"
2021-07-02 11:15:05 -04:00
#include "asterisk/uri.h"
2015-01-29 14:38:23 +00:00
#define MAX_HEADER_LENGTH 1023
2022-10-16 17:03:53 +08:00
#ifdef CURL_AT_LEAST_VERSION
#if CURL_AT_LEAST_VERSION(7, 85, 0)
#define AST_CURL_HAS_PROTOCOLS_STR 1
#endif
#endif
static int http_media_cache_config_pre_apply ( void );
/*! \brief General configuration options for http media cache. */
struct conf_general_options {
/*! \brief Request timeout to use */
int curl_timeout ;
/*! \brief Follow 3xx redirects automatically. */
int curl_followlocation ;
/*! \brief Number of redirects to follow for one request. */
int curl_maxredirs ;
/*! \brief Life-time of CURL DNS cache entries. */
int curl_dns_cache_timeout ;
AST_DECLARE_STRING_FIELDS (
AST_STRING_FIELD ( curl_useragent ); /*! \brief User-agent to use for requests. */
AST_STRING_FIELD ( curl_proxy ); /*! \brief Proxy to use for requests. None by default. */
AST_STRING_FIELD ( curl_protocols ); /*! \brief Allowed protocols to use for requests. All by default. */
AST_STRING_FIELD ( curl_redir_protocols ); /*! \brief Allowed protocols to use on redirect. All by default. */
);
};
/*! \brief All configuration options for http media cache. */
struct conf {
/*! The general section configuration options. */
struct conf_general_options * general ;
};
/*! \brief Locking container for safe configuration access. */
static AO2_GLOBAL_OBJ_STATIC ( confs );
/*! \brief Mapping of the http media cache conf struct's general to the general context in the config file. */
static struct aco_type general_option = {
. type = ACO_GLOBAL ,
. name = "general" ,
. item_offset = offsetof ( struct conf , general ),
. category = "general" ,
. category_match = ACO_WHITELIST_EXACT ,
};
static struct aco_type * general_options [] = ACO_TYPES ( & general_option );
/*! \brief Disposes of the http media cache conf object */
static void conf_destructor ( void * obj )
{
struct conf * cfg = obj ;
ast_string_field_free_memory ( cfg -> general );
ao2_cleanup ( cfg -> general );
}
/*! \brief Creates the http media cache conf object. */
static void * conf_alloc ( void )
{
struct conf * cfg ;
if ( ! ( cfg = ao2_alloc ( sizeof ( * cfg ), conf_destructor ))) {
return NULL ;
}
if ( ! ( cfg -> general = ao2_alloc ( sizeof ( * cfg -> general ), NULL ))) {
ao2_ref ( cfg , - 1 );
return NULL ;
}
if ( ast_string_field_init ( cfg -> general , 256 )) {
ao2_ref ( cfg , - 1 );
return NULL ;
}
return cfg ;
}
/*! \brief The conf file that's processed for the module. */
static struct aco_file conf_file = {
/*! The config file name. */
. filename = "res_http_media_cache.conf" ,
/*! The mapping object types to be processed. */
. types = ACO_TYPES ( & general_option ),
};
CONFIG_INFO_STANDARD ( cfg_info , confs , conf_alloc ,
. pre_apply_config = http_media_cache_config_pre_apply ,
. files = ACO_FILES ( & conf_file ));
/*!
* \brief Pre-apply callback for the config framework.
*
* This validates that used options match the ones supported by CURL.
*/
static int http_media_cache_config_pre_apply ( void )
{
#ifndef AST_CURL_HAS_PROTOCOLS_STR
struct conf * cfg = aco_pending_config ( & cfg_info );
if ( ! ast_strlen_zero ( cfg -> general -> curl_protocols )) {
ast_log ( AST_LOG_ERROR , "'protocols' not supported by linked CURL library. Please recompile against newer CURL. \n " );
return - 1 ;
}
if ( ! ast_strlen_zero ( cfg -> general -> curl_redir_protocols )) {
ast_log ( AST_LOG_ERROR , "'redirect_protocols' not supported by linked CURL library. Please recompile against newer CURL. \n " );
return - 1 ;
}
#endif
return 0 ;
}
2015-01-29 14:38:23 +00:00
/*! \brief Data passed to cURL callbacks */
struct curl_bucket_file_data {
/*! The \c ast_bucket_file object that caused the operation */
struct ast_bucket_file * bucket_file ;
/*! File to write data to */
FILE * out_file ;
};
/*!
* \internal \brief The cURL header callback function
*/
static size_t curl_header_callback ( char * buffer , size_t size , size_t nitems , void * data )
{
struct curl_bucket_file_data * cb_data = data ;
size_t realsize ;
char * value ;
char * header ;
realsize = size * nitems ;
if ( realsize > MAX_HEADER_LENGTH ) {
ast_log ( LOG_WARNING , "cURL header length of '%zu' is too large: max %d \n " ,
realsize , MAX_HEADER_LENGTH );
return 0 ;
}
/* buffer may not be NULL terminated */
header = ast_alloca ( realsize + 1 );
memcpy ( header , buffer , realsize );
header [ realsize ] = '\0' ;
value = strchr ( header , ':' );
if ( ! value ) {
/* Not a header we care about; bail */
return realsize ;
}
* value ++ = '\0' ;
if ( strcasecmp ( header , "ETag" )
&& strcasecmp ( header , "Cache-Control" )
&& strcasecmp ( header , "Last-Modified" )
2018-02-13 10:55:47 -08:00
&& strcasecmp ( header , "Content-Type" )
2015-01-29 14:38:23 +00:00
&& strcasecmp ( header , "Expires" )) {
return realsize ;
}
value = ast_trim_blanks ( ast_skip_blanks ( value ));
header = ast_str_to_lower ( header );
ast_bucket_file_metadata_set ( cb_data -> bucket_file , header , value );
return realsize ;
}
/*!
* \internal \brief The cURL body callback function
*/
static size_t curl_body_callback ( void * ptr , size_t size , size_t nitems , void * data )
{
struct curl_bucket_file_data * cb_data = data ;
size_t realsize ;
realsize = fwrite ( ptr , size , nitems , cb_data -> out_file );
return realsize ;
}
/*!
* \internal \brief Set the expiration metadata on the bucket file based on HTTP caching rules
*/
static void bucket_file_set_expiration ( struct ast_bucket_file * bucket_file )
{
struct ast_bucket_metadata * metadata ;
2022-02-13 12:06:37 -07:00
char time_buf [ 32 ], secs [ AST_TIME_T_LEN ];
2015-01-29 14:38:23 +00:00
struct timeval actual_expires = ast_tvnow ();
metadata = ast_bucket_file_metadata_get ( bucket_file , "cache-control" );
if ( metadata ) {
char * str_max_age ;
str_max_age = strstr ( metadata -> value , "s-maxage" );
if ( ! str_max_age ) {
str_max_age = strstr ( metadata -> value , "max-age" );
}
if ( str_max_age ) {
unsigned int max_age ;
char * equal = strchr ( str_max_age , '=' );
if ( equal && ( sscanf ( equal + 1 , "%30u" , & max_age ) == 1 )) {
actual_expires . tv_sec += max_age ;
}
}
ao2_ref ( metadata , - 1 );
} else {
metadata = ast_bucket_file_metadata_get ( bucket_file , "expires" );
if ( metadata ) {
struct tm expires_time ;
strptime ( metadata -> value , "%a, %d %b %Y %T %z" , & expires_time );
expires_time . tm_isdst = - 1 ;
actual_expires . tv_sec = mktime ( & expires_time );
ao2_ref ( metadata , - 1 );
}
}
/* Use 'now' if we didn't get an expiration time */
2022-02-13 12:06:37 -07:00
ast_time_t_to_string ( actual_expires . tv_sec , secs , sizeof ( secs ));
snprintf ( time_buf , sizeof ( time_buf ), "%30s" , secs );
2015-01-29 14:38:23 +00:00
ast_bucket_file_metadata_set ( bucket_file , "__actual_expires" , time_buf );
}
2021-07-02 11:15:05 -04:00
static char * file_extension_from_string ( const char * str , char * buffer , size_t capacity )
{
const char * ext ;
ext = strrchr ( str , '.' );
if ( ext && ast_get_format_for_file_ext ( ext + 1 )) {
ast_debug ( 3 , "Found extension '%s' at end of string \n " , ext );
ast_copy_string ( buffer , ext , capacity );
return buffer ;
}
return NULL ;
}
/*!
* \internal
* \brief Normalize the value of a Content-Type header
*
* This will trim off any optional parameters after the type/subtype.
2021-09-10 10:40:00 -04:00
*
* \return 0 if no normalization occurred, otherwise true (non-zero)
2021-07-02 11:15:05 -04:00
*/
2021-09-10 10:40:00 -04:00
static int normalize_content_type_header ( char * content_type )
2021-07-02 11:15:05 -04:00
{
char * params = strchr ( content_type , ';' );
if ( params ) {
* params -- = 0 ;
while ( params > content_type && ( * params == ' ' || * params == '\t' )) {
* params -- = 0 ;
}
2021-09-10 10:40:00 -04:00
return 1 ;
}
return 0 ;
}
static int derive_extension_from_mime_type ( const char * mime_type , char * buffer , size_t capacity )
{
int res = 0 ;
/* Compare the provided Content-Type directly, parameters and all */
res = ast_get_extension_for_mime_type ( mime_type , buffer , sizeof ( buffer ));
if ( ! res ) {
char * m = ast_strdupa ( mime_type );
/* Strip MIME type parameters and then check */
if ( normalize_content_type_header ( m )) {
res = ast_get_extension_for_mime_type ( m , buffer , sizeof ( buffer ));
}
2021-07-02 11:15:05 -04:00
}
2021-09-10 10:40:00 -04:00
return res ;
2021-07-02 11:15:05 -04:00
}
static char * file_extension_from_content_type ( struct ast_bucket_file * bucket_file , char * buffer , size_t capacity )
{
/* Check for the extension based on the MIME type passed in the Content-Type
* header.
*
* If a match is found then retrieve the extension from the supported list
* corresponding to the mime-type and use that to rename the file */
struct ast_bucket_metadata * header ;
header = ast_bucket_file_metadata_get ( bucket_file , "content-type" );
if ( ! header ) {
return NULL ;
}
2021-09-10 10:40:00 -04:00
if ( derive_extension_from_mime_type ( header -> value , buffer , capacity )) {
ast_debug ( 3 , "Derived extension '%s' from MIME type %s \n " ,
buffer ,
header -> value );
ao2_ref ( header , - 1 );
return buffer ;
2021-07-02 11:15:05 -04:00
}
2021-09-10 10:40:00 -04:00
2021-07-02 11:15:05 -04:00
ao2_ref ( header , - 1 );
return NULL ;
}
static char * file_extension_from_url_path ( struct ast_bucket_file * bucket_file , char * buffer , size_t capacity )
{
2022-12-16 15:00:42 +08:00
const char * path ;
2021-07-02 11:15:05 -04:00
struct ast_uri * uri ;
2021-07-02 11:15:05 -04:00
2021-07-02 11:15:05 -04:00
uri = ast_uri_parse ( ast_sorcery_object_get_id ( bucket_file ));
if ( ! uri ) {
ast_log ( LOG_ERROR , "Failed to parse URI: %s \n " ,
2021-07-02 11:15:05 -04:00
ast_sorcery_object_get_id ( bucket_file ));
return NULL ;
}
2022-12-16 15:00:42 +08:00
path = ast_uri_path ( uri );
if ( ! path ) {
ao2_cleanup ( uri );
return NULL ;
}
2021-07-02 11:15:05 -04:00
/* Just parse it as a string like before, but without the extra cruft */
2022-12-16 15:00:42 +08:00
buffer = file_extension_from_string ( path , buffer , capacity );
2021-07-02 11:15:05 -04:00
ao2_cleanup ( uri );
2021-07-02 11:15:05 -04:00
return buffer ;
}
static void bucket_file_set_extension ( struct ast_bucket_file * bucket_file )
{
2021-07-23 12:00:00 -04:00
/* Using Content-Type first allows for the most flexibility for whomever
* is serving up the audio file. If that doesn't turn up anything useful
* we'll try to parse the URL and use the extension */
2021-07-02 11:15:05 -04:00
char buffer [ 64 ];
2021-07-23 12:00:00 -04:00
if ( file_extension_from_content_type ( bucket_file , buffer , sizeof ( buffer ))
2021-07-02 11:15:05 -04:00
|| file_extension_from_url_path ( bucket_file , buffer , sizeof ( buffer ))) {
ast_bucket_file_metadata_set ( bucket_file , "ext" , buffer );
}
}
2015-01-29 14:38:23 +00:00
/*! \internal
* \brief Return whether or not we should always revalidate against the server
*/
static int bucket_file_always_revalidate ( struct ast_bucket_file * bucket_file )
{
RAII_VAR ( struct ast_bucket_metadata * , metadata ,
ast_bucket_file_metadata_get ( bucket_file , "cache-control" ),
ao2_cleanup );
if ( ! metadata ) {
return 0 ;
}
if ( strstr ( metadata -> value , "no-cache" )
|| strstr ( metadata -> value , "must-revalidate" )) {
return 1 ;
}
return 0 ;
}
/*! \internal
* \brief Return whether or not the item has expired
*/
static int bucket_file_expired ( struct ast_bucket_file * bucket_file )
{
RAII_VAR ( struct ast_bucket_metadata * , metadata ,
ast_bucket_file_metadata_get ( bucket_file , "__actual_expires" ),
ao2_cleanup );
struct timeval current_time = ast_tvnow ();
struct timeval expires = { . tv_sec = 0 , . tv_usec = 0 };
if ( ! metadata ) {
return 1 ;
}
2022-02-13 12:06:37 -07:00
if (( expires . tv_sec = ast_string_to_time_t ( metadata -> value )) == - 1 ) {
2015-01-29 14:38:23 +00:00
return 1 ;
}
return ast_tvcmp ( current_time , expires ) == - 1 ? 0 : 1 ;
}
/*!
* \internal \brief Obtain a CURL handle with common setup options
*/
static CURL * get_curl_instance ( struct curl_bucket_file_data * cb_data )
{
2022-10-16 17:03:53 +08:00
RAII_VAR ( struct conf * , cfg , ao2_global_obj_ref ( confs ), ao2_cleanup );
CURLcode rc ;
2015-01-29 14:38:23 +00:00
CURL * curl ;
curl = curl_easy_init ();
if ( ! curl ) {
return NULL ;
}
curl_easy_setopt ( curl , CURLOPT_NOSIGNAL , 1 );
curl_easy_setopt ( curl , CURLOPT_HEADERFUNCTION , curl_header_callback );
curl_easy_setopt ( curl , CURLOPT_URL , ast_sorcery_object_get_id ( cb_data -> bucket_file ));
curl_easy_setopt ( curl , CURLOPT_HEADERDATA , cb_data );
2022-10-16 17:03:53 +08:00
curl_easy_setopt ( curl , CURLOPT_TIMEOUT , cfg -> general -> curl_timeout );
curl_easy_setopt ( curl , CURLOPT_USERAGENT , cfg -> general -> curl_useragent );
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , cfg -> general -> curl_followlocation ? 1 : 0 );
curl_easy_setopt ( curl , CURLOPT_MAXREDIRS , cfg -> general -> curl_maxredirs );
if ( ! ast_strlen_zero ( cfg -> general -> curl_proxy )) {
curl_easy_setopt ( curl , CURLOPT_PROXY , cfg -> general -> curl_proxy );
}
if ( ! ast_strlen_zero ( cfg -> general -> curl_protocols )) {
#ifdef AST_CURL_HAS_PROTOCOLS_STR
CURLcode rc = curl_easy_setopt ( curl , CURLOPT_PROTOCOLS_STR , cfg -> general -> curl_protocols );
if ( rc != CURLE_OK ) {
ast_log ( AST_LOG_ERROR , "Setting protocols to '%s' failed: %d \n " , cfg -> general -> curl_protocols , rc );
curl_easy_cleanup ( curl );
return NULL ;
}
#endif
}
if ( ! ast_strlen_zero ( cfg -> general -> curl_redir_protocols )) {
#ifdef AST_CURL_HAS_PROTOCOLS_STR
CURLcode rc = curl_easy_setopt ( curl , CURLOPT_REDIR_PROTOCOLS_STR , cfg -> general -> curl_redir_protocols );
if ( rc != CURLE_OK ) {
ast_log ( AST_LOG_ERROR , "Setting redirect_protocols to '%s' failed: %d \n " , cfg -> general -> curl_redir_protocols , rc );
curl_easy_cleanup ( curl );
return NULL ;
}
#endif
}
rc = curl_easy_setopt ( curl , CURLOPT_DNS_CACHE_TIMEOUT , cfg -> general -> curl_dns_cache_timeout );
if ( rc != CURLE_OK ) {
ast_log ( AST_LOG_ERROR , "Setting dns_cache_timeout to '%d' failed: %d \n " , cfg -> general -> curl_dns_cache_timeout , rc );
curl_easy_cleanup ( curl );
return NULL ;
}
2015-01-29 14:38:23 +00:00
return curl ;
}
/*!
* \brief Execute the CURL
*/
static long execute_curl_instance ( CURL * curl )
{
char curl_errbuf [ CURL_ERROR_SIZE + 1 ];
long http_code ;
curl_errbuf [ CURL_ERROR_SIZE ] = '\0' ;
curl_easy_setopt ( curl , CURLOPT_ERRORBUFFER , curl_errbuf );
if ( curl_easy_perform ( curl )) {
ast_log ( LOG_WARNING , "%s \n " , curl_errbuf );
return - 1 ;
}
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & http_code );
curl_easy_cleanup ( curl );
return http_code ;
}
/*!
* \internal \brief CURL the URI specified by the bucket_file and store it in the provided path
*/
static int bucket_file_run_curl ( struct ast_bucket_file * bucket_file )
{
struct curl_bucket_file_data cb_data = {
. bucket_file = bucket_file ,
};
long http_code ;
CURL * curl ;
cb_data . out_file = fopen ( bucket_file -> path , "wb" );
if ( ! cb_data . out_file ) {
ast_log ( LOG_WARNING , "Failed to open file '%s' for writing: %s (%d) \n " ,
bucket_file -> path , strerror ( errno ), errno );
return - 1 ;
}
curl = get_curl_instance ( & cb_data );
if ( ! curl ) {
fclose ( cb_data . out_file );
return - 1 ;
}
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , curl_body_callback );
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , ( void * ) & cb_data );
http_code = execute_curl_instance ( curl );
fclose ( cb_data . out_file );
if ( http_code / 100 == 2 ) {
bucket_file_set_expiration ( bucket_file );
2021-07-02 11:15:05 -04:00
bucket_file_set_extension ( bucket_file );
2015-01-29 14:38:23 +00:00
return 0 ;
} else {
ast_log ( LOG_WARNING , "Failed to retrieve URL '%s': server returned %ld \n " ,
ast_sorcery_object_get_id ( bucket_file ), http_code );
}
return - 1 ;
}
static int bucket_http_wizard_is_stale ( const struct ast_sorcery * sorcery , void * data , void * object )
{
struct ast_bucket_file * bucket_file = object ;
struct ast_bucket_metadata * metadata ;
struct curl_slist * header_list = NULL ;
long http_code ;
CURL * curl ;
struct curl_bucket_file_data cb_data = {
. bucket_file = bucket_file
};
char etag_buf [ 256 ];
if ( ! bucket_file_expired ( bucket_file ) && ! bucket_file_always_revalidate ( bucket_file )) {
return 0 ;
}
/* See if we have an ETag for this item. If not, it's stale. */
metadata = ast_bucket_file_metadata_get ( bucket_file , "etag" );
if ( ! metadata ) {
return 1 ;
}
curl = get_curl_instance ( & cb_data );
/* Set the ETag header on our outgoing request */
snprintf ( etag_buf , sizeof ( etag_buf ), "If-None-Match: %s" , metadata -> value );
header_list = curl_slist_append ( header_list , etag_buf );
curl_easy_setopt ( curl , CURLOPT_HTTPHEADER , header_list );
curl_easy_setopt ( curl , CURLOPT_NOBODY , 1L );
ao2_ref ( metadata , - 1 );
http_code = execute_curl_instance ( curl );
curl_slist_free_all ( header_list );
if ( http_code == 304 ) {
bucket_file_set_expiration ( bucket_file );
return 0 ;
}
return 1 ;
}
static int bucket_http_wizard_create ( const struct ast_sorcery * sorcery , void * data ,
void * object )
{
struct ast_bucket_file * bucket_file = object ;
return bucket_file_run_curl ( bucket_file );
}
static void * bucket_http_wizard_retrieve_id ( const struct ast_sorcery * sorcery ,
void * data , const char * type , const char * id )
{
struct ast_bucket_file * bucket_file ;
if ( strcmp ( type , "file" )) {
ast_log ( LOG_WARNING , "Failed to create storage: invalid bucket type '%s' \n " , type );
return NULL ;
}
if ( ast_strlen_zero ( id )) {
ast_log ( LOG_WARNING , "Failed to create storage: no URI \n " );
return NULL ;
}
bucket_file = ast_bucket_file_alloc ( id );
if ( ! bucket_file ) {
ast_log ( LOG_WARNING , "Failed to create storage for '%s' \n " , id );
return NULL ;
}
if ( ast_bucket_file_temporary_create ( bucket_file )) {
ast_log ( LOG_WARNING , "Failed to create temporary storage for '%s' \n " , id );
ast_sorcery_delete ( sorcery , bucket_file );
ao2_ref ( bucket_file , - 1 );
return NULL ;
}
if ( bucket_file_run_curl ( bucket_file )) {
ast_sorcery_delete ( sorcery , bucket_file );
ao2_ref ( bucket_file , - 1 );
return NULL ;
}
return bucket_file ;
}
static int bucket_http_wizard_delete ( const struct ast_sorcery * sorcery , void * data ,
void * object )
{
struct ast_bucket_file * bucket_file = object ;
unlink ( bucket_file -> path );
return 0 ;
}
static struct ast_sorcery_wizard http_bucket_wizard = {
. name = "http" ,
. create = bucket_http_wizard_create ,
. retrieve_id = bucket_http_wizard_retrieve_id ,
. delete = bucket_http_wizard_delete ,
. is_stale = bucket_http_wizard_is_stale ,
};
static struct ast_sorcery_wizard http_bucket_file_wizard = {
. name = "http" ,
. create = bucket_http_wizard_create ,
. retrieve_id = bucket_http_wizard_retrieve_id ,
. delete = bucket_http_wizard_delete ,
. is_stale = bucket_http_wizard_is_stale ,
};
static struct ast_sorcery_wizard https_bucket_wizard = {
. name = "https" ,
. create = bucket_http_wizard_create ,
. retrieve_id = bucket_http_wizard_retrieve_id ,
. delete = bucket_http_wizard_delete ,
. is_stale = bucket_http_wizard_is_stale ,
};
static struct ast_sorcery_wizard https_bucket_file_wizard = {
. name = "https" ,
. create = bucket_http_wizard_create ,
. retrieve_id = bucket_http_wizard_retrieve_id ,
. delete = bucket_http_wizard_delete ,
. is_stale = bucket_http_wizard_is_stale ,
};
static int unload_module ( void )
{
2022-10-16 17:03:53 +08:00
aco_info_destroy ( & cfg_info );
ao2_global_obj_release ( confs );
2015-01-29 14:38:23 +00:00
return 0 ;
}
static int load_module ( void )
{
2022-10-16 17:03:53 +08:00
if ( aco_info_init ( & cfg_info )) {
aco_info_destroy ( & cfg_info );
return AST_MODULE_LOAD_DECLINE ;
}
aco_option_register ( & cfg_info , "timeout_secs" , ACO_EXACT , general_options ,
"180" , OPT_INT_T , 0 ,
FLDSET ( struct conf_general_options , curl_timeout ));
aco_option_register ( & cfg_info , "user_agent" , ACO_EXACT , general_options ,
AST_CURL_USER_AGENT , OPT_STRINGFIELD_T , 0 ,
STRFLDSET ( struct conf_general_options , curl_useragent ));
aco_option_register ( & cfg_info , "follow_location" , ACO_EXACT , general_options ,
"yes" , OPT_BOOL_T , 1 ,
FLDSET ( struct conf_general_options , curl_followlocation ));
aco_option_register ( & cfg_info , "max_redirects" , ACO_EXACT , general_options ,
"8" , OPT_INT_T , 0 ,
FLDSET ( struct conf_general_options , curl_maxredirs ));
aco_option_register ( & cfg_info , "proxy" , ACO_EXACT , general_options ,
NULL , OPT_STRINGFIELD_T , 1 ,
STRFLDSET ( struct conf_general_options , curl_proxy ));
aco_option_register ( & cfg_info , "dns_cache_timeout_secs" , ACO_EXACT , general_options ,
"60" , OPT_INT_T , 0 ,
FLDSET ( struct conf_general_options , curl_dns_cache_timeout ));
aco_option_register ( & cfg_info , "protocols" , ACO_EXACT , general_options ,
NULL , OPT_STRINGFIELD_T , 1 ,
STRFLDSET ( struct conf_general_options , curl_protocols ));
aco_option_register ( & cfg_info , "redirect_protocols" , ACO_EXACT , general_options ,
NULL , OPT_STRINGFIELD_T , 1 ,
STRFLDSET ( struct conf_general_options , curl_redir_protocols ));
if ( aco_process_config ( & cfg_info , 0 ) == ACO_PROCESS_ERROR ) {
struct conf * cfg ;
ast_log ( LOG_NOTICE , "Could not load res_http_media_cache config; using defaults \n " );
cfg = conf_alloc ();
if ( ! cfg ) {
aco_info_destroy ( & cfg_info );
return AST_MODULE_LOAD_DECLINE ;
}
if ( aco_set_defaults ( & general_option , "general" , cfg -> general )) {
ast_log ( LOG_ERROR , "Failed to initialize res_http_media_cache defaults. \n " );
ao2_ref ( cfg , - 1 );
aco_info_destroy ( & cfg_info );
return AST_MODULE_LOAD_DECLINE ;
}
ao2_global_obj_replace_unref ( confs , cfg );
ao2_ref ( cfg , - 1 );
}
2015-01-29 14:38:23 +00:00
if ( ast_bucket_scheme_register ( "http" , & http_bucket_wizard , & http_bucket_file_wizard ,
NULL , NULL )) {
ast_log ( LOG_ERROR , "Failed to register Bucket HTTP wizard scheme implementation \n " );
2017-04-12 06:47:59 -06:00
return AST_MODULE_LOAD_DECLINE ;
2015-01-29 14:38:23 +00:00
}
if ( ast_bucket_scheme_register ( "https" , & https_bucket_wizard , & https_bucket_file_wizard ,
NULL , NULL )) {
ast_log ( LOG_ERROR , "Failed to register Bucket HTTPS wizard scheme implementation \n " );
2017-04-12 06:47:59 -06:00
return AST_MODULE_LOAD_DECLINE ;
2015-01-29 14:38:23 +00:00
}
return AST_MODULE_LOAD_SUCCESS ;
}
2017-11-20 13:54:38 -05:00
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_DEFAULT , "HTTP Media Cache Backend" ,
2015-01-29 14:38:23 +00:00
. support_level = AST_MODULE_SUPPORT_CORE ,
. load = load_module ,
. unload = unload_module ,
2017-12-29 03:57:17 -05:00
. requires = "res_curl" ,
2015-01-29 14:38:23 +00:00
);