<p>Sean Bright has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/16143">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">res_http_media_cache.c: Parse media URLs to find extensions.<br><br>Use cURL's URL parsing API - when available - to parse playback URLs<br>in order to find their file extensions.<br><br>For backwards compatibility, we first look at the full URL, then at<br>any Content-Type header, and finally at just the path portion of the<br>URL.<br><br>ASTERISK-27871 #close<br><br>Change-Id: I16d0682f6d794be96539261b3e48f237909139cb<br>---<br>M main/media_cache.c<br>M res/res_http_media_cache.c<br>2 files changed, 167 insertions(+), 51 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/43/16143/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/main/media_cache.c b/main/media_cache.c</span><br><span>index b303643..1899fb4 100644</span><br><span>--- a/main/media_cache.c</span><br><span>+++ b/main/media_cache.c</span><br><span>@@ -126,71 +126,30 @@</span><br><span> </span><br><span> /*!</span><br><span> * \internal</span><br><span style="color: hsl(0, 100%, 40%);">- * \brief Normalize the value of a Content-Type header</span><br><span style="color: hsl(0, 100%, 40%);">- *</span><br><span style="color: hsl(0, 100%, 40%);">- * This will trim off any optional parameters after the type/subtype.</span><br><span style="color: hsl(0, 100%, 40%);">- */</span><br><span style="color: hsl(0, 100%, 40%);">-static void normalize_content_type_header(char *content_type)</span><br><span style="color: hsl(0, 100%, 40%);">-{</span><br><span style="color: hsl(0, 100%, 40%);">- char *params = strchr(content_type, ';');</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- if (params) {</span><br><span style="color: hsl(0, 100%, 40%);">- *params-- = 0;</span><br><span style="color: hsl(0, 100%, 40%);">- while (params > content_type && (*params == ' ' || *params == '\t')) {</span><br><span style="color: hsl(0, 100%, 40%);">- *params-- = 0;</span><br><span style="color: hsl(0, 100%, 40%);">- }</span><br><span style="color: hsl(0, 100%, 40%);">- }</span><br><span style="color: hsl(0, 100%, 40%);">-}</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-/*!</span><br><span style="color: hsl(0, 100%, 40%);">- * \internal</span><br><span> * \brief Update the name of the file backing a \c bucket_file</span><br><span> * \param preferred_file_name The preferred name of the backing file</span><br><span> */</span><br><span> static void bucket_file_update_path(struct ast_bucket_file *bucket_file,</span><br><span> const char *preferred_file_name)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">- char *ext;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span> if (!ast_strlen_zero(preferred_file_name) && strcmp(bucket_file->path, preferred_file_name)) {</span><br><span> /* Use the preferred file name if available */</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span> rename(bucket_file->path, preferred_file_name);</span><br><span> ast_copy_string(bucket_file->path, preferred_file_name,</span><br><span> sizeof(bucket_file->path));</span><br><span style="color: hsl(0, 100%, 40%);">- } else if (!strchr(bucket_file->path, '.') && (ext = strrchr(ast_sorcery_object_get_id(bucket_file), '.'))) {</span><br><span style="color: hsl(0, 100%, 40%);">- /* If we don't have a file extension and were provided one in the URI, use it */</span><br><span style="color: hsl(0, 100%, 40%);">- char found_ext[32];</span><br><span style="color: hsl(0, 100%, 40%);">- char new_path[PATH_MAX + sizeof(found_ext)];</span><br><span style="color: hsl(120, 100%, 40%);">+ } else if (!strchr(bucket_file->path, '.')) {</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_bucket_metadata *ext =</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_bucket_file_metadata_get(bucket_file, "ext");</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- ast_bucket_file_metadata_set(bucket_file, "ext", ext);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- /* Don't pass '.' while checking for supported extension */</span><br><span style="color: hsl(0, 100%, 40%);">- if (!ast_get_format_for_file_ext(ext + 1)) {</span><br><span style="color: hsl(0, 100%, 40%);">- /* If the file extension passed in the URI isn't supported check for the</span><br><span style="color: hsl(0, 100%, 40%);">- * extension based on the MIME type passed in the Content-Type header before</span><br><span style="color: hsl(0, 100%, 40%);">- * giving up.</span><br><span style="color: hsl(0, 100%, 40%);">- * If a match is found then retrieve the extension from the supported list</span><br><span style="color: hsl(0, 100%, 40%);">- * corresponding to the mime-type and use that to rename the file */</span><br><span style="color: hsl(0, 100%, 40%);">- struct ast_bucket_metadata *header = ast_bucket_file_metadata_get(bucket_file, "content-type");</span><br><span style="color: hsl(0, 100%, 40%);">- if (header) {</span><br><span style="color: hsl(0, 100%, 40%);">- char *mime_type = ast_strdup(header->value);</span><br><span style="color: hsl(0, 100%, 40%);">- if (mime_type) {</span><br><span style="color: hsl(0, 100%, 40%);">- normalize_content_type_header(mime_type);</span><br><span style="color: hsl(0, 100%, 40%);">- if (!ast_strlen_zero(mime_type)) {</span><br><span style="color: hsl(0, 100%, 40%);">- if (ast_get_extension_for_mime_type(mime_type, found_ext, sizeof(found_ext))) {</span><br><span style="color: hsl(0, 100%, 40%);">- ext = found_ext;</span><br><span style="color: hsl(0, 100%, 40%);">- }</span><br><span style="color: hsl(0, 100%, 40%);">- }</span><br><span style="color: hsl(0, 100%, 40%);">- ast_free(mime_type);</span><br><span style="color: hsl(0, 100%, 40%);">- }</span><br><span style="color: hsl(0, 100%, 40%);">- ao2_ref(header, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ext) {</span><br><span style="color: hsl(120, 100%, 40%);">+ char *new_path;</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_asprintf(&new_path, "%s%s", bucket_file->path, ext->value) != -1) {</span><br><span style="color: hsl(120, 100%, 40%);">+ rename(bucket_file->path, new_path);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_copy_string(bucket_file->path, new_path, sizeof(bucket_file->path));</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(new_path);</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(ext, -1);</span><br><span> }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- snprintf(new_path, sizeof(new_path), "%s%s", bucket_file->path, ext);</span><br><span style="color: hsl(0, 100%, 40%);">- rename(bucket_file->path, new_path);</span><br><span style="color: hsl(0, 100%, 40%);">- ast_copy_string(bucket_file->path, new_path, sizeof(bucket_file->path));</span><br><span> }</span><br><span> }</span><br><span> </span><br><span>diff --git a/res/res_http_media_cache.c b/res/res_http_media_cache.c</span><br><span>index d761442..1470f11 100644</span><br><span>--- a/res/res_http_media_cache.c</span><br><span>+++ b/res/res_http_media_cache.c</span><br><span>@@ -35,6 +35,7 @@</span><br><span> </span><br><span> #include <curl/curl.h></span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/file.h"</span><br><span> #include "asterisk/module.h"</span><br><span> #include "asterisk/bucket.h"</span><br><span> #include "asterisk/sorcery.h"</span><br><span>@@ -155,6 +156,161 @@</span><br><span> ast_bucket_file_metadata_set(bucket_file, "__actual_expires", time_buf);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static char *file_extension_from_string(const char *str, char *buffer, size_t capacity)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *ext;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ext = strrchr(str, '.');</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ext && ast_get_format_for_file_ext(ext + 1)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_debug(3, "Found extension '%s' at end of string\n", ext);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_copy_string(buffer, ext, capacity);</span><br><span style="color: hsl(120, 100%, 40%);">+ return buffer;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static char *file_extension_from_url(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return file_extension_from_string(ast_sorcery_object_get_id(bucket_file), buffer, capacity);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \internal</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Normalize the value of a Content-Type header</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * This will trim off any optional parameters after the type/subtype.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static void normalize_content_type_header(char *content_type)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ char *params = strchr(content_type, ';');</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (params) {</span><br><span style="color: hsl(120, 100%, 40%);">+ *params-- = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ while (params > content_type && (*params == ' ' || *params == '\t')) {</span><br><span style="color: hsl(120, 100%, 40%);">+ *params-- = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static char *file_extension_from_content_type(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Check for the extension based on the MIME type passed in the Content-Type</span><br><span style="color: hsl(120, 100%, 40%);">+ * header.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * If a match is found then retrieve the extension from the supported list</span><br><span style="color: hsl(120, 100%, 40%);">+ * corresponding to the mime-type and use that to rename the file */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_bucket_metadata *header;</span><br><span style="color: hsl(120, 100%, 40%);">+ char *mime_type;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ header = ast_bucket_file_metadata_get(bucket_file, "content-type");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!header) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ mime_type = ast_strdup(header->value);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (mime_type) {</span><br><span style="color: hsl(120, 100%, 40%);">+ normalize_content_type_header(mime_type);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_strlen_zero(mime_type)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_get_extension_for_mime_type(mime_type, buffer, sizeof(buffer))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_debug(3, "Derived extension '%s' from MIME type %s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ buffer,</span><br><span style="color: hsl(120, 100%, 40%);">+ mime_type);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(mime_type);</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(header, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+ return buffer;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(mime_type);</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(header, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* The URL parsing API was introduced in 7.62.0 */</span><br><span style="color: hsl(120, 100%, 40%);">+#if LIBCURL_VERSION_NUM >= 0x073e00</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ CURLUcode rc;</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *url;</span><br><span style="color: hsl(120, 100%, 40%);">+ char *query, *fragment, *path;</span><br><span style="color: hsl(120, 100%, 40%);">+ CURLU *h;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ h = curl_url();</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!h) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Failed to allocate cURL URL handle\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ url = ast_sorcery_object_get_id(bucket_file);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ rc = curl_url_set(h, CURLUPART_URL, url, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (rc != CURLUE_OK) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Failed to parse URL: %s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_sorcery_object_get_id(bucket_file));</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_url_cleanup(h);</span><br><span style="color: hsl(120, 100%, 40%);">+ return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_url_get(h, CURLUPART_QUERY, &query, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_url_get(h, CURLUPART_FRAGMENT, &fragment, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* An empty fragment and an empty query string are handled differently (at least in</span><br><span style="color: hsl(120, 100%, 40%);">+ 7.68.0), so explicitly check for an empty fragment */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (query == NULL && fragment == NULL && !ast_ends_with(url, "#")) {</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_free(query);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_free(fragment);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_url_cleanup(h);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* No query string? No fragment? if we were going to parse we would have</span><br><span style="color: hsl(120, 100%, 40%);">+ parsed in file_extension_from_url */</span><br><span style="color: hsl(120, 100%, 40%);">+ return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_url_get(h, CURLUPART_PATH, &path, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Just parse it as a string like before, but without the extra cruft */</span><br><span style="color: hsl(120, 100%, 40%);">+ buffer = file_extension_from_string(path, buffer, capacity);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_free(path);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_free(query);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_free(fragment);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_url_cleanup(h);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return buffer;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#else</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ /* NOP */</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#endif</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void bucket_file_set_extension(struct ast_bucket_file *bucket_file)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ /* We will attempt to determine an extension in the following order for backwards</span><br><span style="color: hsl(120, 100%, 40%);">+ * compatibility:</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * 1. Look at tail end of URL for extension</span><br><span style="color: hsl(120, 100%, 40%);">+ * 2. Use the Content-Type header if present</span><br><span style="color: hsl(120, 100%, 40%);">+ * 3. Parse the URL (assuming we can) and look at the tail of the path</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ char buffer[64];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (file_extension_from_url(bucket_file, buffer, sizeof(buffer))</span><br><span style="color: hsl(120, 100%, 40%);">+ || file_extension_from_content_type(bucket_file, buffer, sizeof(buffer))</span><br><span style="color: hsl(120, 100%, 40%);">+ || file_extension_from_url_path(bucket_file, buffer, sizeof(buffer))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_bucket_file_metadata_set(bucket_file, "ext", buffer);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! \internal</span><br><span> * \brief Return whether or not we should always revalidate against the server</span><br><span> */</span><br><span>@@ -278,6 +434,7 @@</span><br><span> </span><br><span> if (http_code / 100 == 2) {</span><br><span> bucket_file_set_expiration(bucket_file);</span><br><span style="color: hsl(120, 100%, 40%);">+ bucket_file_set_extension(bucket_file);</span><br><span> return 0;</span><br><span> } else {</span><br><span> ast_log(LOG_WARNING, "Failed to retrieve URL '%s': server returned %ld\n",</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/16143">change 16143</a>. To unsubscribe, or for help writing mail filters, 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/c/asterisk/+/16143"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 16 </div>
<div style="display:none"> Gerrit-Change-Id: I16d0682f6d794be96539261b3e48f237909139cb </div>
<div style="display:none"> Gerrit-Change-Number: 16143 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Sean Bright <sean@seanbright.com> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>