about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorWilliam Wennerström <william@wstrm.dev>2020-07-05 18:28:23 +0200
committerWilliam Wennerström <william@wstrm.dev>2020-11-16 21:58:09 +0100
commita0cf0844abeeffe83a13e438eff3c309bc9887e9 (patch)
tree7b43f43cc16ad94a8b8319f51910514f5067481d /src
parenteebf54c8596abd5c28b405d8860c173207bbec64 (diff)
downloadprofani-tty-a0cf0844abeeffe83a13e438eff3c309bc9887e9.tar.gz
Remove unsafe Conent-Disposition inferring
Diffstat (limited to 'src')
-rw-r--r--src/command/cmd_funcs.c99
-rw-r--r--src/tools/http_download.c133
-rw-r--r--src/tools/http_download.h5
3 files changed, 80 insertions, 157 deletions
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 5e8b5cab..fb39f104 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -9164,46 +9164,23 @@ cmd_url_open(ProfWin* window, const char* const command, gchar** args)
 }
 
 
-void _url_save_fallback_method(ProfWin *window, const char *url, const char *directory, const char *filename) {
+void _url_save_fallback_method(ProfWin *window, const char *url, const char *filename) {
+    FILE *fh = fopen(filename, "wb");
+    if (!fh) {
+        cons_show_error("Cannot open file '%s' for writing.", filename);
+        return;
+    }
+
     HTTPDownload *download = malloc(sizeof(HTTPDownload));
     download->window = window;
     download->url = strdup(url);
-
-    if (filename) {
-        download->filename = strdup(filename);
-    } else {
-        download->filename = NULL;
-    }
-
-    if (directory) {
-        download->directory = strdup(directory);
-    } else {
-        download->directory = NULL;
-    }
+    download->filehandle = fh;
 
     pthread_create(&(download->worker), NULL, &http_file_get, download);
     http_download_add_download(download);
 }
 
-void _url_save_external_method(const char *scheme_cmd, const char *url, const char *directory, char *filename) {
-    if (!filename) {
-        filename = http_filename_from_url(url);
-    }
-
-    // Explicitly use "." as directory if no directory has been passed.
-    char *fp = NULL;
-    if (directory == NULL) {
-        fp = g_build_filename(".", filename, NULL);
-    } else {
-        fp = g_build_filename(directory, filename, NULL);
-    }
-
-    if (!g_file_test(directory, G_FILE_TEST_EXISTS) ||
-        !g_file_test(directory, G_FILE_TEST_IS_DIR)) {
-        cons_show_error("Directory '%s' does not exist or is not a directory.", directory);
-        return;
-    }
-
+void _url_save_external_method(const char *scheme_cmd, const char *url, const char *filename) {
     gchar **argv = g_strsplit(scheme_cmd, " ", 0);
 
     guint num_args = 0;
@@ -9213,7 +9190,7 @@ void _url_save_external_method(const char *scheme_cmd, const char *url, const ch
             argv[num_args] = g_strdup(url);
         } else if (0 == g_strcmp0(argv[num_args], "%p")) {
             g_free(argv[num_args]);
-            argv[num_args] = fp;
+            argv[num_args] = strdup(filename);
         }
         num_args++;
     }
@@ -9221,10 +9198,29 @@ void _url_save_external_method(const char *scheme_cmd, const char *url, const ch
     if (!call_external(argv, NULL, NULL)) {
         cons_show_error("Unable to save url: check the logs for more information.");
     } else {
-        cons_show("URL '%s' has been saved to '%s'.", url, fp);
+        cons_show("URL '%s' has been saved to '%s'.", url, filename);
     }
 }
 
+char *_make_unique_filename(const char *filename) {
+    char *unique = strdup(filename);
+
+    unsigned int i = 0;
+    while(g_file_test(unique, G_FILE_TEST_EXISTS)) {
+        free(unique);
+
+        if (i > 1000) { // Give up after 1000 attempts.
+            return NULL;
+        }
+
+        if (asprintf(&unique, "%s.%u", filename, i) < 0) {
+            return NULL;
+        }
+    }
+
+    return unique;
+}
+
 gboolean
 cmd_url_save(ProfWin *window, const char *const command, gchar **args)
 {
@@ -9251,23 +9247,50 @@ cmd_url_save(ProfWin *window, const char *const command, gchar **args)
     }
 
     gchar *directory = NULL;
-    gchar *filename = NULL;
+    gchar *basename = NULL;
     if (path != NULL) {
         directory = g_path_get_dirname(path);
-        filename = g_path_get_basename(path);
+        basename = g_path_get_basename(path);
     }
 
+    if (directory == NULL) {
+        // Explicitly use "./" as directory if no directory has been passed.
+        directory = "./";
+    }
+
+    if (!g_file_test(directory, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+        cons_show_error("Directory '%s' does not exist or is not a directory.", directory);
+        return TRUE;
+    }
+
+    if (!basename) {
+        basename = http_basename_from_url(url);
+    }
+
+    char *filename = NULL;
+    filename = g_build_filename(directory, basename, NULL);
+
+    char *unique_filename = _make_unique_filename(filename);
+    if (!unique_filename) {
+        cons_show_error("Failed to generate an unique filename from '%s'.", filename);
+        free(filename);
+        return TRUE;
+    }
+
+    free(filename);
+    filename = unique_filename;
+
     gchar *scheme_cmd = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme);
     if (scheme_cmd == NULL) {
         if (g_strcmp0(scheme, "http") == 0
                 || g_strcmp0(scheme, "https") == 0
                 || g_strcmp0(scheme, OMEMO_AESGCM_URL_SCHEME) == 0) {
-            _url_save_fallback_method(window, url, directory, filename);
+            _url_save_fallback_method(window, url, filename);
         } else {
             cons_show_error("No download method defined for the scheme '%s'.", scheme);
         }
     } else {
-        _url_save_external_method(scheme_cmd, url, directory, filename);
+        _url_save_external_method(scheme_cmd, url, filename);
     }
 
     g_free(scheme_cmd);
diff --git a/src/tools/http_download.c b/src/tools/http_download.c
index 5a0f6f18..3d4003f9 100644
--- a/src/tools/http_download.c
+++ b/src/tools/http_download.c
@@ -104,114 +104,6 @@ _older_progress(void *p, double dltotal, double dlnow, double ultotal, double ul
 }
 #endif
 
-char *http_filename_from_header(char *header) {
-    const char *header_tag_cd = "Content-Disposition:";
-    const int header_tag_cd_len = strlen(header_tag_cd);
-
-    if (!header) {
-        return NULL; // Bad header.
-    }
-
-    if (strncasecmp(header, header_tag_cd, header_tag_cd_len) == 0) {
-        header += header_tag_cd_len; // Move to header content.
-    } else {
-        return NULL; // Not a CD header.
-    }
-
-    const char *filename_key = "filename=";
-    const size_t filename_key_len = strlen(filename_key);
-
-    char *value = strcasestr(header, filename_key);
-    if (!value) {
-        return NULL; // No filename key found.
-    }
-
-    value += filename_key_len; // Move to key value.
-
-    char fn[4096];
-    char *pf = fn;
-    while(*value != '\0' && *value != ';') {
-        *pf++ = *value++;
-    }
-    *pf = '\0';
-
-    if (!strlen(fn)) {
-        return NULL; // Empty tag.
-    }
-
-    return strdup(fn);
-}
-
-char *http_filename_from_url(const char *url) {
-    const char *default_name = "index.html";
-
-    GFile *file = g_file_new_for_uri(url);
-    char *filename = g_file_get_basename(file);
-    g_object_unref(file);
-
-    if (g_strcmp0(filename, ".") == 0
-            || g_strcmp0(filename, G_DIR_SEPARATOR_S) == 0) {
-        g_free(filename);
-        return strdup(default_name);
-    }
-
-    return filename;
-}
-
-static size_t _header_callback(char *data, size_t size, size_t nitems, void *userdata) {
-    char *header = (char*)data;
-
-    HTTPDownload *download = (HTTPDownload *)userdata;
-    size *= nitems;
-
-    if (download->filename != NULL) {
-        return size; // No-op.
-    }
-
-    download->filename = http_filename_from_header(header);
-
-    return size;
-}
-
-FILE *_get_filehandle(const char *directory, const char *filename) {
-    gchar *fp;
-    FILE *fh;
-
-    // Explicitly use "." as directory if no directory has been passed.
-    if (directory == NULL) {
-        fp = g_build_filename(".", filename, NULL);
-    } else {
-        fp = g_build_filename(directory, filename, NULL);
-    }
-
-    fh = fopen(fp, "wb");
-    g_free(fp);
-    return fh;
-}
-
-static size_t _write_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {
-    HTTPDownload *download = (HTTPDownload *)userdata;
-    size *= nmemb;
-
-    if (download->filename == NULL) {
-        download->filename = http_filename_from_url(download->url);
-    }
-
-    if (download->filename == NULL || download->directory == NULL) {
-        return 0; // Missing file name or directory, write no data.
-    }
-
-    if (download->filehandle == NULL ) {
-        FILE *fh = _get_filehandle(download->directory, download->filename);
-        if (!fh) {
-            return 0; // Unable to open file handle.
-        }
-        download->filehandle = fh;
-    }
-
-    return fwrite(buffer, size, nmemb, userdata);
-}
-
 void *
 http_file_get(void *userdata)
 {
@@ -250,11 +142,7 @@ http_file_get(void *userdata)
     #endif
     curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
 
-    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _header_callback);
-    curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)download);
-
-    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_callback);
-    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)download);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)download->filehandle);
 
     curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity");
 
@@ -306,8 +194,6 @@ http_file_get(void *userdata)
     pthread_mutex_unlock(&lock);
 
     free(download->url);
-    free(download->filename);
-    free(download->directory);
     free(download);
 
     return NULL;
@@ -332,3 +218,20 @@ http_download_add_download(HTTPDownload *download)
 {
     download_processes = g_slist_append(download_processes, download);
 }
+
+char *http_basename_from_url(const char *url) {
+    const char *default_name = "index.html";
+
+    GFile *file = g_file_new_for_uri(url);
+    char *filename = g_file_get_basename(file);
+    g_object_unref(file);
+
+    if (g_strcmp0(filename, ".") == 0
+            || g_strcmp0(filename, "..") == 0
+            || g_strcmp0(filename, G_DIR_SEPARATOR_S) == 0) {
+        g_free(filename);
+        return strdup(default_name);
+    }
+
+    return filename;
+}
diff --git a/src/tools/http_download.h b/src/tools/http_download.h
index b0377d93..868b99f2 100644
--- a/src/tools/http_download.h
+++ b/src/tools/http_download.h
@@ -48,8 +48,6 @@
 
 typedef struct http_download_t {
     char *url;
-    char *filename;
-    char *directory;
     FILE *filehandle;
     curl_off_t bytes_received;
     ProfWin *window;
@@ -62,7 +60,6 @@ void* http_file_get(void *userdata);
 void http_download_cancel_processes(ProfWin *window);
 void http_download_add_download(HTTPDownload *download);
 
-char *http_filename_from_url(const char *url);
-char *http_filename_from_header(char *header);
+char *http_basename_from_url(const char *url);
 
 #endif