Asterisk - The Open Source Telephony Project GIT-master-d856a3e
res_http_media_cache.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2015, Matt Jordan
5 *
6 * Matt Jordan <mjordan@digium.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*!
20 * \file
21 * \brief
22 *
23 * \author \verbatim Matt Jordan <mjordan@digium.com> \endverbatim
24 *
25 * HTTP backend for the core media cache
26 */
27
28/*** MODULEINFO
29 <depend>curl</depend>
30 <depend>res_curl</depend>
31 <support_level>core</support_level>
32 ***/
33
34/*** DOCUMENTATION
35 <configInfo name="res_http_media_cache" language="en_US">
36 <synopsis>HTTP media cache</synopsis>
37 <configFile name="http_media_cache.conf">
38 <configObject name="general">
39 <synopsis>General configuration</synopsis>
40 <configOption name="timeout_secs" default="180">
41 <synopsis>The maximum time the transfer is allowed to complete in seconds. See https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html for details.</synopsis>
42 </configOption>
43 <configOption name="user_agent">
44 <synopsis>The HTTP User-Agent to use for requests. See https://curl.se/libcurl/c/CURLOPT_USERAGENT.html for details.</synopsis>
45 </configOption>
46 <configOption name="follow_location" default="1">
47 <synopsis>Follow HTTP 3xx redirects on requests. See https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html for details.</synopsis>
48 </configOption>
49 <configOption name="max_redirects" default="8">
50 <synopsis>The maximum number of redirects to follow. See https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html for details.</synopsis>
51 </configOption>
52 <configOption name="proxy">
53 <synopsis>The proxy to use for requests. See https://curl.se/libcurl/c/CURLOPT_PROXY.html for details.</synopsis>
54 </configOption>
55 <configOption name="protocols">
56 <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>
57 </configOption>
58 <configOption name="redirect_protocols">
59 <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>
60 </configOption>
61 <configOption name="dns_cache_timeout_secs" default="60">
62 <synopsis>The life-time for DNS cache entries. See https://curl.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html for details.</synopsis>
63 </configOption>
64 </configObject>
65 </configFile>
66 </configInfo>
67***/
68
69#include "asterisk.h"
70
71#include <curl/curl.h>
72
73#include "asterisk/file.h"
74#include "asterisk/module.h"
75#include "asterisk/bucket.h"
76#include "asterisk/sorcery.h"
78#include "asterisk/uri.h"
79
80#define MAX_HEADER_LENGTH 1023
81
82#ifdef CURL_AT_LEAST_VERSION
83#if CURL_AT_LEAST_VERSION(7, 85, 0)
84#define AST_CURL_HAS_PROTOCOLS_STR 1
85#endif
86#endif
87
89
90/*! \brief General configuration options for http media cache. */
92 /*! \brief Request timeout to use */
94
95 /*! \brief Follow 3xx redirects automatically. */
97
98 /*! \brief Number of redirects to follow for one request. */
100
101 /*! \brief Life-time of CURL DNS cache entries. */
103
105 AST_STRING_FIELD(curl_useragent); /*! \brief User-agent to use for requests. */
106 AST_STRING_FIELD(curl_proxy); /*! \brief Proxy to use for requests. None by default. */
107 AST_STRING_FIELD(curl_protocols); /*! \brief Allowed protocols to use for requests. All by default. */
108 AST_STRING_FIELD(curl_redir_protocols); /*! \brief Allowed protocols to use on redirect. All by default. */
109 );
110};
111
112/*! \brief All configuration options for http media cache. */
113struct conf {
114 /*! The general section configuration options. */
116};
117
118/*! \brief Locking container for safe configuration access. */
120
121/*! \brief Mapping of the http media cache conf struct's general to the general context in the config file. */
122static struct aco_type general_option = {
123 .type = ACO_GLOBAL,
124 .name = "general",
125 .item_offset = offsetof(struct conf, general),
126 .category = "general",
127 .category_match = ACO_WHITELIST_EXACT,
128};
129
131
132/*! \brief Disposes of the http media cache conf object */
133static void conf_destructor(void *obj)
134{
135 struct conf *cfg = obj;
137 ao2_cleanup(cfg->general);
138}
139
140/*! \brief Creates the http media cache conf object. */
141static void *conf_alloc(void)
142{
143 struct conf *cfg;
144
145 if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) {
146 return NULL;
147 }
148
149 if (!(cfg->general = ao2_alloc(sizeof(*cfg->general), NULL))) {
150 ao2_ref(cfg, -1);
151 return NULL;
152 }
153
154 if (ast_string_field_init(cfg->general, 256)) {
155 ao2_ref(cfg, -1);
156 return NULL;
157 }
158
159 return cfg;
160}
161
162/*! \brief The conf file that's processed for the module. */
163static struct aco_file conf_file = {
164 /*! The config file name. */
165 .filename = "res_http_media_cache.conf",
166 /*! The mapping object types to be processed. */
167 .types = ACO_TYPES(&general_option),
168};
169
171 .pre_apply_config = http_media_cache_config_pre_apply,
172 .files = ACO_FILES(&conf_file));
173
174/*!
175 * \brief Pre-apply callback for the config framework.
176 *
177 * This validates that used options match the ones supported by CURL.
178 */
180{
181#ifndef AST_CURL_HAS_PROTOCOLS_STR
182 struct conf *cfg = aco_pending_config(&cfg_info);
183
185 ast_log(AST_LOG_ERROR, "'protocols' not supported by linked CURL library. Please recompile against newer CURL.\n");
186 return -1;
187 }
188
190 ast_log(AST_LOG_ERROR, "'redirect_protocols' not supported by linked CURL library. Please recompile against newer CURL.\n");
191 return -1;
192 }
193#endif
194
195 return 0;
196}
197
198
199/*! \brief Data passed to cURL callbacks */
201 /*! The \c ast_bucket_file object that caused the operation */
203 /*! File to write data to */
204 FILE *out_file;
205};
206
207/*!
208 * \internal \brief The cURL header callback function
209 */
210static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data)
211{
212 struct curl_bucket_file_data *cb_data = data;
213 size_t realsize;
214 char *value;
215 char *header;
216
217 realsize = size * nitems;
218
219 if (realsize > MAX_HEADER_LENGTH) {
220 ast_log(LOG_WARNING, "cURL header length of '%zu' is too large: max %d\n",
221 realsize, MAX_HEADER_LENGTH);
222 return 0;
223 }
224
225 /* buffer may not be NULL terminated */
226 header = ast_alloca(realsize + 1);
227 memcpy(header, buffer, realsize);
228 header[realsize] = '\0';
229 value = strchr(header, ':');
230 if (!value) {
231 /* Not a header we care about; bail */
232 return realsize;
233 }
234 *value++ = '\0';
235
236 if (strcasecmp(header, "ETag")
237 && strcasecmp(header, "Cache-Control")
238 && strcasecmp(header, "Last-Modified")
239 && strcasecmp(header, "Content-Type")
240 && strcasecmp(header, "Expires")) {
241 return realsize;
242 }
243
246
248
249 return realsize;
250}
251
252/*!
253 * \internal \brief The cURL body callback function
254 */
255static size_t curl_body_callback(void *ptr, size_t size, size_t nitems, void *data)
256{
257 struct curl_bucket_file_data *cb_data = data;
258 size_t realsize;
259
260 realsize = fwrite(ptr, size, nitems, cb_data->out_file);
261
262 return realsize;
263}
264
265/*!
266 * \internal \brief Set the expiration metadata on the bucket file based on HTTP caching rules
267 */
269{
270 struct ast_bucket_metadata *metadata;
271 char time_buf[32], secs[AST_TIME_T_LEN];
272 struct timeval actual_expires = ast_tvnow();
273
274 metadata = ast_bucket_file_metadata_get(bucket_file, "cache-control");
275 if (metadata) {
276 char *str_max_age;
277
278 str_max_age = strstr(metadata->value, "s-maxage");
279 if (!str_max_age) {
280 str_max_age = strstr(metadata->value, "max-age");
281 }
282
283 if (str_max_age) {
284 unsigned int max_age;
285 char *equal = strchr(str_max_age, '=');
286 if (equal && (sscanf(equal + 1, "%30u", &max_age) == 1)) {
287 actual_expires.tv_sec += max_age;
288 }
289 }
290 ao2_ref(metadata, -1);
291 } else {
292 metadata = ast_bucket_file_metadata_get(bucket_file, "expires");
293 if (metadata) {
294 struct tm expires_time;
295
296 strptime(metadata->value, "%a, %d %b %Y %T %z", &expires_time);
297 expires_time.tm_isdst = -1;
298 actual_expires.tv_sec = mktime(&expires_time);
299
300 ao2_ref(metadata, -1);
301 }
302 }
303
304 /* Use 'now' if we didn't get an expiration time */
305 ast_time_t_to_string(actual_expires.tv_sec, secs, sizeof(secs));
306 snprintf(time_buf, sizeof(time_buf), "%30s", secs);
307
308 ast_bucket_file_metadata_set(bucket_file, "__actual_expires", time_buf);
309}
310
311static char *file_extension_from_string(const char *str, char *buffer, size_t capacity)
312{
313 const char *ext;
314
315 ext = strrchr(str, '.');
316 if (ext && ast_get_format_for_file_ext(ext + 1)) {
317 ast_debug(3, "Found extension '%s' at end of string\n", ext);
318 ast_copy_string(buffer, ext, capacity);
319 return buffer;
320 }
321
322 return NULL;
323}
324
325/*!
326 * \internal
327 * \brief Normalize the value of a Content-Type header
328 *
329 * This will trim off any optional parameters after the type/subtype.
330 *
331 * \return 0 if no normalization occurred, otherwise true (non-zero)
332 */
333static int normalize_content_type_header(char *content_type)
334{
335 char *params = strchr(content_type, ';');
336
337 if (params) {
338 *params-- = 0;
339 while (params > content_type && (*params == ' ' || *params == '\t')) {
340 *params-- = 0;
341 }
342 return 1;
343 }
344
345 return 0;
346}
347
348static int derive_extension_from_mime_type(const char *mime_type, char *buffer, size_t capacity)
349{
350 int res = 0;
351
352 /* Compare the provided Content-Type directly, parameters and all */
353 res = ast_get_extension_for_mime_type(mime_type, buffer, sizeof(buffer));
354 if (!res) {
355 char *m = ast_strdupa(mime_type);
356 /* Strip MIME type parameters and then check */
358 res = ast_get_extension_for_mime_type(m, buffer, sizeof(buffer));
359 }
360 }
361
362 return res;
363}
364
365static char *file_extension_from_content_type(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
366{
367 /* Check for the extension based on the MIME type passed in the Content-Type
368 * header.
369 *
370 * If a match is found then retrieve the extension from the supported list
371 * corresponding to the mime-type and use that to rename the file */
372
374
375 header = ast_bucket_file_metadata_get(bucket_file, "content-type");
376 if (!header) {
377 return NULL;
378 }
379
380 if (derive_extension_from_mime_type(header->value, buffer, capacity)) {
381 ast_debug(3, "Derived extension '%s' from MIME type %s\n",
382 buffer,
383 header->value);
384 ao2_ref(header, -1);
385 return buffer;
386 }
387
388 ao2_ref(header, -1);
389
390 return NULL;
391}
392
393static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
394{
395 const char *path;
396 struct ast_uri *uri;
397
399 if (!uri) {
400 ast_log(LOG_ERROR, "Failed to parse URI: %s\n",
401 ast_sorcery_object_get_id(bucket_file));
402 return NULL;
403 }
404
406 if (!path) {
408 return NULL;
409 }
410
411 /* Just parse it as a string like before, but without the extra cruft */
412 buffer = file_extension_from_string(path, buffer, capacity);
414 return buffer;
415}
416
417static void bucket_file_set_extension(struct ast_bucket_file *bucket_file)
418{
419 /* Using Content-Type first allows for the most flexibility for whomever
420 * is serving up the audio file. If that doesn't turn up anything useful
421 * we'll try to parse the URL and use the extension */
422
423 char buffer[64];
424
425 if (file_extension_from_content_type(bucket_file, buffer, sizeof(buffer))
426 || file_extension_from_url_path(bucket_file, buffer, sizeof(buffer))) {
427 ast_bucket_file_metadata_set(bucket_file, "ext", buffer);
428 }
429}
430
431/*! \internal
432 * \brief Return whether or not we should always revalidate against the server
433 */
434static int bucket_file_always_revalidate(struct ast_bucket_file *bucket_file)
435{
436 RAII_VAR(struct ast_bucket_metadata *, metadata,
437 ast_bucket_file_metadata_get(bucket_file, "cache-control"),
439
440 if (!metadata) {
441 return 0;
442 }
443
444 if (strstr(metadata->value, "no-cache")
445 || strstr(metadata->value, "must-revalidate")) {
446 return 1;
447 }
448
449 return 0;
450}
451
452/*! \internal
453 * \brief Return whether or not the item has expired
454 */
455static int bucket_file_expired(struct ast_bucket_file *bucket_file)
456{
457 RAII_VAR(struct ast_bucket_metadata *, metadata,
458 ast_bucket_file_metadata_get(bucket_file, "__actual_expires"),
460 struct timeval current_time = ast_tvnow();
461 struct timeval expires = { .tv_sec = 0, .tv_usec = 0 };
462
463 if (!metadata) {
464 return 1;
465 }
466
467 if ((expires.tv_sec = ast_string_to_time_t(metadata->value)) == -1) {
468 return 1;
469 }
470
471 return ast_tvcmp(current_time, expires) == -1 ? 0 : 1;
472}
473
474/*!
475 * \internal \brief Obtain a CURL handle with common setup options
476 */
477static CURL *get_curl_instance(struct curl_bucket_file_data *cb_data)
478{
480 CURLcode rc;
481 CURL *curl;
482
483 curl = curl_easy_init();
484 if (!curl) {
485 return NULL;
486 }
487
488 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
489 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
490 curl_easy_setopt(curl, CURLOPT_URL, ast_sorcery_object_get_id(cb_data->bucket_file));
491 curl_easy_setopt(curl, CURLOPT_HEADERDATA, cb_data);
492
493 curl_easy_setopt(curl, CURLOPT_TIMEOUT, cfg->general->curl_timeout);
494 curl_easy_setopt(curl, CURLOPT_USERAGENT, cfg->general->curl_useragent);
495 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, cfg->general->curl_followlocation ? 1 : 0);
496 curl_easy_setopt(curl, CURLOPT_MAXREDIRS, cfg->general->curl_maxredirs);
497
498 if (!ast_strlen_zero(cfg->general->curl_proxy)) {
499 curl_easy_setopt(curl, CURLOPT_PROXY, cfg->general->curl_proxy);
500 }
501
502 if (!ast_strlen_zero(cfg->general->curl_protocols)) {
503#ifdef AST_CURL_HAS_PROTOCOLS_STR
504 CURLcode rc = curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, cfg->general->curl_protocols);
505 if (rc != CURLE_OK) {
506 ast_log(AST_LOG_ERROR, "Setting protocols to '%s' failed: %d\n", cfg->general->curl_protocols, rc);
507 curl_easy_cleanup(curl);
508 return NULL;
509 }
510#endif
511 }
512 if (!ast_strlen_zero(cfg->general->curl_redir_protocols)) {
513#ifdef AST_CURL_HAS_PROTOCOLS_STR
514 CURLcode rc = curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, cfg->general->curl_redir_protocols);
515 if (rc != CURLE_OK) {
516 ast_log(AST_LOG_ERROR, "Setting redirect_protocols to '%s' failed: %d\n", cfg->general->curl_redir_protocols, rc);
517 curl_easy_cleanup(curl);
518 return NULL;
519 }
520#endif
521 }
522
523 rc = curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, cfg->general->curl_dns_cache_timeout);
524 if (rc != CURLE_OK) {
525 ast_log(AST_LOG_ERROR, "Setting dns_cache_timeout to '%d' failed: %d\n", cfg->general->curl_dns_cache_timeout, rc);
526 curl_easy_cleanup(curl);
527 return NULL;
528 }
529
530 return curl;
531}
532
533/*!
534 * \brief Execute the CURL
535 */
536static long execute_curl_instance(CURL *curl)
537{
538 char curl_errbuf[CURL_ERROR_SIZE + 1];
539 long http_code;
540
541 curl_errbuf[CURL_ERROR_SIZE] = '\0';
542 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
543
544 if (curl_easy_perform(curl)) {
545 ast_log(LOG_WARNING, "%s\n", curl_errbuf);
546 return -1;
547 }
548
549 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
550
551 curl_easy_cleanup(curl);
552
553 return http_code;
554}
555
556/*!
557 * \internal \brief CURL the URI specified by the bucket_file and store it in the provided path
558 */
559static int bucket_file_run_curl(struct ast_bucket_file *bucket_file)
560{
561 struct curl_bucket_file_data cb_data = {
563 };
564 long http_code;
565 CURL *curl;
566
567 cb_data.out_file = fopen(bucket_file->path, "wb");
568 if (!cb_data.out_file) {
569 ast_log(LOG_WARNING, "Failed to open file '%s' for writing: %s (%d)\n",
570 bucket_file->path, strerror(errno), errno);
571 return -1;
572 }
573
574 curl = get_curl_instance(&cb_data);
575 if (!curl) {
576 fclose(cb_data.out_file);
577 return -1;
578 }
579
580 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_callback);
581 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&cb_data);
582
583 http_code = execute_curl_instance(curl);
584
585 fclose(cb_data.out_file);
586
587 if (http_code / 100 == 2) {
590 return 0;
591 } else {
592 ast_log(LOG_WARNING, "Failed to retrieve URL '%s': server returned %ld\n",
594 }
595
596 return -1;
597}
598
599static int bucket_http_wizard_is_stale(const struct ast_sorcery *sorcery, void *data, void *object)
600{
601 struct ast_bucket_file *bucket_file = object;
602 struct ast_bucket_metadata *metadata;
603 struct curl_slist *header_list = NULL;
604 long http_code;
605 CURL *curl;
606 struct curl_bucket_file_data cb_data = {
608 };
609 char etag_buf[256];
610
612 return 0;
613 }
614
615 /* See if we have an ETag for this item. If not, it's stale. */
616 metadata = ast_bucket_file_metadata_get(bucket_file, "etag");
617 if (!metadata) {
618 return 1;
619 }
620
621 curl = get_curl_instance(&cb_data);
622
623 /* Set the ETag header on our outgoing request */
624 snprintf(etag_buf, sizeof(etag_buf), "If-None-Match: %s", metadata->value);
625 header_list = curl_slist_append(header_list, etag_buf);
626 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
627 curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
628 ao2_ref(metadata, -1);
629
630 http_code = execute_curl_instance(curl);
631
632 curl_slist_free_all(header_list);
633
634 if (http_code == 304) {
636 return 0;
637 }
638
639 return 1;
640}
641
642static int bucket_http_wizard_create(const struct ast_sorcery *sorcery, void *data,
643 void *object)
644{
645 struct ast_bucket_file *bucket_file = object;
646
647 return bucket_file_run_curl(bucket_file);
648}
649
651 void *data, const char *type, const char *id)
652{
653 struct ast_bucket_file *bucket_file;
654
655 if (strcmp(type, "file")) {
656 ast_log(LOG_WARNING, "Failed to create storage: invalid bucket type '%s'\n", type);
657 return NULL;
658 }
659
660 if (ast_strlen_zero(id)) {
661 ast_log(LOG_WARNING, "Failed to create storage: no URI\n");
662 return NULL;
663 }
664
665 bucket_file = ast_bucket_file_alloc(id);
666 if (!bucket_file) {
667 ast_log(LOG_WARNING, "Failed to create storage for '%s'\n", id);
668 return NULL;
669 }
670
671 if (ast_bucket_file_temporary_create(bucket_file)) {
672 ast_log(LOG_WARNING, "Failed to create temporary storage for '%s'\n", id);
673 ast_sorcery_delete(sorcery, bucket_file);
674 ao2_ref(bucket_file, -1);
675 return NULL;
676 }
677
678 if (bucket_file_run_curl(bucket_file)) {
679 ast_sorcery_delete(sorcery, bucket_file);
680 ao2_ref(bucket_file, -1);
681 return NULL;
682 }
683
684 return bucket_file;
685}
686
687static int bucket_http_wizard_delete(const struct ast_sorcery *sorcery, void *data,
688 void *object)
689{
690 struct ast_bucket_file *bucket_file = object;
691
692 unlink(bucket_file->path);
693
694 return 0;
695}
696
698 .name = "http",
700 .retrieve_id = bucket_http_wizard_retrieve_id,
702 .is_stale = bucket_http_wizard_is_stale,
703};
704
706 .name = "http",
708 .retrieve_id = bucket_http_wizard_retrieve_id,
710 .is_stale = bucket_http_wizard_is_stale,
711};
712
714 .name = "https",
716 .retrieve_id = bucket_http_wizard_retrieve_id,
718 .is_stale = bucket_http_wizard_is_stale,
719};
720
722 .name = "https",
724 .retrieve_id = bucket_http_wizard_retrieve_id,
726 .is_stale = bucket_http_wizard_is_stale,
727};
728
729static int unload_module(void)
730{
731 aco_info_destroy(&cfg_info);
733 return 0;
734}
735
736static int load_module(void)
737{
738 if (aco_info_init(&cfg_info)) {
739 aco_info_destroy(&cfg_info);
741 }
742
743
744 aco_option_register(&cfg_info, "timeout_secs", ACO_EXACT, general_options,
745 "180", OPT_INT_T, 0,
746 FLDSET(struct conf_general_options, curl_timeout));
747
748 aco_option_register(&cfg_info, "user_agent", ACO_EXACT, general_options,
750 STRFLDSET(struct conf_general_options, curl_useragent));
751
752 aco_option_register(&cfg_info, "follow_location", ACO_EXACT, general_options,
753 "yes", OPT_BOOL_T, 1,
754 FLDSET(struct conf_general_options, curl_followlocation));
755
756 aco_option_register(&cfg_info, "max_redirects", ACO_EXACT, general_options,
757 "8", OPT_INT_T, 0,
758 FLDSET(struct conf_general_options, curl_maxredirs));
759
760 aco_option_register(&cfg_info, "proxy", ACO_EXACT, general_options,
762 STRFLDSET(struct conf_general_options, curl_proxy));
763
764 aco_option_register(&cfg_info, "dns_cache_timeout_secs", ACO_EXACT, general_options,
765 "60", OPT_INT_T, 0,
766 FLDSET(struct conf_general_options, curl_dns_cache_timeout));
767
768 aco_option_register(&cfg_info, "protocols", ACO_EXACT, general_options,
770 STRFLDSET(struct conf_general_options, curl_protocols));
771
772 aco_option_register(&cfg_info, "redirect_protocols", ACO_EXACT, general_options,
774 STRFLDSET(struct conf_general_options, curl_redir_protocols));
775
776
777 if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
778 struct conf *cfg;
779
780 ast_log(LOG_NOTICE, "Could not load res_http_media_cache config; using defaults\n");
781 cfg = conf_alloc();
782 if (!cfg) {
783 aco_info_destroy(&cfg_info);
785 }
786
787 if (aco_set_defaults(&general_option, "general", cfg->general)) {
788 ast_log(LOG_ERROR, "Failed to initialize res_http_media_cache defaults.\n");
789 ao2_ref(cfg, -1);
790 aco_info_destroy(&cfg_info);
792 }
793
795 ao2_ref(cfg, -1);
796 }
797
799 NULL, NULL)) {
800 ast_log(LOG_ERROR, "Failed to register Bucket HTTP wizard scheme implementation\n");
802 }
803
805 NULL, NULL)) {
806 ast_log(LOG_ERROR, "Failed to register Bucket HTTPS wizard scheme implementation\n");
808 }
809
811}
812
813AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP Media Cache Backend",
814 .support_level = AST_MODULE_SUPPORT_CORE,
815 .load = load_module,
816 .unload = unload_module,
817 .requires = "res_curl",
818 );
const char * str
Definition: app_jack.c:147
Asterisk main include file. File version handling, generic pbx functions.
#define AST_CURL_USER_AGENT
Definition: asterisk.h:44
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_log
Definition: astobj2.c:42
#define ao2_global_obj_replace_unref(holder, obj)
Replace an ao2 object in the global holder, throwing away any old object.
Definition: astobj2.h:901
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_global_obj_ref(holder)
Get a reference to the object stored in the global holder.
Definition: astobj2.h:918
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ao2_global_obj_release(holder)
Release the ao2 object held in the global holder.
Definition: astobj2.h:859
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:409
Bucket File API.
int ast_bucket_file_metadata_set(struct ast_bucket_file *file, const char *name, const char *value)
Set a metadata attribute on a file to a specific value.
Definition: bucket.c:334
struct ast_bucket_file * ast_bucket_file_alloc(const char *uri)
Allocate a new bucket file.
Definition: bucket.c:663
#define ast_bucket_scheme_register(name, bucket, file, create_cb, destroy_cb)
Register support for a specific scheme.
Definition: bucket.h:137
int ast_bucket_file_temporary_create(struct ast_bucket_file *file)
Common file snapshot creation callback for creating a temporary file.
Definition: bucket.c:899
struct ast_bucket_metadata * ast_bucket_file_metadata_get(struct ast_bucket_file *file, const char *name)
Retrieve a metadata attribute from a file.
Definition: bucket.c:359
static const char type[]
Definition: chan_ooh323.c:109
@ ACO_EXACT
int aco_set_defaults(struct aco_type *type, const char *category, void *obj)
Set all default options of obj.
void aco_info_destroy(struct aco_info *info)
Destroy an initialized aco_info struct.
@ ACO_PROCESS_ERROR
Their was an error and no changes were applied.
#define STRFLDSET(type,...)
Convert a struct and a list of stringfield fields to an argument list of field offsets.
int aco_info_init(struct aco_info *info)
Initialize an aco_info structure.
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
#define aco_option_register(info, name, matchtype, types, default_val, opt_type, flags,...)
Register a config option.
#define ACO_FILES(...)
@ OPT_BOOL_T
Type for default option handler for bools (ast_true/ast_false)
@ OPT_INT_T
Type for default option handler for signed integers.
@ OPT_STRINGFIELD_T
Type for default option handler for stringfields.
@ ACO_GLOBAL
@ ACO_WHITELIST_EXACT
void * aco_pending_config(struct aco_info *info)
Get pending config changes.
enum aco_process_status aco_process_config(struct aco_info *info, int reload)
Process a config info via the options registered with an aco_info.
#define ACO_TYPES(...)
A helper macro to ensure that aco_info types always have a sentinel.
Generic File Format Support. Should be included by clients of the file handling routines....
struct ast_format * ast_get_format_for_file_ext(const char *file_ext)
Get the ast_format associated with the given file extension.
Definition: file.c:2006
int ast_get_extension_for_mime_type(const char *mime_type, char *buffer, size_t capacity)
Get a suitable filename extension for the given MIME type.
Definition: file.c:2019
const char * ext
Definition: http.c:150
#define AST_LOG_ERROR
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
int errno
Asterisk module definitions.
@ AST_MODFLAG_DEFAULT
Definition: module.h:329
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
static int http_media_cache_config_pre_apply(void)
Pre-apply callback for the config framework.
static char * file_extension_from_string(const char *str, char *buffer, size_t capacity)
static void bucket_file_set_expiration(struct ast_bucket_file *bucket_file)
static void * conf_alloc(void)
Creates the http media cache conf object.
static int normalize_content_type_header(char *content_type)
static AO2_GLOBAL_OBJ_STATIC(confs)
Locking container for safe configuration access.
static char * file_extension_from_content_type(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
static struct aco_file conf_file
The conf file that's processed for the module.
static void bucket_file_set_extension(struct ast_bucket_file *bucket_file)
static int bucket_file_expired(struct ast_bucket_file *bucket_file)
static struct aco_type general_option
Mapping of the http media cache conf struct's general to the general context in the config file.
static size_t curl_body_callback(void *ptr, size_t size, size_t nitems, void *data)
static struct ast_sorcery_wizard http_bucket_wizard
static void conf_destructor(void *obj)
Disposes of the http media cache conf object.
CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,.pre_apply_config=http_media_cache_config_pre_apply,.files=ACO_FILES(&conf_file))
static struct aco_type * general_options[]
static CURL * get_curl_instance(struct curl_bucket_file_data *cb_data)
static int bucket_http_wizard_delete(const struct ast_sorcery *sorcery, void *data, void *object)
static struct ast_sorcery_wizard http_bucket_file_wizard
static struct ast_sorcery_wizard https_bucket_wizard
static struct ast_sorcery_wizard https_bucket_file_wizard
static int bucket_http_wizard_create(const struct ast_sorcery *sorcery, void *data, void *object)
static int load_module(void)
static int bucket_file_always_revalidate(struct ast_bucket_file *bucket_file)
static char * file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
static void * bucket_http_wizard_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
static long execute_curl_instance(CURL *curl)
Execute the CURL.
static int unload_module(void)
static int bucket_http_wizard_is_stale(const struct ast_sorcery *sorcery, void *data, void *object)
static int bucket_file_run_curl(struct ast_bucket_file *bucket_file)
static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data)
#define MAX_HEADER_LENGTH
static int derive_extension_from_mime_type(const char *mime_type, char *buffer, size_t capacity)
static struct ast_sorcery * sorcery
#define NULL
Definition: resample.c:96
Sorcery Data Access Layer API.
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object)
Delete an object.
Definition: sorcery.c:2238
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
static force_inline char * ast_str_to_lower(char *str)
Convert a string to all lower-case.
Definition: strings.h:1321
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
Definition: strings.h:186
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
char * ast_skip_blanks(const char *str)
Gets a pointer to the first non-whitespace character in a string.
Definition: strings.h:161
The representation of a single configuration file to be processed.
const char * filename
Type information about a category-level configurable object.
enum aco_type_t type
Bucket file structure, contains reference to file and information about it.
Definition: bucket.h:78
char path[PATH_MAX]
Local path to this file.
Definition: bucket.h:95
Bucket metadata structure, AO2 key value pair.
Definition: bucket.h:47
const char * value
Value of the attribute.
Definition: bucket.h:51
Interface for a sorcery wizard.
Definition: sorcery.h:276
const char * name
Name of the wizard.
Definition: sorcery.h:278
Full structure for sorcery.
Definition: sorcery.c:230
Stores parsed uri information.
Definition: uri.c:30
char * path
Definition: uri.c:40
char uri[0]
Definition: uri.c:44
General configuration options for http media cache.
const ast_string_field curl_useragent
int curl_dns_cache_timeout
Life-time of CURL DNS cache entries.
const ast_string_field curl_protocols
const ast_string_field curl_proxy
int curl_maxredirs
Number of redirects to follow for one request.
int curl_followlocation
Follow 3xx redirects automatically.
int curl_timeout
Request timeout to use.
const ast_string_field curl_redir_protocols
All configuration options for http media cache.
struct conf_general_options * general
Data passed to cURL callbacks.
struct ast_bucket_file * bucket_file
const ast_string_field value
int value
Definition: syslog.c:37
Definitions to aid in the use of thread local storage.
int ast_time_t_to_string(time_t time, char *buf, size_t length)
Converts to a string representation of a time_t as decimal seconds since the epoch....
Definition: time.c:152
time_t ast_string_to_time_t(const char *str)
Returns a time_t from a string containing seconds since the epoch.
Definition: time.c:163
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compress two struct timeval instances returning -1, 0, 1 if the first arg is smaller,...
Definition: time.h:137
#define AST_TIME_T_LEN
Definition: time.h:45
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
const char * ast_uri_path(const struct ast_uri *uri)
Retrieve the uri path.
Definition: uri.c:135
struct ast_uri * ast_uri_parse(const char *uri)
Parse the given uri into a structure.
Definition: uri.c:195
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941