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
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));
}
}
#include <curl/curl.h>
+#include "asterisk/file.h"
#include "asterisk/module.h"
#include "asterisk/bucket.h"
#include "asterisk/sorcery.h"
#include "asterisk/threadstorage.h"
+#include "asterisk/uri.h"
#define GLOBAL_USERAGENT "asterisk-libcurl-agent/1.0"
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;
+}
+
+static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
+{
+ struct ast_uri *uri;
+
+ uri = ast_uri_parse(ast_sorcery_object_get_id(bucket_file));
+ if (!uri) {
+ ast_log(LOG_ERROR, "Failed to parse URI: %s\n",
+ ast_sorcery_object_get_id(bucket_file));
+ return NULL;
+ }
+
+ /* Just parse it as a string like before, but without the extra cruft */
+ buffer = file_extension_from_string(ast_uri_path(uri), buffer, capacity);
+ ao2_cleanup(uri);
+ return buffer;
+}
+
+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
*/
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",
struct timeval expires;
const char *status_text;
const char *etag;
+ const char *content_type;
};
static struct test_options options;
}
}
+ 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);
}
}
+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;
+}
+
+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;
+}
+
AST_TEST_DEFINE(retrieve_cache_control_directives)
{
RAII_VAR(struct ast_bucket_file *, bucket_file, NULL, bucket_file_cleanup);
AST_TEST_REGISTER(retrieve_etag_expired);
AST_TEST_REGISTER(retrieve_cache_control_age);
AST_TEST_REGISTER(retrieve_cache_control_directives);
+ AST_TEST_REGISTER(retrieve_parsed_uri);
+ AST_TEST_REGISTER(retrieve_content_type);
ast_test_register_init(CATEGORY, pre_test_cb);
AST_TEST_UNREGISTER(retrieve_etag_expired);
AST_TEST_UNREGISTER(retrieve_cache_control_age);
AST_TEST_UNREGISTER(retrieve_cache_control_directives);
+ AST_TEST_REGISTER(retrieve_parsed_uri);
+ AST_TEST_REGISTER(retrieve_content_type);
return 0;
}