From b208e48ee1daa9ded411890aedf528fb063f2574 Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Sat, 11 Jul 2026 15:54:32 +0000 Subject: [PATCH 1/2] feat(cli): resolve contact names to JIDs in multiple commands and improve autocomplete Introduce _resolve_contact_jid helper to convert contact names to JIDs. Apply this helper to /caps, /software, /disco, /lastactivity, and /ping commands, allowing users to specify contacts by name instead of full JIDs. Enhance command-line autocomplete by adding _disco_autocomplete and _roster_jid_autocomplete functions. Update /caps autocomplete to utilize the new roster resolution logic and ensure it only returns results when connected. --- src/command/cmd_ac.c | 90 +++++++++++++++++++++++++---------------- src/command/cmd_funcs.c | 41 +++++++++++-------- 2 files changed, 80 insertions(+), 51 deletions(-) diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index 6b24dc22..6e91f057 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -110,6 +110,8 @@ static char* _software_autocomplete(ProfWin* window, const char* const input, gb 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* _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean previous); +static char* _disco_autocomplete(ProfWin* window, const char* const input, gboolean previous); +static char* _roster_jid_autocomplete(const char* const input, const char* const prefix, gboolean previous); static char* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _mood_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _strophe_autocomplete(ProfWin* window, const char* const input, gboolean previous); @@ -122,6 +124,7 @@ static char* _ai_autocomplete(ProfWin* window, const char* const input, gboolean static char* _script_autocomplete_func(const char* const prefix, gboolean previous, void* context); static char* _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous); +static gboolean _is_connected(void); static Autocomplete commands_ac; static Autocomplete who_room_ac; @@ -1426,6 +1429,7 @@ cmd_ac_init(void) g_hash_table_insert(ac_funcs, "/color", _color_autocomplete); g_hash_table_insert(ac_funcs, "/connect", _connect_autocomplete); g_hash_table_insert(ac_funcs, "/console", _console_autocomplete); + g_hash_table_insert(ac_funcs, "/disco", _disco_autocomplete); g_hash_table_insert(ac_funcs, "/correct", _correct_autocomplete); g_hash_table_insert(ac_funcs, "/correction", _correction_autocomplete); g_hash_table_insert(ac_funcs, "/executable", _executable_autocomplete); @@ -1941,7 +1945,6 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ Autocomplete completer; } ac_cmds[] = { { "/prefs", prefs_ac }, - { "/disco", disco_ac }, { "/room", room_ac }, { "/mainwin", winpos_ac }, { "/inputwin", winpos_ac }, @@ -1991,6 +1994,31 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ return NULL; } +static gboolean +_is_connected(void) +{ + return connection_get_status() == JABBER_CONNECTED; +} + +static char* +_roster_jid_autocomplete(const char* const input, const char* const prefix, gboolean previous) +{ + if (!_is_connected()) { + return NULL; + } + char* result = NULL; + result = autocomplete_param_with_func(input, prefix, roster_contact_autocomplete, previous, NULL); + if (result) { + return result; + } + result = autocomplete_param_with_func(input, prefix, roster_barejid_autocomplete, previous, NULL); + if (result) { + return result; + } + result = autocomplete_param_with_func(input, prefix, roster_fulljid_autocomplete, previous, NULL); + return result; +} + static char* _caps_autocomplete(ProfWin* window, const char* const input, gboolean previous) { @@ -2006,20 +2034,29 @@ _caps_autocomplete(ProfWin* window, const char* const input, gboolean previous) if (nick_ac) { result = autocomplete_param_with_ac(input, "/caps", nick_ac, TRUE, previous); } - } else if (connection_get_status() == JABBER_CONNECTED) { - result = autocomplete_param_with_func(input, "/caps", roster_contact_autocomplete, previous, NULL); - if (result) { - return result; - } - result = autocomplete_param_with_func(input, "/caps", roster_barejid_autocomplete, previous, NULL); - if (result) { - return result; - } - result = autocomplete_param_with_func(input, "/caps", roster_fulljid_autocomplete, previous, NULL); + } else { + result = _roster_jid_autocomplete(input, "/caps", previous); } return result; } +static char* +_disco_autocomplete(ProfWin* window, const char* const input, gboolean previous) +{ + char* result = NULL; + result = autocomplete_param_with_ac(input, "/disco", disco_ac, TRUE, previous); + if (result) { + return result; + } + + result = _roster_jid_autocomplete(input, "/disco info", previous); + if (result) { + return result; + } + result = _roster_jid_autocomplete(input, "/disco items", previous); + return result; +} + static char* _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous) { @@ -4032,10 +4069,7 @@ _invite_autocomplete(ProfWin* window, const char* const input, gboolean previous return result; } - jabber_conn_status_t conn_status = connection_get_status(); - - if (conn_status == JABBER_CONNECTED) { - result = autocomplete_param_with_func(input, "/invite send", roster_contact_autocomplete, previous, NULL); + result = _roster_jid_autocomplete(input, "/invite send", previous); if (result) { return result; } @@ -4043,7 +4077,6 @@ _invite_autocomplete(ProfWin* window, const char* const input, gboolean previous result = autocomplete_param_with_func(input, "/invite decline", muc_invites_find, previous, NULL); if (result) { return result; - } } return NULL; @@ -4059,10 +4092,6 @@ _status_autocomplete(ProfWin* window, const char* const input, gboolean previous return result; } - jabber_conn_status_t conn_status = connection_get_status(); - - if (conn_status == JABBER_CONNECTED) { - // complete with: online, away etc. result = autocomplete_param_with_ac(input, "/status set", account_status_ac, TRUE, previous); if (result) { @@ -4086,7 +4115,7 @@ _status_autocomplete(ProfWin* window, const char* const input, gboolean previous } // roster completion } else { - result = autocomplete_param_with_func(unquoted, "/status get", roster_contact_autocomplete, previous, NULL); + result = _roster_jid_autocomplete(input, "/status get", previous); if (result) { free(unquoted); return result; @@ -4094,7 +4123,6 @@ _status_autocomplete(ProfWin* window, const char* const input, gboolean previous } free(unquoted); - } return NULL; } @@ -4223,10 +4251,7 @@ _software_autocomplete(ProfWin* window, const char* const input, gboolean previo } } } else { - result = autocomplete_param_with_func(input, "/software", roster_fulljid_autocomplete, previous, NULL); - if (result) { - return result; - } + result = _roster_jid_autocomplete(input, "/software", previous); } return result; @@ -4325,16 +4350,11 @@ _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean pr return result; } - jabber_conn_status_t conn_status = connection_get_status(); - - if (conn_status == JABBER_CONNECTED) { - result = autocomplete_param_with_func(input, "/lastactivity set", prefs_autocomplete_boolean_choice, previous, NULL); if (result) { return result; } - result = autocomplete_param_with_func(input, "/lastactivity get", roster_barejid_autocomplete, previous, NULL); - } + result = _roster_jid_autocomplete(input, "/lastactivity get", previous); return result; } @@ -4514,19 +4534,19 @@ _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous) } else { char* unquoted = strip_arg_quotes(input); - result = autocomplete_param_with_func(unquoted, "/vcard get", roster_contact_autocomplete, previous, NULL); + result = _roster_jid_autocomplete(input, "/vcard get", previous); if (result) { free(unquoted); return result; } - result = autocomplete_param_with_func(unquoted, "/vcard photo open", roster_contact_autocomplete, previous, NULL); + result = _roster_jid_autocomplete(input, "/vcard photo open", previous); if (result) { free(unquoted); return result; } - result = autocomplete_param_with_func(unquoted, "/vcard photo save", roster_contact_autocomplete, previous, NULL); + result = _roster_jid_autocomplete(input, "/vcard photo save", previous); if (result) { free(unquoted); return result; diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 77b6dd96..7f856ee9 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -110,6 +110,15 @@ static gboolean _cmd_execute_default(ProfWin* window, const char* inp); static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran); static gboolean _download_install_plugin(ProfWin* window, gchar* url, gchar* path); +static const gchar* +_resolve_contact_jid(const char* const name) +{ + char* barejid = roster_barejid_from_name(name); + if (barejid == NULL) { + barejid = (char*)name; + } + return barejid; +} static void _vcard_editor_finished_cb(gchar* message, void* user_data) @@ -3414,7 +3423,7 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args) case WIN_CONSOLE: case WIN_XML: if (args[0]) { - auto_jid Jid* jid = jid_create(args[0]); + auto_jid Jid* jid = jid_create(_resolve_contact_jid(args[0])); if (jid == NULL || jid->fulljid == NULL) { cons_show("You must provide a full jid to the /caps command."); @@ -3456,7 +3465,7 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args) } static void -_send_software_version_iq_to_fulljid(char* request) +_send_software_version_iq_to_fulljid(const gchar* const request) { auto_jid Jid* jid = jid_create(request); @@ -3497,7 +3506,7 @@ cmd_software(ProfWin* window, const char* const command, gchar** args) break; case WIN_CHAT: if (args[0]) { - _send_software_version_iq_to_fulljid(args[0]); + _send_software_version_iq_to_fulljid(_resolve_contact_jid(args[0])); break; } else { ProfChatWin* chatwin = (ProfChatWin*)window; @@ -3521,7 +3530,7 @@ cmd_software(ProfWin* window, const char* const command, gchar** args) } case WIN_CONSOLE: if (args[0]) { - _send_software_version_iq_to_fulljid(args[0]); + _send_software_version_iq_to_fulljid(_resolve_contact_jid(args[0])); } else { cons_show("You must provide a jid to the /software command."); } @@ -4831,7 +4840,7 @@ cmd_disco(ProfWin* window, const char* const command, gchar** args) return TRUE; } - auto_gchar gchar* jid = g_strdup_printf("%s", args[1] ?: connection_get_jid()->domainpart); + auto_gchar gchar* jid = g_strdup_printf("%s", args[1] ? _resolve_contact_jid(args[1]) : connection_get_jid()->domainpart); if (g_strcmp0(args[0], "info") == 0) { iq_disco_info_request(jid); @@ -5053,7 +5062,7 @@ cmd_lastactivity(ProfWin* window, const char* const command, gchar** args) if (args[1] == NULL) { iq_last_activity_request(connection_get_jid()->domainpart); } else { - iq_last_activity_request(args[1]); + iq_last_activity_request(_resolve_contact_jid(args[1])); } return TRUE; } @@ -6406,17 +6415,17 @@ cmd_ping(ProfWin* window, const char* const command, gchar** args) return TRUE; } - if (args[0] != NULL && caps_jid_has_feature(args[0], XMPP_FEATURE_PING) == FALSE) { - cons_show("%s does not support ping requests.", args[0]); - return TRUE; - } - - iq_send_ping(args[0]); - - if (args[0] == NULL) { - cons_show("Pinged server…"); + if (args[0] != NULL) { + const gchar* ping_target = _resolve_contact_jid(args[0]); + if (caps_jid_has_feature(ping_target, XMPP_FEATURE_PING) == FALSE) { + cons_show("%s does not support ping requests.", ping_target); + return TRUE; + } + iq_send_ping(ping_target); + cons_show("Pinged %s…", ping_target); } else { - cons_show("Pinged %s…", args[0]); + iq_send_ping(NULL); + cons_show("Pinged server…"); } return TRUE; } -- 2.49.1 From b7dec705c324939712c9e6a7054a7d5a094911fa Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Sat, 11 Jul 2026 16:09:36 +0000 Subject: [PATCH 2/2] fix(autocomplete): guard history completion safely Prevent autocomplete crashes on asset failures when executing history subcommands without an active connection by validating the session state beforehand. Standardize JID resolution across command handlers to ensure consistent contact lookup behavior. Use auto_gchar gchar instead of GString for performance and readability purposes. --- src/command/cmd_ac.c | 98 ++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index 6e91f057..abc5a295 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -1866,10 +1866,12 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ } gchar* history_jid_subcmds[] = { "/history verify", "/history export", "/history import" }; - for (size_t i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) { - result = autocomplete_param_with_func(input, history_jid_subcmds[i], roster_barejid_autocomplete, previous, NULL); - if (result) { - return result; + if (conn_status == JABBER_CONNECTED) { + for (size_t i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) { + result = autocomplete_param_with_func(input, history_jid_subcmds[i], roster_barejid_autocomplete, previous, NULL); + if (result) { + return result; + } } } @@ -2045,14 +2047,14 @@ _disco_autocomplete(ProfWin* window, const char* const input, gboolean previous) { char* result = NULL; result = autocomplete_param_with_ac(input, "/disco", disco_ac, TRUE, previous); - if (result) { - return result; - } + if (result) { + return result; + } result = _roster_jid_autocomplete(input, "/disco info", previous); - if (result) { - return result; - } + if (result) { + return result; + } result = _roster_jid_autocomplete(input, "/disco items", previous); return result; } @@ -3735,7 +3737,7 @@ _win_autocomplete(ProfWin* window, const char* const input, gboolean previous) } char* unquoted = strip_arg_quotes(input); - result = autocomplete_param_with_func(unquoted, "/win", roster_contact_autocomplete, previous, NULL); + result = _roster_jid_autocomplete(input, "/win", previous); free(unquoted); return result; } @@ -4070,13 +4072,13 @@ _invite_autocomplete(ProfWin* window, const char* const input, gboolean previous } result = _roster_jid_autocomplete(input, "/invite send", previous); - if (result) { - return result; - } + if (result) { + return result; + } - result = autocomplete_param_with_func(input, "/invite decline", muc_invites_find, previous, NULL); - if (result) { - return result; + result = autocomplete_param_with_func(input, "/invite decline", muc_invites_find, previous, NULL); + if (result) { + return result; } return NULL; @@ -4092,37 +4094,37 @@ _status_autocomplete(ProfWin* window, const char* const input, gboolean previous return result; } - // complete with: online, away etc. - result = autocomplete_param_with_ac(input, "/status set", account_status_ac, TRUE, previous); - if (result) { - return result; - } + // complete with: online, away etc. + result = autocomplete_param_with_ac(input, "/status set", account_status_ac, TRUE, previous); + if (result) { + return result; + } - // Remove quote character before and after names when doing autocomplete - char* unquoted = strip_arg_quotes(input); + // Remove quote character before and after names when doing autocomplete + char* unquoted = strip_arg_quotes(input); - // MUC completion with nicknames - if (window->type == WIN_MUC) { - ProfMucWin* mucwin = (ProfMucWin*)window; - assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); - Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid); - if (nick_ac) { - result = autocomplete_param_with_ac(unquoted, "/status get", nick_ac, TRUE, previous); - if (result) { - free(unquoted); - return result; - } - } - // roster completion - } else { - result = _roster_jid_autocomplete(input, "/status get", previous); + // MUC completion with nicknames + if (window->type == WIN_MUC) { + ProfMucWin* mucwin = (ProfMucWin*)window; + assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid); + if (nick_ac) { + result = autocomplete_param_with_ac(unquoted, "/status get", nick_ac, TRUE, previous); if (result) { free(unquoted); return result; } } + // roster completion + } else { + result = _roster_jid_autocomplete(input, "/status get", previous); + if (result) { + free(unquoted); + return result; + } + } - free(unquoted); + free(unquoted); return NULL; } @@ -4230,14 +4232,12 @@ _software_autocomplete(ProfWin* window, const char* const input, gboolean previo { char* result = NULL; - if (window->type == WIN_CHAT) { + if (window->type == WIN_CHAT && _is_connected()) { ProfChatWin* chatwin = (ProfChatWin*)window; assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); - GString* search_str = g_string_new("/software "); - g_string_append(search_str, chatwin->barejid); - result = autocomplete_param_with_func(search_str->str, "/software", roster_fulljid_autocomplete, previous, NULL); - g_string_free(search_str, TRUE); + auto_gchar gchar* search_str = g_strdup_printf("/software %s", chatwin->barejid); + result = autocomplete_param_with_func(search_str, "/software", roster_fulljid_autocomplete, previous, NULL); } else if (window->type == WIN_MUC) { ProfMucWin* mucwin = (ProfMucWin*)window; assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); @@ -4350,10 +4350,10 @@ _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean pr return result; } - result = autocomplete_param_with_func(input, "/lastactivity set", prefs_autocomplete_boolean_choice, previous, NULL); - if (result) { - return result; - } + result = autocomplete_param_with_func(input, "/lastactivity set", prefs_autocomplete_boolean_choice, previous, NULL); + if (result) { + return result; + } result = _roster_jid_autocomplete(input, "/lastactivity get", previous); return result; -- 2.49.1