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 09:03:53 +00: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 3 xx 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 15:15:05 +00: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 15:15:05 +00:00
# include "asterisk/uri.h"
2015-01-29 14:38:23 +00:00
# define MAX_HEADER_LENGTH 1023
2022-10-16 09:03:53 +00: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 18:55:47 +00: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 19:06:37 +00: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 19:06:37 +00: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 15:15:05 +00: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 14:40:00 +00:00
*
* \ return 0 if no normalization occurred , otherwise true ( non - zero )
2021-07-02 15:15:05 +00:00
*/
2021-09-10 14:40:00 +00:00
static int normalize_content_type_header ( char * content_type )
2021-07-02 15:15:05 +00:00
{
char * params = strchr ( content_type , ' ; ' ) ;
if ( params ) {
* params - - = 0 ;
while ( params > content_type & & ( * params = = ' ' | | * params = = ' \t ' ) ) {
* params - - = 0 ;
}
2021-09-10 14:40:00 +00: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 15:15:05 +00:00
}
2021-09-10 14:40:00 +00:00
return res ;
2021-07-02 15:15:05 +00: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 14:40:00 +00: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 15:15:05 +00:00
}
2021-09-10 14:40:00 +00:00
2021-07-02 15:15:05 +00: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 )
{
res_http_media_cache: Do not crash when there is no extension
Do not crash when a URL has no path component as in this case the
ast_uri_path function will return NULL. Make the code cope with not
having a path.
The below would crash
> media cache create http://google.com /tmp/foo.wav
Thread 1 "asterisk" received signal SIGSEGV, Segmentation fault.
0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
(gdb) bt
#0 0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
#1 0x0000ffff43d43a78 in file_extension_from_string (str=<optimized out>, buffer=buffer@entry=0xffffca9973c0 "",
capacity=capacity@entry=64) at res_http_media_cache.c:288
#2 0x0000ffff43d43bac in file_extension_from_url_path (bucket_file=bucket_file@entry=0x3bf96568,
buffer=buffer@entry=0xffffca9973c0 "", capacity=capacity@entry=64) at res_http_media_cache.c:378
#3 0x0000ffff43d43c74 in bucket_file_set_extension (bucket_file=bucket_file@entry=0x3bf96568) at res_http_media_cache.c:392
#4 0x0000ffff43d43d10 in bucket_file_run_curl (bucket_file=0x3bf96568) at res_http_media_cache.c:555
#5 0x0000ffff43d43f74 in bucket_http_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
at res_http_media_cache.c:613
#6 0x0000000000487638 in bucket_file_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
at bucket.c:191
#7 0x0000000000554408 in sorcery_wizard_create (object_wizard=object_wizard@entry=0x3b9f0718,
details=details@entry=0xffffca9974a8) at sorcery.c:2027
#8 0x0000000000559698 in ast_sorcery_create (sorcery=<optimized out>, object=object@entry=0x3bf96568) at sorcery.c:2077
#9 0x00000000004893a4 in ast_bucket_file_create (file=file@entry=0x3bf96568) at bucket.c:727
#10 0x00000000004f877c in ast_media_cache_create_or_update (uri=0x3bfa1103 "https://google.com",
file_path=0x3bfa1116 "/tmp/foo.wav", metadata=metadata@entry=0x0) at media_cache.c:335
#11 0x00000000004f88ec in media_cache_handle_create_item (e=<optimized out>, cmd=<optimized out>, a=0xffffca9976b8)
at media_cache.c:640
ASTERISK-30375 #close
Change-Id: I6a9433688cb5d3d4be8758b7642d923bdde6c273
2022-12-16 07:00:42 +00:00
const char * path ;
2021-07-02 15:15:05 +00:00
struct ast_uri * uri ;
2021-07-02 15:15:05 +00:00
2021-07-02 15:15:05 +00: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 15:15:05 +00:00
ast_sorcery_object_get_id ( bucket_file ) ) ;
return NULL ;
}
res_http_media_cache: Do not crash when there is no extension
Do not crash when a URL has no path component as in this case the
ast_uri_path function will return NULL. Make the code cope with not
having a path.
The below would crash
> media cache create http://google.com /tmp/foo.wav
Thread 1 "asterisk" received signal SIGSEGV, Segmentation fault.
0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
(gdb) bt
#0 0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
#1 0x0000ffff43d43a78 in file_extension_from_string (str=<optimized out>, buffer=buffer@entry=0xffffca9973c0 "",
capacity=capacity@entry=64) at res_http_media_cache.c:288
#2 0x0000ffff43d43bac in file_extension_from_url_path (bucket_file=bucket_file@entry=0x3bf96568,
buffer=buffer@entry=0xffffca9973c0 "", capacity=capacity@entry=64) at res_http_media_cache.c:378
#3 0x0000ffff43d43c74 in bucket_file_set_extension (bucket_file=bucket_file@entry=0x3bf96568) at res_http_media_cache.c:392
#4 0x0000ffff43d43d10 in bucket_file_run_curl (bucket_file=0x3bf96568) at res_http_media_cache.c:555
#5 0x0000ffff43d43f74 in bucket_http_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
at res_http_media_cache.c:613
#6 0x0000000000487638 in bucket_file_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
at bucket.c:191
#7 0x0000000000554408 in sorcery_wizard_create (object_wizard=object_wizard@entry=0x3b9f0718,
details=details@entry=0xffffca9974a8) at sorcery.c:2027
#8 0x0000000000559698 in ast_sorcery_create (sorcery=<optimized out>, object=object@entry=0x3bf96568) at sorcery.c:2077
#9 0x00000000004893a4 in ast_bucket_file_create (file=file@entry=0x3bf96568) at bucket.c:727
#10 0x00000000004f877c in ast_media_cache_create_or_update (uri=0x3bfa1103 "https://google.com",
file_path=0x3bfa1116 "/tmp/foo.wav", metadata=metadata@entry=0x0) at media_cache.c:335
#11 0x00000000004f88ec in media_cache_handle_create_item (e=<optimized out>, cmd=<optimized out>, a=0xffffca9976b8)
at media_cache.c:640
ASTERISK-30375 #close
Change-Id: I6a9433688cb5d3d4be8758b7642d923bdde6c273
2022-12-16 07:00:42 +00:00
path = ast_uri_path ( uri ) ;
if ( ! path ) {
ao2_cleanup ( uri ) ;
return NULL ;
}
2021-07-02 15:15:05 +00:00
/* Just parse it as a string like before, but without the extra cruft */
res_http_media_cache: Do not crash when there is no extension
Do not crash when a URL has no path component as in this case the
ast_uri_path function will return NULL. Make the code cope with not
having a path.
The below would crash
> media cache create http://google.com /tmp/foo.wav
Thread 1 "asterisk" received signal SIGSEGV, Segmentation fault.
0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
(gdb) bt
#0 0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
#1 0x0000ffff43d43a78 in file_extension_from_string (str=<optimized out>, buffer=buffer@entry=0xffffca9973c0 "",
capacity=capacity@entry=64) at res_http_media_cache.c:288
#2 0x0000ffff43d43bac in file_extension_from_url_path (bucket_file=bucket_file@entry=0x3bf96568,
buffer=buffer@entry=0xffffca9973c0 "", capacity=capacity@entry=64) at res_http_media_cache.c:378
#3 0x0000ffff43d43c74 in bucket_file_set_extension (bucket_file=bucket_file@entry=0x3bf96568) at res_http_media_cache.c:392
#4 0x0000ffff43d43d10 in bucket_file_run_curl (bucket_file=0x3bf96568) at res_http_media_cache.c:555
#5 0x0000ffff43d43f74 in bucket_http_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
at res_http_media_cache.c:613
#6 0x0000000000487638 in bucket_file_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
at bucket.c:191
#7 0x0000000000554408 in sorcery_wizard_create (object_wizard=object_wizard@entry=0x3b9f0718,
details=details@entry=0xffffca9974a8) at sorcery.c:2027
#8 0x0000000000559698 in ast_sorcery_create (sorcery=<optimized out>, object=object@entry=0x3bf96568) at sorcery.c:2077
#9 0x00000000004893a4 in ast_bucket_file_create (file=file@entry=0x3bf96568) at bucket.c:727
#10 0x00000000004f877c in ast_media_cache_create_or_update (uri=0x3bfa1103 "https://google.com",
file_path=0x3bfa1116 "/tmp/foo.wav", metadata=metadata@entry=0x0) at media_cache.c:335
#11 0x00000000004f88ec in media_cache_handle_create_item (e=<optimized out>, cmd=<optimized out>, a=0xffffca9976b8)
at media_cache.c:640
ASTERISK-30375 #close
Change-Id: I6a9433688cb5d3d4be8758b7642d923bdde6c273
2022-12-16 07:00:42 +00:00
buffer = file_extension_from_string ( path , buffer , capacity ) ;
2021-07-02 15:15:05 +00:00
ao2_cleanup ( uri ) ;
2021-07-02 15:15:05 +00:00
return buffer ;
}
static void bucket_file_set_extension ( struct ast_bucket_file * bucket_file )
{
2021-07-23 16:00:00 +00: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 15:15:05 +00:00
char buffer [ 64 ] ;
2021-07-23 16:00:00 +00:00
if ( file_extension_from_content_type ( bucket_file , buffer , sizeof ( buffer ) )
2021-07-02 15:15:05 +00: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 19:06:37 +00: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 09:03:53 +00: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 09:03:53 +00: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 15:15:05 +00: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 09:03:53 +00: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 09:03:53 +00: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 12:47:59 +00: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 12:47:59 +00:00
return AST_MODULE_LOAD_DECLINE ;
2015-01-29 14:38:23 +00:00
}
return AST_MODULE_LOAD_SUCCESS ;
}
2017-11-20 18:54:38 +00: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 08:57:17 +00:00
. requires = " res_curl " ,
2015-01-29 14:38:23 +00:00
) ;