Asterisk - The Open Source Telephony Project  21.4.1
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"
77 #include "asterisk/threadstorage.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 
88 static int http_media_cache_config_pre_apply(void);
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. */
113 struct 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. */
122 static 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 
130 static struct aco_type *general_options[] = ACO_TYPES(&general_option);
131 
132 /*! \brief Disposes of the http media cache conf object */
133 static 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. */
141 static 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. */
163 static 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 
184  if (!ast_strlen_zero(cfg->general->curl_protocols)) {
185  ast_log(AST_LOG_ERROR, "'protocols' not supported by linked CURL library. Please recompile against newer CURL.\n");
186  return -1;
187  }
188 
189  if (!ast_strlen_zero(cfg->general->curl_redir_protocols)) {
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  */
210 static 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 
244  value = ast_trim_blanks(ast_skip_blanks(value));
245  header = ast_str_to_lower(header);
246 
247  ast_bucket_file_metadata_set(cb_data->bucket_file, header, value);
248 
249  return realsize;
250 }
251 
252 /*!
253  * \internal \brief The cURL body callback function
254  */
255 static 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  */
268 static void bucket_file_set_expiration(struct ast_bucket_file *bucket_file)
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 
311 static 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  */
333 static 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 
348 static 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 */
357  if (normalize_content_type_header(m)) {
358  res = ast_get_extension_for_mime_type(m, buffer, sizeof(buffer));
359  }
360  }
361 
362  return res;
363 }
364 
365 static 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 
373  struct ast_bucket_metadata *header;
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 
393 static 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 
398  uri = ast_uri_parse(ast_sorcery_object_get_id(bucket_file));
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 
405  path = ast_uri_path(uri);
406  if (!path) {
407  ao2_cleanup(uri);
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);
413  ao2_cleanup(uri);
414  return buffer;
415 }
416 
417 static 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  */
434 static 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"),
438  ao2_cleanup);
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  */
455 static 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"),
459  ao2_cleanup);
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  */
477 static CURL *get_curl_instance(struct curl_bucket_file_data *cb_data)
478 {
479  RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
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  */
536 static 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  */
559 static 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) {
588  bucket_file_set_expiration(bucket_file);
589  bucket_file_set_extension(bucket_file);
590  return 0;
591  } else {
592  ast_log(LOG_WARNING, "Failed to retrieve URL '%s': server returned %ld\n",
593  ast_sorcery_object_get_id(bucket_file), http_code);
594  }
595 
596  return -1;
597 }
598 
599 static 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 = {
607  .bucket_file = bucket_file
608  };
609  char etag_buf[256];
610 
611  if (!bucket_file_expired(bucket_file) && !bucket_file_always_revalidate(bucket_file)) {
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) {
635  bucket_file_set_expiration(bucket_file);
636  return 0;
637  }
638 
639  return 1;
640 }
641 
642 static 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 
650 static void *bucket_http_wizard_retrieve_id(const struct ast_sorcery *sorcery,
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 
687 static 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 
697 static struct ast_sorcery_wizard http_bucket_wizard = {
698  .name = "http",
699  .create = bucket_http_wizard_create,
700  .retrieve_id = bucket_http_wizard_retrieve_id,
701  .delete = bucket_http_wizard_delete,
702  .is_stale = bucket_http_wizard_is_stale,
703 };
704 
705 static struct ast_sorcery_wizard http_bucket_file_wizard = {
706  .name = "http",
707  .create = bucket_http_wizard_create,
708  .retrieve_id = bucket_http_wizard_retrieve_id,
709  .delete = bucket_http_wizard_delete,
710  .is_stale = bucket_http_wizard_is_stale,
711 };
712 
713 static struct ast_sorcery_wizard https_bucket_wizard = {
714  .name = "https",
715  .create = bucket_http_wizard_create,
716  .retrieve_id = bucket_http_wizard_retrieve_id,
717  .delete = bucket_http_wizard_delete,
718  .is_stale = bucket_http_wizard_is_stale,
719 };
720 
721 static struct ast_sorcery_wizard https_bucket_file_wizard = {
722  .name = "https",
723  .create = bucket_http_wizard_create,
724  .retrieve_id = bucket_http_wizard_retrieve_id,
725  .delete = bucket_http_wizard_delete,
726  .is_stale = bucket_http_wizard_is_stale,
727 };
728 
729 static int unload_module(void)
730 {
731  aco_info_destroy(&cfg_info);
733  return 0;
734 }
735 
736 static 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,
749  AST_CURL_USER_AGENT, OPT_STRINGFIELD_T, 0,
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,
761  NULL, OPT_STRINGFIELD_T, 1,
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,
769  NULL, OPT_STRINGFIELD_T, 1,
770  STRFLDSET(struct conf_general_options, curl_protocols));
771 
772  aco_option_register(&cfg_info, "redirect_protocols", ACO_EXACT, general_options,
773  NULL, OPT_STRINGFIELD_T, 1,
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 
798  if (ast_bucket_scheme_register("http", &http_bucket_wizard, &http_bucket_file_wizard,
799  NULL, NULL)) {
800  ast_log(LOG_ERROR, "Failed to register Bucket HTTP wizard scheme implementation\n");
802  }
803 
804  if (ast_bucket_scheme_register("https", &https_bucket_wizard, &https_bucket_file_wizard,
805  NULL, NULL)) {
806  ast_log(LOG_ERROR, "Failed to register Bucket HTTPS wizard scheme implementation\n");
808  }
809 
811 }
812 
813 AST_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  );
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
int curl_dns_cache_timeout
Life-time of CURL DNS cache entries.
static struct aco_type general_option
Mapping of the http media cache conf struct's general to the general context in the config file...
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
Asterisk main include file. File version handling, generic pbx functions.
struct ast_bucket_file * bucket_file
const ast_string_field curl_redir_protocols
Allowed protocols to use for requests. All by default.
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
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. Returns -1 on failure, zero otherwise.
Definition: time.c:152
#define aco_option_register(info, name, matchtype, types, default_val, opt_type, flags,...)
Register a config option.
Stores parsed uri information.
Definition: uri.c:30
Full structure for sorcery.
Definition: sorcery.c:230
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 ao2_global_obj_ref(holder)
Get a reference to the object stored in the global holder.
Definition: astobj2.h:918
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
struct ast_bucket_file * ast_bucket_file_alloc(const char *uri)
Allocate a new bucket file.
Definition: bucket.c:663
Definitions to aid in the use of thread local storage.
The representation of a single configuration file to be processed.
const char * name
Name of the wizard.
Definition: sorcery.h:278
enum aco_type_t type
Bucket File API.
#define ACO_TYPES(...)
A helper macro to ensure that aco_info types always have a sentinel.
All configuration options for http media cache.
int curl_timeout
Request timeout to use.
struct conf_general_options * general
const char * value
Value of the attribute.
Definition: bucket.h:51
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
int aco_info_init(struct aco_info *info)
Initialize an aco_info structure.
General configuration options for http media cache.
void * aco_pending_config(struct aco_info *info)
Get pending config changes.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
static AO2_GLOBAL_OBJ_STATIC(confs)
Locking container for safe configuration access.
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
#define ast_debug(level,...)
Log a DEBUG message.
Bucket file structure, contains reference to file and information about it.
Definition: bucket.h:78
static force_inline char * ast_str_to_lower(char *str)
Convert a string to all lower-case.
Definition: strings.h:1321
Data passed to cURL callbacks.
Their was an error and no changes were applied.
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object)
Delete an object.
Definition: sorcery.c:2238
static long execute_curl_instance(CURL *curl)
Execute the CURL.
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compress two struct timeval instances returning -1, 0, 1 if the first arg is smaller, equal or greater to the second.
Definition: time.h:137
static void conf_destructor(void *obj)
Disposes of the http media cache conf object.
static void * conf_alloc(void)
Creates the http media cache conf object.
void aco_info_destroy(struct aco_info *info)
Destroy an initialized aco_info struct.
#define ao2_global_obj_release(holder)
Release the ao2 object held in the global holder.
Definition: astobj2.h:859
Type for default option handler for bools (ast_true/ast_false)
char * ast_skip_blanks(const char *str)
Gets a pointer to the first non-whitespace character in a string.
Definition: strings.h:161
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
Definition: strings.h:186
int curl_maxredirs
Number of redirects to follow for one request.
static int http_media_cache_config_pre_apply(void)
Pre-apply callback for the config framework.
Bucket metadata structure, AO2 key value pair.
Definition: bucket.h:47
int aco_set_defaults(struct aco_type *type, const char *category, void *obj)
Set all default options of obj.
#define STRFLDSET(type,...)
Convert a struct and a list of stringfield fields to an argument list of field offsets.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
const ast_string_field curl_proxy
User-agent to use for requests.
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
char uri[0]
Definition: uri.c:44
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
Interface for a sorcery wizard.
Definition: sorcery.h:276
const ast_string_field curl_protocols
Proxy to use for requests. None by default.
#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
Type information about a category-level configurable object.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
const char * filename
#define ast_bucket_scheme_register(name, bucket, file, create_cb, destroy_cb)
Register support for a specific scheme.
Definition: bucket.h:137
Type for default option handler for stringfields.
static struct aco_file conf_file
The conf file that's processed for the module.
int curl_followlocation
Follow 3xx redirects automatically.
Type for default option handler for signed integers.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#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
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
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
#define CONFIG_INFO_STANDARD(name, arr, alloc,...)
Declare an aco_info struct with default module and preload values.
char path[PATH_MAX]
Local path to this file.
Definition: bucket.h:95
Sorcery Data Access Layer API.