From 3a6597ee2967f91f49a1b4e17cf0595f37064587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Wennerstr=C3=B6m?= Date: Thu, 3 Dec 2020 16:43:07 +0100 Subject: [PATCH] Refactor for threaded external executable for built-in download methods --- Makefile.am | 5 +- src/command/cmd_funcs.c | 315 +++++++++++++++--------------------- src/common.c | 20 +++ src/common.h | 1 + src/config/files.h | 19 +-- src/config/preferences.c | 49 +----- src/config/preferences.h | 1 - src/omemo/omemo.c | 1 - src/omemo/omemo.h | 1 + src/tools/aesgcm_download.c | 21 +++ src/tools/aesgcm_download.h | 1 + src/tools/http_common.h | 4 +- src/tools/http_download.c | 20 +++ src/tools/http_download.h | 1 + src/ui/console.c | 4 +- 15 files changed, 216 insertions(+), 247 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) -{ - 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"); - } - - 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. -} - -void -_url_save_fallback_method(ProfWin* window, 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; +void +_url_aesgcm_method(ProfWin* window, const char* cmd_template, const char* url, const char* filename) +{ + 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; } + + pthread_create(&(download->worker), NULL, &aesgcm_file_get, download); + aesgcm_download_add_download(download); +} #endif +void +_url_http_method(ProfWin* window, const char* cmd_template, const char* url, const char* filename) +{ + 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,6 +9133,92 @@ _make_unique_filename(const char* filename) return unique; } +char* +_unique_filename_from_url(char* url, char* path) +{ + gchar* directory = NULL; + gchar* basename = NULL; + if (path != NULL) { + directory = g_path_get_dirname(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 NULL; + } + + if (!basename) { + basename = http_basename_from_url(url); + } + + char* filename = NULL; + filename = g_build_filename(directory, basename, NULL); + + char* unique_filename = _unique_filename(filename); + if (!unique_filename) { + cons_show_error("Failed to generate an unique filename from '%s'.", filename); + free(filename); + return NULL; + } + + free(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); + + 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) { @@ -9260,59 +9237,29 @@ cmd_url_save(ProfWin* window, const char* const command, gchar** args) gchar* scheme = g_uri_parse_scheme(url); if (scheme == NULL) { - cons_show("URL '%s' is not valid.", url); - g_free(url); + cons_show("URL '%s' is not valid.", args[1]); return TRUE; } - gchar* directory = NULL; - gchar* basename = NULL; - if (path != NULL) { - directory = g_path_get_dirname(path); - basename = g_path_get_basename(path); - } + char* filename = _unique_filename_from_url(url, 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) { + 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 #include #include -#include #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 +#include #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 #include #include +#include #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 #include #include +#include #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);