diff --git a/main/media_cache.c b/main/media_cache.c
index b303643f5e7c71df43bb610e7ff3a79a7b03d460..1899fb453f943a324fe773f076d24bb303a0bea8 100644
--- a/main/media_cache.c
+++ b/main/media_cache.c
@@ -124,24 +124,6 @@ static void media_cache_item_del_from_astdb(struct ast_bucket_file *bucket_file)
 	ast_free(hash_value);
 }
 
-/*!
- * \internal
- * \brief Normalize the value of a Content-Type header
- *
- * This will trim off any optional parameters after the type/subtype.
- */
-static void normalize_content_type_header(char *content_type)
-{
-	char *params = strchr(content_type, ';');
-
-	if (params) {
-		*params-- = 0;
-		while (params > content_type && (*params == ' ' || *params == '\t')) {
-			*params-- = 0;
-		}
-	}
-}
-
 /*!
  * \internal
  * \brief Update the name of the file backing a \c bucket_file
@@ -150,47 +132,24 @@ static void normalize_content_type_header(char *content_type)
 static void bucket_file_update_path(struct ast_bucket_file *bucket_file,
 	const char *preferred_file_name)
 {
-	char *ext;
-
 	if (!ast_strlen_zero(preferred_file_name) && strcmp(bucket_file->path, preferred_file_name)) {
 		/* Use the preferred file name if available */
-
 		rename(bucket_file->path, preferred_file_name);
 		ast_copy_string(bucket_file->path, preferred_file_name,
 			sizeof(bucket_file->path));
-	} else if (!strchr(bucket_file->path, '.') && (ext = strrchr(ast_sorcery_object_get_id(bucket_file), '.'))) {
-		/* If we don't have a file extension and were provided one in the URI, use it */
-		char found_ext[32];
-		char new_path[PATH_MAX + sizeof(found_ext)];
-
-		ast_bucket_file_metadata_set(bucket_file, "ext", ext);
-
-		/* Don't pass '.' while checking for supported extension */
-		if (!ast_get_format_for_file_ext(ext + 1)) {
-			/* If the file extension passed in the URI isn't supported check for the
-			 * extension based on the MIME type passed in the Content-Type header before
-			 * giving up.
-			 * If a match is found then retrieve the extension from the supported list
-			 * corresponding to the mime-type and use that to rename the file */
-			struct ast_bucket_metadata *header = ast_bucket_file_metadata_get(bucket_file, "content-type");
-			if (header) {
-				char *mime_type = ast_strdup(header->value);
-				if (mime_type) {
-					normalize_content_type_header(mime_type);
-					if (!ast_strlen_zero(mime_type)) {
-						if (ast_get_extension_for_mime_type(mime_type, found_ext, sizeof(found_ext))) {
-							ext = found_ext;
-						}
-					}
-					ast_free(mime_type);
-				}
-				ao2_ref(header, -1);
+	} else if (!strchr(bucket_file->path, '.')) {
+		struct ast_bucket_metadata *ext =
+			ast_bucket_file_metadata_get(bucket_file, "ext");
+
+		if (ext) {
+			char *new_path;
+			if (ast_asprintf(&new_path, "%s%s", bucket_file->path, ext->value) != -1) {
+				rename(bucket_file->path, new_path);
+				ast_copy_string(bucket_file->path, new_path, sizeof(bucket_file->path));
+				ast_free(new_path);
 			}
+			ao2_ref(ext, -1);
 		}
-
-		snprintf(new_path, sizeof(new_path), "%s%s", bucket_file->path, ext);
-		rename(bucket_file->path, new_path);
-		ast_copy_string(bucket_file->path, new_path, sizeof(bucket_file->path));
 	}
 }
 
diff --git a/res/res_http_media_cache.c b/res/res_http_media_cache.c
index d761442b41ab4280c1ecf3090cce110956441838..ba9428d597ce19ce2a0560bbf6dc93108b492706 100644
--- a/res/res_http_media_cache.c
+++ b/res/res_http_media_cache.c
@@ -35,6 +35,7 @@
 
 #include <curl/curl.h>
 
+#include "asterisk/file.h"
 #include "asterisk/module.h"
 #include "asterisk/bucket.h"
 #include "asterisk/sorcery.h"
@@ -155,6 +156,176 @@ static void bucket_file_set_expiration(struct ast_bucket_file *bucket_file)
 	ast_bucket_file_metadata_set(bucket_file, "__actual_expires", time_buf);
 }
 
+static char *file_extension_from_string(const char *str, char *buffer, size_t capacity)
+{
+	const char *ext;
+
+	ext = strrchr(str, '.');
+	if (ext && ast_get_format_for_file_ext(ext + 1)) {
+		ast_debug(3, "Found extension '%s' at end of string\n", ext);
+		ast_copy_string(buffer, ext, capacity);
+		return buffer;
+	}
+
+	return NULL;
+}
+
+static char *file_extension_from_url(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
+{
+	return file_extension_from_string(ast_sorcery_object_get_id(bucket_file), buffer, capacity);
+}
+
+/*!
+ * \internal
+ * \brief Normalize the value of a Content-Type header
+ *
+ * This will trim off any optional parameters after the type/subtype.
+ */
+static void normalize_content_type_header(char *content_type)
+{
+	char *params = strchr(content_type, ';');
+
+	if (params) {
+		*params-- = 0;
+		while (params > content_type && (*params == ' ' || *params == '\t')) {
+			*params-- = 0;
+		}
+	}
+}
+
+static char *file_extension_from_content_type(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
+{
+	/* Check for the extension based on the MIME type passed in the Content-Type
+	 * header.
+	 *
+	 * If a match is found then retrieve the extension from the supported list
+	 * corresponding to the mime-type and use that to rename the file */
+
+	struct ast_bucket_metadata *header;
+	char *mime_type;
+
+	header = ast_bucket_file_metadata_get(bucket_file, "content-type");
+	if (!header) {
+		return NULL;
+	}
+
+	mime_type = ast_strdup(header->value);
+	if (mime_type) {
+		normalize_content_type_header(mime_type);
+		if (!ast_strlen_zero(mime_type)) {
+			if (ast_get_extension_for_mime_type(mime_type, buffer, sizeof(buffer))) {
+				ast_debug(3, "Derived extension '%s' from MIME type %s\n",
+					buffer,
+					mime_type);
+				ast_free(mime_type);
+				ao2_ref(header, -1);
+				return buffer;
+			}
+		}
+	}
+	ast_free(mime_type);
+	ao2_ref(header, -1);
+
+	return NULL;
+}
+
+/* The URL parsing API was introduced in 7.62.0 */
+#if LIBCURL_VERSION_NUM >= 0x073e00
+
+static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
+{
+	char *path;
+	CURLU *h;
+
+	h = curl_url();
+	if (!h) {
+		ast_log(LOG_ERROR, "Failed to allocate cURL URL handle\n");
+		return NULL;
+	}
+
+	if (curl_url_set(h, CURLUPART_URL, ast_sorcery_object_get_id(bucket_file), 0)) {
+		ast_log(LOG_ERROR, "Failed to parse URL: %s\n",
+			ast_sorcery_object_get_id(bucket_file));
+		curl_url_cleanup(h);
+		return NULL;
+	}
+
+	curl_url_get(h, CURLUPART_PATH, &path, 0);
+
+	/* Just parse it as a string like before, but without the extra cruft */
+	buffer = file_extension_from_string(path, buffer, capacity);
+
+	curl_free(path);
+	curl_url_cleanup(h);
+
+	return buffer;
+}
+
+#elif defined(HAVE_URIPARSER)
+
+#include <uriparser/Uri.h>
+
+static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
+{
+	UriParserStateA state;
+	UriUriA full_uri;
+	char *path;
+
+	state.uri = &full_uri;
+	if (uriParseUriA(&state, ast_sorcery_object_get_id(bucket_file)) != URI_SUCCESS
+	   || !full_uri.scheme.first
+	   || !full_uri.scheme.afterLast
+	   || !full_uri.pathTail) {
+		ast_log(LOG_ERROR, "Failed to parse URL: %s\n",
+			ast_sorcery_object_get_id(bucket_file));
+		uriFreeUriMembersA(&full_uri);
+		return NULL;
+	}
+
+	if (ast_asprintf(&path,
+			"%.*s",
+			(int) (full_uri.pathTail->text.afterLast - full_uri.pathTail->text.first),
+			full_uri.pathTail->text.first) != -1) {
+		/* Just parse it as a string like before, but without the extra cruft */
+		file_extension_from_string(path, buffer, capacity);
+		ast_free(path);
+		uriFreeUriMembersA(&full_uri);
+		return buffer;
+	}
+
+	uriFreeUriMembersA(&full_uri);
+	return NULL;
+}
+
+#else
+
+static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
+{
+	/* NOP */
+	return NULL;
+}
+
+#endif
+
+static void bucket_file_set_extension(struct ast_bucket_file *bucket_file)
+{
+	/* We will attempt to determine an extension in the following order for backwards
+	 * compatibility:
+	 *
+	 * 1. Look at tail end of URL for extension
+	 * 2. Use the Content-Type header if present
+	 * 3. Parse the URL (assuming we can) and look at the tail of the path
+	 */
+
+	char buffer[64];
+
+	if (file_extension_from_url(bucket_file, buffer, sizeof(buffer))
+	   || file_extension_from_content_type(bucket_file, buffer, sizeof(buffer))
+	   || file_extension_from_url_path(bucket_file, buffer, sizeof(buffer))) {
+		ast_bucket_file_metadata_set(bucket_file, "ext", buffer);
+	}
+}
+
 /*! \internal
  * \brief Return whether or not we should always revalidate against the server
  */
@@ -278,6 +449,7 @@ static int bucket_file_run_curl(struct ast_bucket_file *bucket_file)
 
 	if (http_code / 100 == 2) {
 		bucket_file_set_expiration(bucket_file);
+		bucket_file_set_extension(bucket_file);
 		return 0;
 	} else {
 		ast_log(LOG_WARNING, "Failed to retrieve URL '%s': server returned %ld\n",
diff --git a/tests/test_http_media_cache.c b/tests/test_http_media_cache.c
index c1975390a5e7704a6bd838d7f60a6f733e3e9bab..dfb28b703e0e0372d37e14be08ab8f5a688ea118 100644
--- a/tests/test_http_media_cache.c
+++ b/tests/test_http_media_cache.c
@@ -41,6 +41,14 @@
 #include "asterisk/bucket.h"
 #include "asterisk/test.h"
 
+#undef INCLUDE_URI_PARSING_TESTS
+#if defined(HAVE_CURL)
+# include <curl/curl.h>
+#endif
+#if (defined(HAVE_CURL) && LIBCURL_VERSION_NUM >= 0x073e00) || defined(HAVE_URIPARSER)
+# define INCLUDE_URI_PARSING_TESTS 1
+#endif
+
 #define CATEGORY "/res/http_media_cache/"
 
 #define TEST_URI "test_media_cache"
@@ -57,6 +65,7 @@ struct test_options {
 	struct timeval expires;
 	const char *status_text;
 	const char *etag;
+	const char *content_type;
 };
 
 static struct test_options options;
@@ -125,6 +134,10 @@ static int http_callback(struct ast_tcptls_session_instance *ser, const struct a
 		}
 	}
 
+	if (!ast_strlen_zero(options.content_type)) {
+		ast_str_append(&http_header, 0, "Content-Type: %s\r\n", options.content_type);
+	}
+
 	if (options.cache_control.maxage) {
 		SET_OR_APPEND_CACHE_CONTROL(cache_control);
 		ast_str_append(&cache_control, 0, "max-age=%d", options.cache_control.maxage);
@@ -220,6 +233,77 @@ static void bucket_file_cleanup(void *obj)
 	}
 }
 
+AST_TEST_DEFINE(retrieve_content_type)
+{
+	RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
+	char uri[1024];
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = CATEGORY;
+		info->summary = "Test retrieval of a resource with a Content-Type header";
+		info->description =
+			"This test covers retrieval of a resource whose URL does not end with\n"
+			"a parseable extension and whose response includes a Content-Type\n"
+			"header that we recognize.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	options.send_file = 1;
+	options.status_code = 200;
+	options.status_text = "OK";
+	options.content_type = "audio/wav";
+
+	snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav?account_id=1234");
+
+	bucket_file = ast_bucket_file_retrieve(uri);
+	ast_test_validate(test, bucket_file != NULL);
+	ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
+	ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
+	VALIDATE_STR_METADATA(test, bucket_file, "ext", ".wav");
+
+	return AST_TEST_PASS;
+}
+
+#ifdef INCLUDE_URI_PARSING_TESTS
+AST_TEST_DEFINE(retrieve_parsed_uri)
+{
+	RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
+	char uri[1024];
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = CATEGORY;
+		info->summary = "Test retrieval of a resource with a complex URI";
+		info->description =
+			"This test covers retrieval of a resource whose URL does not end with\n"
+			"a parseable extension, but the path portion of the URL does end with\n"
+			"parseable extension.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	options.send_file = 1;
+	options.status_code = 200;
+	options.status_text = "OK";
+
+	snprintf(uri, sizeof(uri), "%s/%s", server_uri, "foo.wav?account_id=1234");
+
+	bucket_file = ast_bucket_file_retrieve(uri);
+	ast_test_validate(test, bucket_file != NULL);
+	ast_test_validate(test, !strcmp(uri, ast_sorcery_object_get_id(bucket_file)));
+	ast_test_validate(test, !ast_strlen_zero(bucket_file->path));
+	VALIDATE_STR_METADATA(test, bucket_file, "ext", ".wav");
+
+	return AST_TEST_PASS;
+}
+#endif
+
 AST_TEST_DEFINE(retrieve_cache_control_directives)
 {
 	RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
@@ -670,6 +754,11 @@ static int load_module(void)
 	AST_TEST_REGISTER(retrieve_etag_expired);
 	AST_TEST_REGISTER(retrieve_cache_control_age);
 	AST_TEST_REGISTER(retrieve_cache_control_directives);
+	AST_TEST_REGISTER(retrieve_content_type);
+
+#ifdef INCLUDE_URI_PARSING_TESTS
+	AST_TEST_REGISTER(retrieve_parsed_uri);
+#endif
 
 	ast_test_register_init(CATEGORY, pre_test_cb);
 
@@ -688,6 +777,11 @@ static int unload_module(void)
 	AST_TEST_UNREGISTER(retrieve_etag_expired);
 	AST_TEST_UNREGISTER(retrieve_cache_control_age);
 	AST_TEST_UNREGISTER(retrieve_cache_control_directives);
+	AST_TEST_REGISTER(retrieve_content_type);
+
+#ifdef INCLUDE_URI_PARSING_TESTS
+	AST_TEST_REGISTER(retrieve_parsed_uri);
+#endif
 
 	return 0;
 }