<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/8882">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Joshua Colp: Looks good to me, but someone else must approve
  Richard Mudgett: Looks good to me, approved
  Jenkins2: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Add the ability to read the media file type from HTTP header for playback<br><br>How it works today:<br>media_cache tries to parse out the extension of the media file to be played<br>from the URI provided to Asterisk while caching the file.<br><br>What's expected:<br>Better will be to have Asterisk get extension from other ways too. One of the<br>common ways is to get the type of content from the CONTENT-TYPE header in the<br>HTTP response for fetching the media file using the URI provided.<br><br>Steps to Reproduce:<br>Provide a URL of the form: http://host/media/1234 to Asterisk for media<br>playback. It fails to play and logs show the following error line:<br><br>[Sep 15 15:48:05] WARNING [29148] [C-00000092] file.c:<br>File http://host/media/1234 does not exist in any format<br><br>Scenario this issue is blocking:<br>In the case where the media files are stored in some cloud object store,<br>following can block the media being played via Asterisk:<br><br>Cloud storage generally needs authenticated access to the storage. The way<br>to do that is by using signed URIs. With the signed URIs there's no way to<br>preserve the name of the file.<br>In most cases Cloud storage returns a key to access the object and preserving<br>file name is also not a thing there<br><br>ASTERISK-27286<br><br> Reporter: Gaurav Khurana<br><br>Change-Id: I1b14692a49b2c1ac67688f58757184122e92ba89<br>---<br>M formats/format_pcm.c<br>M formats/format_vox.c<br>M formats/format_wav.c<br>M include/asterisk/file.h<br>M include/asterisk/mod_format.h<br>M main/file.c<br>M main/media_cache.c<br>M res/res_http_media_cache.c<br>8 files changed, 87 insertions(+), 6 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/formats/format_pcm.c b/formats/format_pcm.c<br>index 4e846d7..674c8b5 100644<br>--- a/formats/format_pcm.c<br>+++ b/formats/format_pcm.c<br>@@ -507,6 +507,7 @@<br> static struct ast_format_def pcm_f = {<br>        .name = "pcm",<br>      .exts = "pcm|ulaw|ul|mu|ulw",<br>+      .mime_types = "audio/basic",<br>        .write = pcm_write,<br>   .seek = pcm_seek,<br>     .trunc = pcm_trunc,<br>diff --git a/formats/format_vox.c b/formats/format_vox.c<br>index b63e225..023c409 100644<br>--- a/formats/format_vox.c<br>+++ b/formats/format_vox.c<br>@@ -128,6 +128,7 @@<br> static struct ast_format_def vox_f = {<br>    .name = "vox",<br>      .exts = "vox",<br>+     .mime_types = "audio/x-vox",<br>        .write = vox_write,<br>   .seek = vox_seek,<br>     .trunc = vox_trunc,<br>diff --git a/formats/format_wav.c b/formats/format_wav.c<br>index 81a686e..ec7e3d3 100644<br>--- a/formats/format_wav.c<br>+++ b/formats/format_wav.c<br>@@ -532,6 +532,7 @@<br> static struct ast_format_def wav_f = {<br>    .name = "wav",<br>      .exts = "wav",<br>+     .mime_types = "audio/wav|audio/x-wav",<br>      .open = wav_open,<br>     .rewrite = wav_rewrite,<br>       .write = wav_write,<br>diff --git a/include/asterisk/file.h b/include/asterisk/file.h<br>index c17cb32..1c2c7a8 100644<br>--- a/include/asterisk/file.h<br>+++ b/include/asterisk/file.h<br>@@ -427,6 +427,18 @@<br>  */<br> struct ast_format *ast_get_format_for_file_ext(const char *file_ext);<br> <br>+/*!<br>+ * \brief Get a suitable filename extension for the given MIME type<br>+ *<br>+ * \param mime_type The MIME type for which to find extensions<br>+ * \param buffer A pointer to a buffer to receive the extension<br>+ * \param capacity The size of 'buffer' in bytes<br>+ *<br>+ * \retval 1 if an extension was found for the provided MIME type<br>+ * \retval 0 if the MIME type was not found<br>+ */<br>+int ast_get_extension_for_mime_type(const char *mime_type, char *buffer, size_t capacity);<br>+<br> #if defined(__cplusplus) || defined(c_plusplus)<br> }<br> #endif<br>diff --git a/include/asterisk/mod_format.h b/include/asterisk/mod_format.h<br>index 5f93ea4..6e772f0 100644<br>--- a/include/asterisk/mod_format.h<br>+++ b/include/asterisk/mod_format.h<br>@@ -44,6 +44,7 @@<br>    char name[80];          /*!< Name of format */<br>     char exts[80];          /*!< Extensions (separated by | if more than one)<br>                                           * this format can read.  First is assumed for writing (e.g. .mp3) */<br>+        char mime_types[80]; /*!< MIME Types related to the format (separated by | if more than one)*/<br>     struct ast_format *format;      /*!< Format of frames it uses/provides (one only) */<br>       /*!<br>    * \brief Prepare an input stream for playback.<br>diff --git a/main/file.c b/main/file.c<br>index 8dded81..3ee58b1 100644<br>--- a/main/file.c<br>+++ b/main/file.c<br>@@ -333,19 +333,20 @@<br> <br> /* compare type against the list 'exts' */<br> /* XXX need a better algorithm */<br>-static int exts_compare(const char *exts, const char *type)<br>+static int type_in_list(const char *list, const char *type, int (*cmp)(const char *s1, const char *s2))<br> {<br>-  char tmp[256];<br>-       char *stringp = tmp, *ext;<br>+   char *stringp = ast_strdupa(list), *item;<br> <br>- ast_copy_string(tmp, exts, sizeof(tmp));<br>-     while ((ext = strsep(&stringp, "|"))) {<br>-                if (!strcmp(ext, type))<br>+      while ((item = strsep(&stringp, "|"))) {<br>+               if (!cmp(item, type)) {<br>                       return 1;<br>+            }<br>     }<br> <br>  return 0;<br> }<br>+<br>+#define exts_compare(list, type) (type_in_list((list), (type), strcmp))<br> <br> /*!<br>  * \internal<br>@@ -1926,6 +1927,27 @@<br>    return NULL;<br> }<br> <br>+int ast_get_extension_for_mime_type(const char *mime_type, char *buffer, size_t capacity)<br>+{<br>+  struct ast_format_def *f;<br>+    SCOPED_RDLOCK(lock, &formats.lock);<br>+<br>+   ast_assert(buffer && capacity);<br>+<br>+   AST_RWLIST_TRAVERSE(&formats, f, list) {<br>+         if (type_in_list(f->mime_types, mime_type, strcasecmp)) {<br>+                 size_t item_len = strcspn(f->exts, "|");<br>+                        size_t bytes_written = snprintf(buffer, capacity, ".%.*s", (int) item_len, f->exts);<br>+                    if (bytes_written < capacity) {<br>+                           /* Only return success if we didn't truncate */<br>+                          return 1;<br>+                    }<br>+            }<br>+    }<br>+<br>+ return 0;<br>+}<br>+<br> static struct ast_cli_entry cli_file[] = {<br>         AST_CLI_DEFINE(handle_cli_core_show_file_formats, "Displays file formats")<br> };<br>diff --git a/main/media_cache.c b/main/media_cache.c<br>index e93d1a0..e0a6dbb 100644<br>--- a/main/media_cache.c<br>+++ b/main/media_cache.c<br>@@ -35,6 +35,7 @@<br> #include "asterisk/bucket.h"<br> #include "asterisk/astdb.h"<br> #include "asterisk/cli.h"<br>+#include "asterisk/file.h"<br> #include "asterisk/media_cache.h"<br> <br> /*! The name of the AstDB family holding items in the cache. */<br>@@ -125,6 +126,24 @@<br> <br> /*!<br>  * \internal<br>+ * \brief Normalize the value of a Content-Type header<br>+ *<br>+ * This will trim off any optional parameters after the type/subtype.<br>+ */<br>+static void normalize_content_type_header(char *content_type)<br>+{<br>+ char *params = strchr(content_type, ';');<br>+<br>+ if (params) {<br>+                *params-- = 0;<br>+               while (params > content_type && (*params == ' ' || *params == '\t')) {<br>+                    *params-- = 0;<br>+               }<br>+    }<br>+}<br>+<br>+/*!<br>+ * \internal<br>  * \brief Update the name of the file backing a \c bucket_file<br>  * \param preferred_file_name The preferred name of the backing file<br>  */<br>@@ -142,9 +161,32 @@<br>     } else if (!strchr(bucket_file->path, '.') && (ext = strrchr(ast_sorcery_object_get_id(bucket_file), '.'))) {<br>              /* If we don't have a file extension and were provided one in the URI, use it */<br>          char new_path[PATH_MAX];<br>+             char found_ext[PATH_MAX];<br> <br>          ast_bucket_file_metadata_set(bucket_file, "ext", ext);<br> <br>+          /* Don't pass '.' while checking for supported extension */<br>+              if (!ast_get_format_for_file_ext(ext + 1)) {<br>+                 /* If the file extension passed in the URI isn't supported check for the<br>+                  * extension based on the MIME type passed in the Content-Type header before<br>+                  * giving up.<br>+                         * If a match is found then retrieve the extension from the supported list<br>+                    * corresponding to the mime-type and use that to rename the file */<br>+                 struct ast_bucket_metadata *header = ast_bucket_file_metadata_get(bucket_file, "content-type");<br>+                    if (header) {<br>+                                char *mime_type = ast_strdup(header->value);<br>+                              if (mime_type) {<br>+                                     normalize_content_type_header(mime_type);<br>+                                    if (!ast_strlen_zero(mime_type)) {<br>+                                           if (ast_get_extension_for_mime_type(mime_type, found_ext, sizeof(found_ext))) {<br>+                                                      ext = found_ext;<br>+                                             }<br>+                                    }<br>+                                    ast_free(mime_type);<br>+                         }<br>+                    }<br>+            }<br>+<br>          snprintf(new_path, sizeof(new_path), "%s%s", bucket_file->path, ext);<br>            rename(bucket_file->path, new_path);<br>               ast_copy_string(bucket_file->path, new_path, sizeof(bucket_file->path));<br>diff --git a/res/res_http_media_cache.c b/res/res_http_media_cache.c<br>index eba7ecc..bca5763 100644<br>--- a/res/res_http_media_cache.c<br>+++ b/res/res_http_media_cache.c<br>@@ -84,6 +84,7 @@<br>    if (strcasecmp(header, "ETag")<br>              && strcasecmp(header, "Cache-Control")<br>              && strcasecmp(header, "Last-Modified")<br>+             && strcasecmp(header, "Content-Type")<br>               && strcasecmp(header, "Expires")) {<br>                 return realsize;<br>      }<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/8882">change 8882</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/8882"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I1b14692a49b2c1ac67688f58757184122e92ba89 </div>
<div style="display:none"> Gerrit-Change-Number: 8882 </div>
<div style="display:none"> Gerrit-PatchSet: 2 </div>
<div style="display:none"> Gerrit-Owner: Sean Bright <sean.bright@gmail.com> </div>
<div style="display:none"> Gerrit-Reviewer: Gaurav Khurana <gkhurana@godaddy.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Richard Mudgett <rmudgett@digium.com> </div>