about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorWilliam Wennerström <william@wstrm.dev>2020-12-03 16:43:07 +0100
committerWilliam Wennerström <william@wstrm.dev>2020-12-03 16:54:06 +0100
commit3a6597ee2967f91f49a1b4e17cf0595f37064587 (patch)
tree82d021e0c76b6980d908952edd4402272f14dcab
parent1bb6cecee69d5167220a18cc4c125c215784de66 (diff)
downloadprofani-tty-3a6597ee2967f91f49a1b4e17cf0595f37064587.tar.gz
Refactor for threaded external executable for built-in download methods
-rw-r--r--Makefile.am5
-rw-r--r--src/command/cmd_funcs.c289
-rw-r--r--src/common.c20
-rw-r--r--src/common.h1
-rw-r--r--src/config/files.h19
-rw-r--r--src/config/preferences.c49
-rw-r--r--src/config/preferences.h1
-rw-r--r--src/omemo/omemo.c1
-rw-r--r--src/omemo/omemo.h1
-rw-r--r--src/tools/aesgcm_download.c21
-rw-r--r--src/tools/aesgcm_download.h1
-rw-r--r--src/tools/http_common.h4
-rw-r--r--src/tools/http_download.c20
-rw-r--r--src/tools/http_download.h1
-rw-r--r--src/ui/console.c4
15 files changed, 203 insertions, 234 deletions
diff --git a/Makefile.am b/Makefile.am
index aeb52abd..9b2f75c6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -47,8 +47,6 @@ core_sources = \
 	src/tools/http_upload.h \
 	src/tools/http_download.c \
 	src/tools/http_download.h \
-	src/tools/aesgcm_download.c \
-	src/tools/aesgcm_download.h \
 	src/tools/bookmark_ignore.c \
 	src/tools/bookmark_ignore.h \
 	src/tools/autocomplete.c src/tools/autocomplete.h \
@@ -200,7 +198,8 @@ otr4_sources = \
 
 omemo_sources = \
 	src/omemo/omemo.h src/omemo/omemo.c src/omemo/crypto.h src/omemo/crypto.c \
-	src/omemo/store.h src/omemo/store.c src/xmpp/omemo.h src/xmpp/omemo.c
+	src/omemo/store.h src/omemo/store.c src/xmpp/omemo.h src/xmpp/omemo.c \
+	src/tools/aesgcm_download.h src/tools/aesgcm_download.c
 
 omemo_unittest_sources = \
 	tests/unittests/omemo/stub_omemo.c
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index c6557159..88461f1d 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -60,6 +60,7 @@
 #include "command/cmd_funcs.h"
 #include "command/cmd_defs.h"
 #include "command/cmd_ac.h"
+#include "config/files.h"
 #include "config/accounts.h"
 #include "config/account.h"
 #include "config/preferences.h"
@@ -1089,7 +1090,7 @@ _writecsv(int fd, const char* const str)
     size_t len = strlen(str);
     char* s = malloc(2 * len * sizeof(char));
     char* c = s;
-    for (int i =0; i < strlen(str); i++) {
+    for (int i = 0; i < strlen(str); i++) {
         if (str[i] != '"')
             *c++ = str[i];
         else {
@@ -9058,169 +9059,59 @@ cmd_slashguard(ProfWin* window, const char* const command, gchar** args)
     return TRUE;
 }
 
-gboolean
-cmd_url_open(ProfWin* window, const char* const command, gchar** args)
+#ifdef HAVE_OMEMO
+void
+_url_aesgcm_method(ProfWin* window, const char* cmd_template, const char* url, const char* filename)
 {
-    if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) {
-        cons_show("url open not supported in this window");
-        return TRUE;
-    }
-
-    if (args[1] == NULL) {
-        cons_bad_cmd_usage(command);
-        return TRUE;
-    }
-
-    gboolean require_save = false;
-
-    gchar* fileStart = g_strrstr(args[1], "/");
-    if (fileStart == NULL) {
-        cons_show("URL '%s' is not valid.", args[1]);
-        return TRUE;
-    }
-
-    fileStart++;
-    if (((char*)(fileStart - 2))[0] == '/' && ((char*)(fileStart - 3))[0] == ':') {
-        // If the '/' is last character of the '://' string, there will be no suffix
-        // Therefore, it is considered that there is no file name in the URL and
-        // fileStart is set to the end of the URL.
-        fileStart = args[1] + strlen(args[1]);
-    }
-
-    gchar* suffix = NULL;
-    gchar* suffixStart = g_strrstr(fileStart, ".");
-    if (suffixStart != NULL) {
-        suffixStart++;
-        gchar* suffixEnd = g_strrstr(suffixStart, "#");
-        if (suffixEnd == NULL) {
-            suffix = g_strdup(suffixStart);
-        } else {
-            suffix = g_strndup(suffixStart, suffixEnd - suffixStart);
-        }
-    }
-
-    gchar** suffix_cmd_pref = prefs_get_string_list_with_option(PREF_URL_OPEN_CMD, NULL);
-    if (suffix != NULL) {
-        gchar* lowercase_suffix = g_ascii_strdown(suffix, -1);
-        g_strfreev(suffix_cmd_pref);
-        suffix_cmd_pref = prefs_get_string_list_with_option(PREF_URL_OPEN_CMD, lowercase_suffix);
-        g_free(lowercase_suffix);
-        g_free(suffix);
-    }
-
-    if (0 == g_strcmp0(suffix_cmd_pref[0], "true")) {
-        require_save = true;
-    }
-
-    gchar* suffix_cmd = g_strdup(suffix_cmd_pref[1]);
-    g_strfreev(suffix_cmd_pref);
-
-    gchar* scheme = g_uri_parse_scheme(args[1]);
-    if (0 == g_strcmp0(scheme, "aesgcm")) {
-        require_save = true;
-    }
-    g_free(scheme);
-
-    if (require_save) {
-        gchar* save_args[] = { "open", args[1], "/tmp/profanity.tmp", NULL };
-        cmd_url_save(window, command, save_args);
-    }
-
-    gchar** argv = g_strsplit(suffix_cmd, " ", 0);
-    guint num_args = 0;
-    while (argv[num_args]) {
-        if (0 == g_strcmp0(argv[num_args], "%u")) {
-            g_free(argv[num_args]);
-            if (require_save) {
-                argv[num_args] = g_strdup("/tmp/profanity.tmp");
-            } else {
-                argv[num_args] = g_strdup(args[1]);
-            }
-            break;
-        }
-        num_args++;
-    }
-
-    if (!call_external(argv, NULL, NULL)) {
-        cons_show_error("Unable to open url: check the logs for more information.");
-    }
-
-    if (require_save) {
-        g_unlink("/tmp/profanity.tmp");
+    AESGCMDownload* download = malloc(sizeof(AESGCMDownload));
+    download->window = window;
+    download->url = strdup(url);
+    download->filename = strdup(filename);
+    if (cmd_template != NULL) {
+        download->cmd_template = strdup(cmd_template);
+    } else {
+        download->cmd_template = NULL;
     }
 
-    g_strfreev(argv);
-    g_free(suffix_cmd);
-
-    return TRUE;
-}
-
-void
-_url_open_fallback_method(ProfWin* window, const char* url, const char* filename)
-{
-    // TODO(wstrm): Use _url_save_fallback_method?.
-    // We probably want to do the cmd_url_open in a separate thread and wait for
-    // the transfer to be finished before calling call_external.
+    pthread_create(&(download->worker), NULL, &aesgcm_file_get, download);
+    aesgcm_download_add_download(download);
 }
+#endif
 
 void
-_url_save_fallback_method(ProfWin* window, const char* url, const char* filename)
+_url_http_method(ProfWin* window, const char* cmd_template, const char* url, const char* filename)
 {
-    gchar* scheme = g_uri_parse_scheme(url);
-
-#ifdef HAVE_OMEMO
-    if (g_strcmp0(scheme, "aesgcm") == 0) {
-        AESGCMDownload* download = malloc(sizeof(AESGCMDownload));
-        download->window = window;
-        download->url = strdup(url);
-        download->filename = strdup(filename);
-
-        pthread_create(&(download->worker), NULL, &aesgcm_file_get, download);
-        aesgcm_download_add_download(download);
-
-        free(scheme);
-
-        return;
-    }
-#endif
 
     HTTPDownload* download = malloc(sizeof(HTTPDownload));
     download->window = window;
     download->url = strdup(url);
     download->filename = strdup(filename);
+    if (cmd_template != NULL) {
+        download->cmd_template = strdup(cmd_template);
+    } else {
+        download->cmd_template = NULL;
+    }
 
     pthread_create(&(download->worker), NULL, &http_file_get, download);
     http_download_add_download(download);
-
-    free(scheme);
 }
 
 void
-_url_save_external_method(const char* scheme_cmd, const char* url, const char* filename)
+_url_external_method(const char* cmd_template, const char* url, const char* filename)
 {
-    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(url);
-        } else if (0 == g_strcmp0(argv[num_args], "%p")) {
-            g_free(argv[num_args]);
-            argv[num_args] = strdup(filename);
-        }
-        num_args++;
-    }
+    gchar** argv = format_call_external_argv(cmd_template, url, filename);
 
     if (!call_external(argv, NULL, NULL)) {
-        cons_show_error("Unable to save url: check the logs for more information.");
+        cons_show_error("Unable to call external executable for url: check the logs for more information.");
     } else {
-        cons_show("URL '%s' has been saved to '%s'.", url, filename);
+        cons_show("URL '%s' has been called with '%s'.", cmd_template);
     }
+
+    g_strfreev(argv);
 }
 
 char*
-_make_unique_filename(const char* filename)
+_unique_filename(const char* filename)
 {
     char* unique = strdup(filename);
 
@@ -9242,29 +9133,9 @@ _make_unique_filename(const char* filename)
     return unique;
 }
 
-gboolean
-cmd_url_save(ProfWin* window, const char* const command, gchar** args)
+char*
+_unique_filename_from_url(char* url, char* path)
 {
-    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* basename = NULL;
     if (path != NULL) {
@@ -9279,7 +9150,7 @@ cmd_url_save(ProfWin* window, const char* const command, gchar** args)
 
     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;
+        return NULL;
     }
 
     if (!basename) {
@@ -9289,30 +9160,106 @@ cmd_url_save(ProfWin* window, const char* const command, gchar** args)
     char* filename = NULL;
     filename = g_build_filename(directory, basename, NULL);
 
-    char* unique_filename = _make_unique_filename(filename);
+    char* unique_filename = _unique_filename(filename);
     if (!unique_filename) {
         cons_show_error("Failed to generate an unique filename from '%s'.", filename);
         free(filename);
-        return TRUE;
+        return NULL;
     }
 
     free(filename);
-    filename = unique_filename;
+    return unique_filename;
+}
+
+gboolean
+cmd_url_open(ProfWin* window, const char* const command, gchar** args)
+{
+    if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) {
+        cons_show("url open not supported in this window");
+        return TRUE;
+    }
+
+    gchar* url = args[1];
+    if (url == NULL) {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    gchar* scheme = g_uri_parse_scheme(url);
+    if (scheme == NULL) {
+        cons_show("URL '%s' is not valid.", args[1]);
+        return TRUE;
+    }
+
+    char* cmd_template = prefs_get_string_with_option(PREF_URL_OPEN_CMD, scheme);
+    if (cmd_template == NULL) {
+        cons_show("No default open command found in url open preferences");
+        return TRUE;
+    }
+
+#ifdef HAVE_OMEMO
+    // OMEMO URLs (aesgcm://) must be saved and decrypted before being opened.
+    if (0 == g_strcmp0(scheme, "aesgcm")) {
+        char* filename = _unique_filename_from_url(url, files_get_data_path(DIR_DOWNLOADS));
+        _url_aesgcm_method(window, cmd_template, url, filename);
 
-    gchar* scheme_cmd = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme);
-    if (scheme_cmd == NULL) {
+        free(filename);
+        goto out;
+    }
+#endif
+
+    _url_external_method(cmd_template, url, NULL);
+
+#ifdef HAVE_OMEMO
+out:
+#endif
+    free(cmd_template);
+    free(scheme);
+
+    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_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.", args[1]);
+        return TRUE;
+    }
+
+    char* filename = _unique_filename_from_url(url, path);
+
+    char* cmd_template = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme);
+    if (cmd_template == NULL) {
         if (g_strcmp0(scheme, "http") == 0
-            || g_strcmp0(scheme, "https") == 0
-            || g_strcmp0(scheme, "aesgcm") == 0) {
-            _url_save_fallback_method(window, url, filename);
+            || g_strcmp0(scheme, "https") == 0) {
+            _url_http_method(window, url, filename, cmd_template);
+#ifdef HAVE_OMEMO
+        } else if (g_strcmp0(scheme, "aesgcm") == 0) {
+            _url_aesgcm_method(window, url, filename, cmd_template);
+#endif
         } else {
             cons_show_error("No download method defined for the scheme '%s'.", scheme);
         }
     } else {
-        _url_save_external_method(scheme_cmd, url, filename);
+        _url_external_method(cmd_template, url, filename);
     }
 
-    g_free(scheme_cmd);
+    free(cmd_template);
 
     return TRUE;
 }
diff --git a/src/common.c b/src/common.c
index c0bd6525..079c3af5 100644
--- a/src/common.c
+++ b/src/common.c
@@ -555,3 +555,23 @@ call_external(gchar** argv, gchar*** const output_ptr, gchar*** const error_ptr)
 
     return TRUE;
 }
+
+gchar**
+format_call_external_argv(const char* template, const char* url, const char* filename)
+{
+    gchar** argv = g_strsplit(template, " ", 0);
+
+    guint num_args = 0;
+    while (argv[num_args]) {
+        if (0 == g_strcmp0(argv[num_args], "%u") && url != NULL) {
+            g_free(argv[num_args]);
+            argv[num_args] = g_strdup(url);
+        } else if (0 == g_strcmp0(argv[num_args], "%p") && filename != NULL) {
+            g_free(argv[num_args]);
+            argv[num_args] = strdup(filename);
+        }
+        num_args++;
+    }
+
+    return argv;
+}
diff --git a/src/common.h b/src/common.h
index 13332f7a..b56d31d4 100644
--- a/src/common.h
+++ b/src/common.h
@@ -105,5 +105,6 @@ void get_file_paths_recursive(const char* directory, GSList** contents);
 char* get_random_string(int length);
 
 gboolean call_external(gchar** argv, gchar*** const output_ptr, gchar*** const error_ptr);
+gchar** format_call_external_argv(const char* template, const char* url, const char* filename);
 
 #endif
diff --git a/src/config/files.h b/src/config/files.h
index d5c96b0f..42499663 100644
--- a/src/config/files.h
+++ b/src/config/files.h
@@ -48,15 +48,16 @@
 #define FILE_PROFANITY_IDENTIFIER     "profident"
 #define FILE_BOOKMARK_AUTOJOIN_IGNORE "bookmark_ignore"
 
-#define DIR_THEMES   "themes"
-#define DIR_ICONS    "icons"
-#define DIR_SCRIPTS  "scripts"
-#define DIR_CHATLOGS "chatlogs"
-#define DIR_OTR      "otr"
-#define DIR_PGP      "pgp"
-#define DIR_OMEMO    "omemo"
-#define DIR_PLUGINS  "plugins"
-#define DIR_DATABASE "database"
+#define DIR_THEMES    "themes"
+#define DIR_ICONS     "icons"
+#define DIR_SCRIPTS   "scripts"
+#define DIR_CHATLOGS  "chatlogs"
+#define DIR_OTR       "otr"
+#define DIR_PGP       "pgp"
+#define DIR_OMEMO     "omemo"
+#define DIR_PLUGINS   "plugins"
+#define DIR_DATABASE  "database"
+#define DIR_DOWNLOADS "downloads"
 
 void files_create_directories(void);
 
diff --git a/src/config/preferences.c b/src/config/preferences.c
index c7a74429..4b524fcf 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -81,7 +81,6 @@ static const char* _get_group(preference_t pref);
 static const char* _get_key(preference_t pref);
 static gboolean _get_default_boolean(preference_t pref);
 static char* _get_default_string(preference_t pref);
-static char** _get_default_string_list(preference_t pref);
 
 static void
 _prefs_load(void)
@@ -544,33 +543,6 @@ prefs_get_string_with_option(preference_t pref, gchar* option)
     return result;
 }
 
-gchar**
-prefs_get_string_list_with_option(preference_t pref, gchar* option)
-{
-    const char* group = _get_group(pref);
-    const char* key = _get_key(pref);
-    char** def = _get_default_string_list(pref);
-
-    gchar** result = g_key_file_get_locale_string_list(prefs, group, key, option, NULL, NULL);
-    if (result) {
-        g_strfreev(def);
-        return result;
-    }
-
-    result = g_key_file_get_string_list(prefs, group, key, NULL, NULL);
-    if (result) {
-        g_strfreev(def);
-        return result;
-    }
-
-    if (def) {
-        return def;
-    } else {
-        g_strfreev(def);
-        return NULL;
-    }
-}
-
 void
 prefs_set_string(preference_t pref, char* value)
 {
@@ -1893,6 +1865,7 @@ _get_group(preference_t pref)
         return PREF_GROUP_LOGGING;
     case PREF_AVATAR_CMD:
     case PREF_URL_OPEN_CMD:
+        return PREF_GROUP_EXECUTABLES;
     case PREF_URL_SAVE_CMD:
         return PREF_GROUP_EXECUTABLES;
     case PREF_AUTOAWAY_CHECK:
@@ -2318,24 +2291,10 @@ _get_default_string(preference_t pref)
         return "false";
     case PREF_AVATAR_CMD:
         return "xdg-open";
-    default:
-        return NULL;
-    }
-}
-
-// the default setting for a string list type preference
-// if it is not specified in .profrc
-static char**
-_get_default_string_list(preference_t pref)
-{
-    char** str_array = NULL;
-
-    switch (pref) {
     case PREF_URL_OPEN_CMD:
-        str_array = g_malloc0(3);
-        str_array[0] = g_strdup("false");
-        str_array[1] = g_strdup("xdg-open %u");
-        return str_array;
+        return "xdg-open %u";
+    case PREF_URL_SAVE_CMD:
+        return NULL; // Default to built-in method.
     default:
         return NULL;
     }
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 141d8fce..bfad7d6b 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -320,7 +320,6 @@ gboolean prefs_get_boolean(preference_t pref);
 void prefs_set_boolean(preference_t pref, gboolean value);
 char* prefs_get_string(preference_t pref);
 char* prefs_get_string_with_option(preference_t pref, gchar* option);
-gchar** prefs_get_string_list_with_option(preference_t pref, gchar* option);
 void prefs_set_string(preference_t pref, char* value);
 void prefs_set_string_with_option(preference_t pref, char* option, char* value);
 void prefs_set_string_list_with_option(preference_t pref, char* option, const gchar* const* values);
diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c
index 2e698591..22ada3a8 100644
--- a/src/omemo/omemo.c
+++ b/src/omemo/omemo.c
@@ -45,7 +45,6 @@
 #include <signal/signal_protocol.h>
 #include <signal/session_builder.h>
 #include <signal/session_cipher.h>
-#include <gcrypt.h>
 
 #include "config/account.h"
 #include "config/files.h"
diff --git a/src/omemo/omemo.h b/src/omemo/omemo.h
index b8d84498..8c17f48d 100644
--- a/src/omemo/omemo.h
+++ b/src/omemo/omemo.h
@@ -33,6 +33,7 @@
  *
  */
 #include <glib.h>
+#include <gcrypt.h>
 
 #include "ui/ui.h"
 #include "config/account.h"
diff --git a/src/tools/aesgcm_download.c b/src/tools/aesgcm_download.c
index d6a85d06..d75cabe3 100644
--- a/src/tools/aesgcm_download.c
+++ b/src/tools/aesgcm_download.c
@@ -47,6 +47,7 @@
 #include <gio/gio.h>
 #include <pthread.h>
 #include <assert.h>
+#include <errno.h>
 
 #include "profanity.h"
 #include "event/client_events.h"
@@ -146,8 +147,28 @@ aesgcm_file_get(void* userdata)
     free(https_url);
     free(fragment);
 
+    if (aesgcm_dl->cmd_template != NULL) {
+        gchar** argv = format_call_external_argv(aesgcm_dl->cmd_template,
+                                                 aesgcm_dl->url,
+                                                 aesgcm_dl->filename);
+
+        // TODO(wstrm): Log the error.
+        if (!call_external(argv, NULL, NULL)) {
+            http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->url,
+                                       "Downloading '%s' failed: Unable to call "
+                                       "command '%s' with file at '%s' (%s).",
+                                       aesgcm_dl->url,
+                                       aesgcm_dl->cmd_template,
+                                       aesgcm_dl->filename,
+                                       "TODO(wstrm): Log the error");
+        }
+
+        g_strfreev(argv);
+    }
+
     free(aesgcm_dl->filename);
     free(aesgcm_dl->url);
+    free(aesgcm_dl->cmd_template);
     free(aesgcm_dl);
 
     return NULL;
diff --git a/src/tools/aesgcm_download.h b/src/tools/aesgcm_download.h
index e172b89a..c0096f1d 100644
--- a/src/tools/aesgcm_download.h
+++ b/src/tools/aesgcm_download.h
@@ -52,6 +52,7 @@ typedef struct aesgcm_download_t
 {
     char* url;
     char* filename;
+    char* cmd_template;
     ProfWin* window;
     pthread_t worker;
     HTTPDownload* http_dl;
diff --git a/src/tools/http_common.h b/src/tools/http_common.h
index 3fbc6fcd..c0a553de 100644
--- a/src/tools/http_common.h
+++ b/src/tools/http_common.h
@@ -40,7 +40,7 @@
 
 char* http_basename_from_url(const char* url);
 void http_print_transfer(ProfWin* window, char* url, const char* fmt, ...);
-void http_print_transfer_update(ProfWin* window, char* url,
-                                const char* fmt, ...);
+void http_print_transfer_update(ProfWin* window, char* url, const char* fmt, ...);
+gchar** http_format_external_argv(const char* cmd, const char* url, const char* filename);
 
 #endif
diff --git a/src/tools/http_download.c b/src/tools/http_download.c
index d14ab0e8..ef7e2906 100644
--- a/src/tools/http_download.c
+++ b/src/tools/http_download.c
@@ -47,6 +47,7 @@
 #include <gio/gio.h>
 #include <pthread.h>
 #include <assert.h>
+#include <errno.h>
 
 #include "profanity.h"
 #include "event/client_events.h"
@@ -187,6 +188,25 @@ http_file_get(void* userdata)
     download_processes = g_slist_remove(download_processes, download);
     pthread_mutex_unlock(&lock);
 
+    if (download->cmd_template != NULL) {
+        gchar** argv = format_call_external_argv(download->cmd_template,
+                                                 download->url,
+                                                 download->filename);
+
+        // TODO(wstrm): Log the error.
+        if (!call_external(argv, NULL, NULL)) {
+            http_print_transfer_update(download->window, download->url,
+                                       "Downloading '%s' failed: Unable to call "
+                                       "command '%s' with file at '%s' (%s).",
+                                       download->url,
+                                       download->cmd_template,
+                                       download->filename,
+                                       "TODO(wstrm): Log the error");
+        }
+
+        g_strfreev(argv);
+    }
+
     free(download->url);
     free(download->filename);
     free(download);
diff --git a/src/tools/http_download.h b/src/tools/http_download.h
index b6ce42ca..23344f6c 100644
--- a/src/tools/http_download.h
+++ b/src/tools/http_download.h
@@ -51,6 +51,7 @@ typedef struct http_download_t
 {
     char* url;
     char* filename;
+    char* cmd_template;
     curl_off_t bytes_received;
     ProfWin* window;
     pthread_t worker;
diff --git a/src/ui/console.c b/src/ui/console.c
index 8d028139..dd217105 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -2074,9 +2074,9 @@ cons_executable_setting(void)
 
     //TODO: there needs to be a way to get all the "locales"/schemes so we can
     //display the defualt openers for all filetypes
-    gchar** urlopen = prefs_get_string_list_with_option(PREF_URL_OPEN_CMD, "");
+    char* urlopen = prefs_get_string_with_option(PREF_URL_OPEN_CMD, "");
     cons_show("Default '/url open' command (/executable urlopen)                        : %s", urlopen[1]);
-    g_strfreev(urlopen);
+    g_free(urlopen);
 
     char* urlsave = prefs_get_string(PREF_URL_SAVE_CMD);
     cons_show("Default '/url save' command (/executable urlsave)                        : %s", urlsave);