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 17:21:20 +0200
committerWilliam Wennerström <william@wstrm.dev>2020-11-16 21:58:09 +0100
commiteebf54c8596abd5c28b405d8860c173207bbec64 (patch)
treebb1abfaad055aadbb0908dff921259a6e5967aa3 /src
parent9499df65859abd1404d3f9e96b8af859d3315bff (diff)
downloadprofani-tty-eebf54c8596abd5c28b405d8860c173207bbec64.tar.gz
Infer filename from content-disposition or URL
The Content-Disposition inferring is probably a bad idea security wise,
so I am going to remove it.
Diffstat (limited to 'src')
-rw-r--r--src/command/cmd_funcs.c243
-rw-r--r--src/tools/http_download.c125
-rw-r--r--src/tools/http_download.h5
3 files changed, 254 insertions, 119 deletions
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 7d921ea3..5e8b5cab 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -4,6 +4,7 @@
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
  * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org>
+ * Copyright (C) 2020 William Wennerström <william@wstrm.dev>
  *
  * This file is part of Profanity.
  *
@@ -67,6 +68,7 @@
 #include "config/scripts.h"
 #include "event/client_events.h"
 #include "tools/http_upload.h"
+#include "tools/http_download.h"
 #include "tools/autocomplete.h"
 #include "tools/parser.h"
 #include "tools/bookmark_ignore.h"
@@ -4805,6 +4807,43 @@ cmd_disco(ProfWin* window, const char* const command, gchar** args)
     return TRUE;
 }
 
+
+char *_add_omemo_stream(int *fd, FILE **fh, char **err) {
+    // Create temporary file for writing ciphertext.
+    int tmpfd;
+    char *tmpname = NULL;
+    if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) {
+        *err = "Unable to create temporary file for encrypted transfer.";
+        return NULL;
+    }
+    FILE *tmpfh = fdopen(tmpfd, "wb");
+
+    // The temporary ciphertext file should be removed after it has
+    // been closed.
+    remove(tmpname);
+    free(tmpname);
+
+    int crypt_res;
+    char *fragment;
+    fragment = omemo_encrypt_file(*fh, tmpfh, file_size(*fd), &crypt_res);
+    if (crypt_res != 0) {
+        fclose(tmpfh);
+        return NULL;
+    }
+
+    // Force flush as the upload will read from the same stream.
+    fflush(tmpfh);
+    rewind(tmpfh);
+
+    fclose(*fh); // Also closes descriptor.
+
+    // Switch original stream with temporary ciphertext stream.
+    *fd = tmpfd;
+    *fh = tmpfh;
+
+    return fragment;
+}
+
 gboolean
 cmd_sendfile(ProfWin* window, const char* const command, gchar** args)
 {
@@ -4855,59 +4894,29 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args)
         case WIN_CHAT:
         {
             ProfChatWin *chatwin = (ProfChatWin*)window;
-            assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
 
 #ifdef HAVE_OMEMO
             if (chatwin->is_omemo) {
-
-                // Create temporary file for writing ciphertext.
-                int tmpfd;
-                char *tmpname = NULL;
-                if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) {
-                    char *msg = "Unable to create temporary file for encrypted transfer.";
-                    cons_show_error(msg);
-                    win_println(window, THEME_ERROR, "-", msg);
-                    fclose(fh);
-                    goto out;
-                }
-                FILE *tmpfh = fdopen(tmpfd, "wb");
-
-                // The temporary ciphertext file should be removed after it has
-                // been closed.
-                remove(tmpname);
-                free(tmpname);
-
-                int crypt_res;
+                char *err = NULL;
                 alt_scheme = OMEMO_AESGCM_URL_SCHEME;
-                alt_fragment = omemo_encrypt_file(fh, tmpfh, file_size(fd), &crypt_res);
-                if (crypt_res != 0) {
-                    char *msg = "Failed to encrypt file.";
-                    cons_show_error(msg);
-                    win_println(window, THEME_ERROR, "-", msg);
-                    fclose(fh);
-                    fclose(tmpfh);
+                alt_fragment = _add_omemo_stream(&fd, &fh, &err);
+                if (err != NULL) {
+                    cons_show_error(err);
+                    win_println(window, THEME_ERROR, "-", err);
                     goto out;
                 }
-
-                // Force flush as the upload will read from the same stream.
-                fflush(tmpfh);
-                rewind(tmpfh);
-
-                fclose(fh); // Also closes descriptor.
-
-                // Switch original stream with temporary ciphertext stream.
-                fd = tmpfd;
-                fh = tmpfh;
-
                 break;
             }
 #endif
 
-            if ((chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE))
-                    || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) {
-                cons_show_error("Uploading unencrypted files disabled. See /otr sendfile or /pgp sendfile.");
-                win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet.");
-                goto out;
+            if (window->type == WIN_CHAT) {
+                assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+                if ((chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE))
+                        || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) {
+                    cons_show_error("Uploading unencrypted files disabled. See /otr sendfile or /pgp sendfile.");
+                    win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet.");
+                    goto out;
+                }
             }
             break;
         }
@@ -9154,91 +9163,57 @@ cmd_url_open(ProfWin* window, const char* const command, gchar** args)
     return TRUE;
 }
 
-gboolean
-cmd_url_save(ProfWin* window, const char* const command, gchar** args)
-{
-    if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) {
-        cons_show("url save not supported in this window");
-        return TRUE;
-    }
 
-    if (args[1] == NULL) {
-        cons_bad_cmd_usage(command);
-        return TRUE;
-    }
-
-    gchar* uri = args[1];
-    gchar* target_path = g_strdup(args[2]);
+void _url_save_fallback_method(ProfWin *window, const char *url, const char *directory, const char *filename) {
+    HTTPDownload *download = malloc(sizeof(HTTPDownload));
+    download->window = window;
+    download->url = strdup(url);
 
-    GFile* file = g_file_new_for_uri(uri);
-
-    gchar* target_dir = NULL;
-    gchar* base_name = NULL;
-
-    if (target_path == NULL) {
-        target_dir = g_strdup("./");
-        base_name = g_file_get_basename(file);
-        if (0 == g_strcmp0(base_name, ".")) {
-            g_free(base_name);
-            base_name = g_strdup("saved_url_content.html");
-        }
-        target_path = g_strconcat(target_dir, base_name, NULL);
+    if (filename) {
+        download->filename = strdup(filename);
+    } else {
+        download->filename = NULL;
     }
 
-    if (g_file_test(target_path, G_FILE_TEST_EXISTS) && g_file_test(target_path, G_FILE_TEST_IS_DIR)) {
-        target_dir = g_strdup(target_path);
-        base_name = g_file_get_basename(file);
-        g_free(target_path);
-        target_path = g_strconcat(target_dir, "/", base_name, NULL);
+    if (directory) {
+        download->directory = strdup(directory);
+    } else {
+        download->directory = NULL;
     }
 
-    g_object_unref(file);
-    file = NULL;
+    pthread_create(&(download->worker), NULL, &http_file_get, download);
+    http_download_add_download(download);
+}
 
-    if (base_name == NULL) {
-        base_name = g_path_get_basename(target_path);
-        target_dir = g_path_get_dirname(target_path);
+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);
     }
 
-    if (!g_file_test(target_dir, G_FILE_TEST_EXISTS) || !g_file_test(target_dir, G_FILE_TEST_IS_DIR)) {
-        cons_show("%s does not exist or is not a directory.", target_dir);
-        g_free(target_path);
-        g_free(target_dir);
-        g_free(base_name);
-        return TRUE;
+    // 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);
     }
 
-    gchar* scheme = g_uri_parse_scheme(uri);
-    if (scheme == NULL) {
-        cons_show("URL '%s' is not valid.", uri);
-        g_free(target_path);
-        g_free(target_dir);
-        g_free(base_name);
-        return TRUE;
+    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;
     }
 
-    gchar* scheme_cmd = NULL;
-
-    if (0 == g_strcmp0(scheme, "http")
-        || 0 == g_strcmp0(scheme, "https")
-        || 0 == g_strcmp0(scheme, OMEMO_AESGCM_URL_SCHEME)
-       ) {
-        scheme_cmd = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme);
-    }
-
-    g_free(scheme);
-
-    gchar** argv = g_strsplit(scheme_cmd, " ", 0);
-    g_free(scheme_cmd);
+    gchar **argv = g_strsplit(scheme_cmd, " ", 0);
 
     guint num_args = 0;
     while (argv[num_args]) {
         if (0 == g_strcmp0(argv[num_args], "%u")) {
             g_free(argv[num_args]);
-            argv[num_args] = g_strdup(uri);
+            argv[num_args] = g_strdup(url);
         } else if (0 == g_strcmp0(argv[num_args], "%p")) {
             g_free(argv[num_args]);
-            argv[num_args] = target_path;
+            argv[num_args] = fp;
         }
         num_args++;
     }
@@ -9246,12 +9221,56 @@ cmd_url_save(ProfWin* window, const char* const command, gchar** args)
     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 into '%s'.", uri, target_path);
+        cons_show("URL '%s' has been saved to '%s'.", url, fp);
     }
+}
 
-    g_free(target_dir);
-    g_free(base_name);
-    g_strfreev(argv);
+gboolean
+cmd_url_save(ProfWin *window, const char *const command, gchar **args)
+{
+    if (window->type != WIN_CHAT &&
+        window->type != WIN_MUC &&
+        window->type != WIN_PRIVATE) {
+        cons_show_error("`/url save` is not supported in this window.");
+        return TRUE;
+    }
+
+    if (args[1] == NULL) {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    gchar *url = args[1];
+    gchar *path = g_strdup(args[2]);
+
+    gchar *scheme = g_uri_parse_scheme(url);
+    if (scheme == NULL) {
+        cons_show("URL '%s' is not valid.", url);
+        g_free(url);
+        return TRUE;
+    }
+
+    gchar *directory = NULL;
+    gchar *filename = NULL;
+    if (path != NULL) {
+        directory = g_path_get_dirname(path);
+        filename = g_path_get_basename(path);
+    }
+
+    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);
+        } else {
+            cons_show_error("No download method defined for the scheme '%s'.", scheme);
+        }
+    } else {
+        _url_save_external_method(scheme_cmd, url, directory, filename);
+    }
+
+    g_free(scheme_cmd);
 
     return TRUE;
 }
diff --git a/src/tools/http_download.c b/src/tools/http_download.c
index 80916385..5a0f6f18 100644
--- a/src/tools/http_download.c
+++ b/src/tools/http_download.c
@@ -104,13 +104,119 @@ _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)
 {
     HTTPDownload *download = (HTTPDownload *)userdata;
 
-    FILE *fh = NULL;
-
     char *err = NULL;
 
     CURL *curl;
@@ -144,9 +250,12 @@ http_file_get(void *userdata)
     #endif
     curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
 
-    fh = download->filehandle;
+    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 *)&fh);
     curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity");
 
     if (cert_path) {
@@ -160,8 +269,8 @@ http_file_get(void *userdata)
     curl_easy_cleanup(curl);
     curl_global_cleanup();
 
-    if (fh) {
-        fclose(fh);
+    if (download->filehandle) {
+        fclose(download->filehandle);
     }
 
     pthread_mutex_lock(&lock);
@@ -188,7 +297,7 @@ http_file_get(void *userdata)
                 msg = strdup(FALLBACK_MSG);
             }
             win_update_entry_message(download->window, download->url, msg);
-            win_mark_received(download->window, download->put_url);
+            win_mark_received(download->window, download->url);
             free(msg);
         }
     }
@@ -197,6 +306,8 @@ http_file_get(void *userdata)
     pthread_mutex_unlock(&lock);
 
     free(download->url);
+    free(download->filename);
+    free(download->directory);
     free(download);
 
     return NULL;
diff --git a/src/tools/http_download.h b/src/tools/http_download.h
index 7348b77c..b0377d93 100644
--- a/src/tools/http_download.h
+++ b/src/tools/http_download.h
@@ -48,6 +48,8 @@
 
 typedef struct http_download_t {
     char *url;
+    char *filename;
+    char *directory;
     FILE *filehandle;
     curl_off_t bytes_received;
     ProfWin *window;
@@ -60,4 +62,7 @@ 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);
+
 #endif