about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorMichael Vetter <jubalh@iodoru.org>2020-07-02 11:26:18 +0200
committerGitHub <noreply@github.com>2020-07-02 11:26:18 +0200
commit9774b0c5509193027d4f68df9dcb862455699cfc (patch)
tree1ade86abc718ae2cc06199c12c5d6a4f0cb49df0 /src
parent3af5f33489d1e065c9da03b0bb99eddff57816a4 (diff)
parent86cd33405e7f67647ce2c171e19bab4120201140 (diff)
downloadprofani-tty-9774b0c5509193027d4f68df9dcb862455699cfc.tar.gz
Merge pull request #1374 from profanity-im/revampUrlopen
Rework /url and /executable for filetypes
Diffstat (limited to 'src')
-rw-r--r--src/command/cmd_ac.c26
-rw-r--r--src/command/cmd_defs.c50
-rw-r--r--src/command/cmd_funcs.c231
-rw-r--r--src/command/cmd_funcs.h3
-rw-r--r--src/config/preferences.c147
-rw-r--r--src/config/preferences.h5
-rw-r--r--src/ui/console.c14
-rw-r--r--src/ui/window_list.c2
8 files changed, 426 insertions, 52 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index eefca0cc..0d9d4cc5 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -124,7 +124,7 @@ static char* _avatar_autocomplete(ProfWin *window, const char *const input, gboo
 static char* _correction_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _correct_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _software_autocomplete(ProfWin *window, const char *const input, gboolean previous);
-static char* _urlopen_autocomplete(ProfWin *window, const char *const input, gboolean previous);
+static char* _url_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _executable_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 
 static char* _script_autocomplete_func(const char *const prefix, gboolean previous, void *context);
@@ -263,6 +263,7 @@ static Autocomplete logging_group_ac;
 static Autocomplete color_ac;
 static Autocomplete correction_ac;
 static Autocomplete avatar_ac;
+static Autocomplete url_ac;
 static Autocomplete executable_ac;
 
 /*!
@@ -1041,9 +1042,14 @@ cmd_ac_init(void)
     autocomplete_add(avatar_ac, "get");
     autocomplete_add(avatar_ac, "open");
 
+    url_ac = autocomplete_new();
+    autocomplete_add(url_ac, "open");
+    autocomplete_add(url_ac, "save");
+
     executable_ac = autocomplete_new();
     autocomplete_add(executable_ac, "avatar");
     autocomplete_add(executable_ac, "urlopen");
+    autocomplete_add(executable_ac, "urlsave");
 }
 
 void
@@ -1362,6 +1368,7 @@ cmd_ac_reset(ProfWin *window)
     autocomplete_reset(color_ac);
     autocomplete_reset(correction_ac);
     autocomplete_reset(avatar_ac);
+    autocomplete_reset(url_ac);
     autocomplete_reset(executable_ac);
 
     autocomplete_reset(script_ac);
@@ -1523,6 +1530,7 @@ cmd_ac_uninit(void)
     autocomplete_free(color_ac);
     autocomplete_free(correction_ac);
     autocomplete_free(avatar_ac);
+    autocomplete_free(url_ac);
     autocomplete_free(executable_ac);
 }
 
@@ -1784,7 +1792,7 @@ _cmd_ac_complete_params(ProfWin *window, const char *const input, gboolean previ
     g_hash_table_insert(ac_funcs, "/correction",    _correction_autocomplete);
     g_hash_table_insert(ac_funcs, "/correct",       _correct_autocomplete);
     g_hash_table_insert(ac_funcs, "/software",      _software_autocomplete);
-    g_hash_table_insert(ac_funcs, "/urlopen",       _urlopen_autocomplete);
+    g_hash_table_insert(ac_funcs, "/url",           _url_autocomplete);
     g_hash_table_insert(ac_funcs, "/executable",    _executable_autocomplete);
 
     int len = strlen(input);
@@ -4133,14 +4141,24 @@ _software_autocomplete(ProfWin *window, const char *const input, gboolean previo
 }
 
 static char*
-_urlopen_autocomplete(ProfWin *window, const char *const input, gboolean previous)
+_url_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 {
     char *result = NULL;
 
+    result = autocomplete_param_with_ac(input, "/url", url_ac, TRUE, previous);
+    if (result) {
+        return result;
+    }
+
 	if (window->type == WIN_CHAT ||
         window->type == WIN_MUC ||
         window->type == WIN_PRIVATE) {
-        result = autocomplete_param_with_func(input, "/urlopen", wins_get_url, previous, window);
+        result = autocomplete_param_with_func(input, "/url open", wins_get_url, previous, window);
+        if (result) {
+            return result;
+        }
+
+        result = autocomplete_param_with_func(input, "/url save", wins_get_url, previous, window);
     }
 
     return result;
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 815fe9d8..0e91f803 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -2506,40 +2506,50 @@ static struct cmd_t command_defs[] =
             "/software xmpp.vanaheimr.edda")
     },
 
-    { "/urlopen",
-        parse_args, 1, -1, NULL,
-        CMD_NOSUBFUNCS
-        CMD_MAINFUNC(cmd_urlopen)
-        CMD_TAGS(
-            CMD_TAG_CHAT,
-            CMD_TAG_GROUPCHAT)
-        CMD_SYN(
-            "/urlopen <url>")
-        CMD_DESC(
-            "Open the URL")
-        CMD_ARGS(
-            { "<url>",    "URL to open."})
-        CMD_NOEXAMPLES
-    },
-
     { "/executable",
-        parse_args, 2, 2, &cons_executable_setting,
+        parse_args, 2, 4, &cons_executable_setting,
         CMD_NOSUBFUNCS
         CMD_MAINFUNC(cmd_executable)
         CMD_TAGS(
             CMD_TAG_DISCOVERY)
         CMD_SYN(
             "/executable avatar <cmd>",
-            "/executable urlopen <cmd>")
+            "/executable urlopen (<fileType>|DEF <require_save> <cmd>",
+            "/executable urlsave (<protocol>|DEF) <cmd>")
         CMD_DESC(
             "Configure executable that should be called upon a certain command."
             "Default is xdg-open.")
         CMD_ARGS(
             { "avatar", "Set executable that is run in /avatar open. Use your favourite image viewer." },
-            { "urlopen", "Set executable that is run in /urlopen. Use your favourite browser." })
+            { "urlopen", "Set executable that is run in /url open for a given file type. It may be your favorite browser or a specific viewer. Use DEF to set default command for undefined file type." },
+            { "urlsave", "Set executable that is run in /url save for a given protocol. Use your favourite downloader. Use DEF to set default command for undefined protocol."})
         CMD_EXAMPLES(
             "/executable avatar xdg-open",
-            "/executable urlopen firefox")
+            "/executable urlopen DEF false \"xdg-open %u\"",
+            "/executable urlopen html false \"firefox %u\"",
+            "/executable urlsave aesgcm \"omut -d %u %p\"")
+    },
+
+    { "/url",
+        parse_args, 2, 3, NULL,
+        CMD_SUBFUNCS(
+            { "open", cmd_url_open},
+            { "save", cmd_url_save })
+        CMD_NOMAINFUNC
+        CMD_TAGS(
+            CMD_TAG_CHAT,
+            CMD_TAG_GROUPCHAT)
+        CMD_SYN(
+            "/url open <url>",
+            "/url save <url> [<path>]")
+        CMD_DESC(
+            "Deal with URLs")
+        CMD_ARGS(
+            { "open", "Open URL with predefined executable." },
+            { "save", "Save URL to optional path, default path is current directory"})
+        CMD_EXAMPLES(
+            "/url open https://profanity-im.github.io",
+            "/url save https://profanity-im.github.io/guide/latest/userguide.html /home/user/Download/")
     },
 };
 
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index bd4dc95f..b60c9667 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -44,6 +44,8 @@
 #include <errno.h>
 #include <assert.h>
 #include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <fcntl.h>
@@ -9071,29 +9073,210 @@ cmd_slashguard(ProfWin *window, const char *const command, gchar **args)
 }
 
 gboolean
-cmd_urlopen(ProfWin *window, const char *const command, gchar **args)
+cmd_url_open(ProfWin *window, const char *const command, gchar **args)
 {
-	if (window->type == WIN_CHAT ||
-        window->type == WIN_MUC ||
-        window->type == WIN_PRIVATE) {
+    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[0] == NULL) {
-            cons_bad_cmd_usage(command);
-            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* cmd = prefs_get_string(PREF_URL_OPEN_CMD);
-        gchar *argv[] = {cmd, args[0], NULL};
+    gchar *suffix_cmd = g_strdup(suffix_cmd_pref[1]);
+    g_strfreev(suffix_cmd_pref);
 
-        if (!call_external(argv, NULL, NULL)) {
-          cons_show_error("Unable to open url: check the logs for more information.");
+    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.");
+    }
 
-        g_free(cmd);
+    if (require_save) {
+        g_unlink("/tmp/profanity.tmp");
+    }
+
+    g_strfreev(argv);
+    g_free(suffix_cmd);
+
+    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]);
+
+    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 (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);
+    }
+
+    g_object_unref(file);
+    file = NULL;
+
+    if (base_name == NULL) {
+        base_name = g_path_get_basename(target_path);
+        target_dir = g_path_get_dirname(target_path);
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    gchar *scheme_cmd = NULL;
+
+    if (0 == g_strcmp0(scheme, "http")
+        || 0 == g_strcmp0(scheme, "https")
+        || 0 == g_strcmp0(scheme, "aesgcm")
+       ) {
+        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);
+
+    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);
+        } else if (0 == g_strcmp0(argv[num_args], "%p")) {
+            g_free(argv[num_args]);
+            argv[num_args] = target_path;
+        }
+        num_args++;
+    }
+
+    if (!call_external(argv, NULL, NULL)) {
+        cons_show_error("Unable to save url: check the logs for more information.");
     } else {
-        cons_show("urlopen not supported in this window");
+        cons_show("URL '%s' has been saved into '%s'.", uri, target_path);
     }
 
+    g_free(target_dir);
+    g_free(base_name);
+    g_strfreev(argv);
+
     return TRUE;
 }
 
@@ -9104,8 +9287,26 @@ cmd_executable(ProfWin *window, const char *const command, gchar **args)
         prefs_set_string(PREF_AVATAR_CMD, args[1]);
         cons_show("Avatar command set to: %s", args[1]);
     } else if (g_strcmp0(args[0], "urlopen") == 0) {
-        prefs_set_string(PREF_URL_OPEN_CMD, args[1]);
-        cons_show("urlopen command set to: %s", args[1]);
+        if (g_strv_length(args) < 4) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        gchar *str = g_strjoinv(" ", &args[3]);
+        const gchar* const list[] = {args[2], str, NULL};
+        prefs_set_string_list_with_option(PREF_URL_OPEN_CMD, args[1], list);
+        cons_show("`url open` command set to: %s for %s files", str, args[1]);
+        g_free(str);
+    } else if (g_strcmp0(args[0], "urlsave") == 0) {
+        if (g_strv_length(args) < 3) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        gchar *str = g_strjoinv(" ", &args[2]);
+        prefs_set_string_with_option(PREF_URL_SAVE_CMD, args[1], str);
+        cons_show("`url save` command set to: %s for scheme %s", str, args[1]);
+        g_free(str);
     } else {
         cons_bad_cmd_usage(command);
     }
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index b75755cb..955a2e39 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -236,7 +236,8 @@ gboolean cmd_correction(ProfWin *window, const char *const command, gchar **args
 gboolean cmd_correct(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_slashguard(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_serversoftware(ProfWin *window, const char *const command, gchar **args);
-gboolean cmd_urlopen(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_url_open(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_url_save(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_executable(ProfWin *window, const char *const command, gchar **args);
 
 #endif
diff --git a/src/config/preferences.c b/src/config/preferences.c
index 87f1acad..e46cbe45 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -65,6 +65,7 @@
 #define PREF_GROUP_OX "ox"
 #define PREF_GROUP_MUC "muc"
 #define PREF_GROUP_PLUGINS "plugins"
+#define PREF_GROUP_EXECUTABLES "executables"
 
 #define INPBLOCK_DEFAULT 1000
 
@@ -80,6 +81,7 @@ 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)
 {
@@ -160,6 +162,27 @@ static void _prefs_load(void)
         }
     }
 
+    // 0.9.0 introduced /urlopen. It was saved under "logging" section. Now we have a new "executables" section.
+    if (g_key_file_has_key(prefs, PREF_GROUP_LOGGING, "urlopen.cmd", NULL)) {
+        char *val = g_key_file_get_string(prefs, PREF_GROUP_LOGGING, "urlopen.cmd", NULL);
+
+        GString *value = g_string_new("false;");
+        value = g_string_append(value, val);
+        value = g_string_append(value, " %u;");
+
+        g_key_file_set_locale_string(prefs, PREF_GROUP_EXECUTABLES, "url.open.cmd", "DEF", value->str);
+        g_key_file_remove_key(prefs, PREF_GROUP_LOGGING, "urlopen.cmd", NULL);
+
+        g_string_free(value, TRUE);
+    }
+
+    // 0.9.0 introduced configurable /avatar. It was saved under "logging" section. Now we have a new "executables" section.
+    if (g_key_file_has_key(prefs, PREF_GROUP_LOGGING, "avatar.cmd", NULL)) {
+        char *value = g_key_file_get_string(prefs, PREF_GROUP_LOGGING, "avatar.cmd", NULL);
+        g_key_file_set_string(prefs, PREF_GROUP_EXECUTABLES, "avatar.cmd", value);
+        g_key_file_remove_key(prefs, PREF_GROUP_LOGGING, "avatar.cmd", NULL);
+    }
+
     _save_prefs();
 
     boolean_choice_ac = autocomplete_new();
@@ -218,7 +241,7 @@ prefs_load(char *config_file)
     }
 
     prefs = g_key_file_new();
-    g_key_file_load_from_file(prefs, prefs_loc, G_KEY_FILE_KEEP_COMMENTS, NULL);
+    g_key_file_load_from_file(prefs, prefs_loc, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
 
     _prefs_load();
 }
@@ -494,12 +517,62 @@ prefs_get_string(preference_t pref)
     }
 }
 
+char*
+prefs_get_string_with_option(preference_t pref, gchar *option)
+{
+    const char *group = _get_group(pref);
+    const char *key = _get_key(pref);
+    char *def = _get_default_string(pref);
+
+    char *result = g_key_file_get_locale_string(prefs, group, key, option, NULL);
+
+    if (result == NULL) {
+        // check for user set default
+        result = g_key_file_get_locale_string(prefs, group, key, "DEF", NULL);
+        if (result == NULL) {
+            if (def) {
+                // use hardcoded profanity default
+                return g_strdup(def);
+            } else {
+                return NULL;
+            }
+        }
+    }
+
+    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_free_string(char *pref)
 {
-    if (pref) {
-        g_free(pref);
-    }
+    g_free(pref);
 }
 
 void
@@ -514,6 +587,42 @@ prefs_set_string(preference_t pref, char *value)
     }
 }
 
+void
+prefs_set_string_with_option(preference_t pref, char *option, char *value)
+{
+    const char *group = _get_group(pref);
+    const char *key = _get_key(pref);
+    if (value == NULL) {
+        g_key_file_remove_key(prefs, group, key, NULL);
+    } else {
+        g_key_file_set_locale_string(prefs, group, key, option, value);
+    }
+}
+
+void
+prefs_set_string_list_with_option(preference_t pref, char *option, const gchar* const *values)
+{
+    const char *group = _get_group(pref);
+    const char *key = _get_key(pref);
+    if (values == NULL || *values == NULL){
+        if (g_strcmp0(option, "*") == 0) {
+            g_key_file_set_string_list(prefs, group, key, NULL, 0);
+        } else {
+            g_key_file_set_locale_string_list(prefs, group, key, option, NULL, 0);
+        }
+    } else {
+        guint num_values = 0;
+        while(values[num_values]) {
+            num_values++;
+        }
+        if (g_strcmp0(option, "*") == 0) {
+            g_key_file_set_string_list(prefs, group, key, values, num_values);
+        } else {
+            g_key_file_set_locale_string_list(prefs, group, key, option, values, num_values);
+        }
+    }
+}
+
 char*
 prefs_get_tls_certpath(void)
 {
@@ -1796,9 +1905,11 @@ _get_group(preference_t pref)
         case PREF_GRLOG:
         case PREF_LOG_ROTATE:
         case PREF_LOG_SHARED:
+            return PREF_GROUP_LOGGING;
         case PREF_AVATAR_CMD:
         case PREF_URL_OPEN_CMD:
-            return PREF_GROUP_LOGGING;
+        case PREF_URL_SAVE_CMD:
+            return PREF_GROUP_EXECUTABLES;
         case PREF_AUTOAWAY_CHECK:
         case PREF_AUTOAWAY_MODE:
         case PREF_AUTOAWAY_MESSAGE:
@@ -2088,7 +2199,9 @@ _get_key(preference_t pref)
         case PREF_MAM:
             return "mam";
         case PREF_URL_OPEN_CMD:
-            return "urlopen.cmd";
+            return "url.open.cmd";
+        case PREF_URL_SAVE_CMD:
+            return "url.save.cmd";
         default:
             return NULL;
     }
@@ -2225,8 +2338,28 @@ _get_default_string(preference_t pref)
         case PREF_COLOR_NICK:
             return "false";
         case PREF_AVATAR_CMD:
-        case PREF_URL_OPEN_CMD:
             return "xdg-open";
+        case PREF_URL_SAVE_CMD:
+            return "curl -o %p %u";
+        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;
         default:
             return NULL;
     }
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 920342b8..e3110904 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -172,6 +172,7 @@ typedef enum {
     PREF_SLASH_GUARD,
     PREF_MAM,
     PREF_URL_OPEN_CMD,
+    PREF_URL_SAVE_CMD,
 } preference_t;
 
 typedef struct prof_alias_t {
@@ -317,8 +318,12 @@ void prefs_save_win_placement(ProfWinPlacement *placement);
 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_free_string(char *pref);
 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);
 
 char* prefs_get_tls_certpath(void);
 
diff --git a/src/ui/console.c b/src/ui/console.c
index 8b6693e4..47267e1c 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -2067,12 +2067,18 @@ void
 cons_executable_setting(void)
 {
     char *avatar = prefs_get_string(PREF_AVATAR_CMD);
-    cons_show("Avatar command (/executable avatar)                                   : %s", avatar);
+    cons_show("Default '/avatar open' command (/executable avatar)                      : %s", avatar);
     prefs_free_string(avatar);
 
-    char *exec = prefs_get_string(PREF_URL_OPEN_CMD);
-    cons_show("urlopen command (/executable urlopen)                                 : %s", exec);
-    prefs_free_string(exec);
+    //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, "");
+    cons_show("Default '/url open' command (/executable urlopen)                        : %s", urlopen[1]);
+    g_strfreev(urlopen);
+
+    char *urlsave = prefs_get_string(PREF_URL_SAVE_CMD);
+    cons_show("Default '/url save' command (/executable urlsave)                        : %s", urlsave);
+    g_free(urlsave);
 }
 
 void
diff --git a/src/ui/window_list.c b/src/ui/window_list.c
index 01b5177f..3972fadb 100644
--- a/src/ui/window_list.c
+++ b/src/ui/window_list.c
@@ -1156,7 +1156,7 @@ wins_add_urls_ac(const ProfWin *const win, const ProfMessage *const message)
     GRegex *regex;
     GMatchInfo *match_info;
 
-    regex = g_regex_new("https?://\\S+", 0, 0, NULL);
+    regex = g_regex_new("(https?|aesgcm)://\\S+", 0, 0, NULL);
     g_regex_match (regex, message->plain, 0, &match_info);
 
     while (g_match_info_matches (match_info))