Compare commits

...

2 Commits

Author SHA1 Message Date
b7dec705c3 fix(autocomplete): guard history completion safely
All checks were successful
CI Code / Check spelling (pull_request) Successful in 12s
CI Code / Check coding style (pull_request) Successful in 23s
CI Code / Code Coverage (pull_request) Successful in 3m40s
CI Code / Linux (debian) (pull_request) Successful in 5m14s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m20s
CI Code / Linux (arch) (pull_request) Successful in 7m23s
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.
2026-07-11 16:09:36 +00:00
b208e48ee1 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.
2026-07-11 15:54:32 +00:00
2 changed files with 121 additions and 92 deletions

View File

@@ -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* _url_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _executable_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* _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* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _mood_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); 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* _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 char* _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous);
static gboolean _is_connected(void);
static Autocomplete commands_ac; static Autocomplete commands_ac;
static Autocomplete who_room_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, "/color", _color_autocomplete);
g_hash_table_insert(ac_funcs, "/connect", _connect_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, "/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, "/correct", _correct_autocomplete);
g_hash_table_insert(ac_funcs, "/correction", _correction_autocomplete); g_hash_table_insert(ac_funcs, "/correction", _correction_autocomplete);
g_hash_table_insert(ac_funcs, "/executable", _executable_autocomplete); g_hash_table_insert(ac_funcs, "/executable", _executable_autocomplete);
@@ -1862,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" }; gchar* history_jid_subcmds[] = { "/history verify", "/history export", "/history import" };
for (size_t i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) { if (conn_status == JABBER_CONNECTED) {
result = autocomplete_param_with_func(input, history_jid_subcmds[i], roster_barejid_autocomplete, previous, NULL); for (size_t i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) {
if (result) { result = autocomplete_param_with_func(input, history_jid_subcmds[i], roster_barejid_autocomplete, previous, NULL);
return result; if (result) {
return result;
}
} }
} }
@@ -1941,7 +1947,6 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
Autocomplete completer; Autocomplete completer;
} ac_cmds[] = { } ac_cmds[] = {
{ "/prefs", prefs_ac }, { "/prefs", prefs_ac },
{ "/disco", disco_ac },
{ "/room", room_ac }, { "/room", room_ac },
{ "/mainwin", winpos_ac }, { "/mainwin", winpos_ac },
{ "/inputwin", winpos_ac }, { "/inputwin", winpos_ac },
@@ -1991,6 +1996,31 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
return NULL; 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* static char*
_caps_autocomplete(ProfWin* window, const char* const input, gboolean previous) _caps_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{ {
@@ -2006,20 +2036,29 @@ _caps_autocomplete(ProfWin* window, const char* const input, gboolean previous)
if (nick_ac) { if (nick_ac) {
result = autocomplete_param_with_ac(input, "/caps", nick_ac, TRUE, previous); result = autocomplete_param_with_ac(input, "/caps", nick_ac, TRUE, previous);
} }
} else if (connection_get_status() == JABBER_CONNECTED) { } else {
result = autocomplete_param_with_func(input, "/caps", roster_contact_autocomplete, previous, NULL); result = _roster_jid_autocomplete(input, "/caps", previous);
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);
} }
return result; 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* static char*
_sub_autocomplete(ProfWin* window, const char* const input, gboolean previous) _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{ {
@@ -3698,7 +3737,7 @@ _win_autocomplete(ProfWin* window, const char* const input, gboolean previous)
} }
char* unquoted = strip_arg_quotes(input); 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); free(unquoted);
return result; return result;
} }
@@ -4032,18 +4071,14 @@ _invite_autocomplete(ProfWin* window, const char* const input, gboolean previous
return result; return result;
} }
jabber_conn_status_t conn_status = connection_get_status(); result = _roster_jid_autocomplete(input, "/invite send", previous);
if (result) {
return result;
}
if (conn_status == JABBER_CONNECTED) { result = autocomplete_param_with_func(input, "/invite decline", muc_invites_find, previous, NULL);
result = autocomplete_param_with_func(input, "/invite send", roster_contact_autocomplete, previous, NULL); if (result) {
if (result) { return result;
return result;
}
result = autocomplete_param_with_func(input, "/invite decline", muc_invites_find, previous, NULL);
if (result) {
return result;
}
} }
return NULL; return NULL;
@@ -4059,43 +4094,38 @@ _status_autocomplete(ProfWin* window, const char* const input, gboolean previous
return result; return result;
} }
jabber_conn_status_t conn_status = connection_get_status(); // complete with: online, away etc.
result = autocomplete_param_with_ac(input, "/status set", account_status_ac, TRUE, previous);
if (result) {
return result;
}
if (conn_status == JABBER_CONNECTED) { // Remove quote character before and after names when doing autocomplete
char* unquoted = strip_arg_quotes(input);
// complete with: online, away etc. // MUC completion with nicknames
result = autocomplete_param_with_ac(input, "/status set", account_status_ac, TRUE, previous); if (window->type == WIN_MUC) {
if (result) { ProfMucWin* mucwin = (ProfMucWin*)window;
return result; assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
} Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
if (nick_ac) {
// Remove quote character before and after names when doing autocomplete result = autocomplete_param_with_ac(unquoted, "/status get", nick_ac, TRUE, previous);
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 = autocomplete_param_with_func(unquoted, "/status get", roster_contact_autocomplete, previous, NULL);
if (result) { if (result) {
free(unquoted); free(unquoted);
return result; return result;
} }
} }
// roster completion
free(unquoted); } else {
result = _roster_jid_autocomplete(input, "/status get", previous);
if (result) {
free(unquoted);
return result;
}
} }
free(unquoted);
return NULL; return NULL;
} }
@@ -4202,14 +4232,12 @@ _software_autocomplete(ProfWin* window, const char* const input, gboolean previo
{ {
char* result = NULL; char* result = NULL;
if (window->type == WIN_CHAT) { if (window->type == WIN_CHAT && _is_connected()) {
ProfChatWin* chatwin = (ProfChatWin*)window; ProfChatWin* chatwin = (ProfChatWin*)window;
assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
GString* search_str = g_string_new("/software "); auto_gchar gchar* search_str = g_strdup_printf("/software %s", chatwin->barejid);
g_string_append(search_str, chatwin->barejid); result = autocomplete_param_with_func(search_str, "/software", roster_fulljid_autocomplete, previous, NULL);
result = autocomplete_param_with_func(search_str->str, "/software", roster_fulljid_autocomplete, previous, NULL);
g_string_free(search_str, TRUE);
} else if (window->type == WIN_MUC) { } else if (window->type == WIN_MUC) {
ProfMucWin* mucwin = (ProfMucWin*)window; ProfMucWin* mucwin = (ProfMucWin*)window;
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
@@ -4223,10 +4251,7 @@ _software_autocomplete(ProfWin* window, const char* const input, gboolean previo
} }
} }
} else { } else {
result = autocomplete_param_with_func(input, "/software", roster_fulljid_autocomplete, previous, NULL); result = _roster_jid_autocomplete(input, "/software", previous);
if (result) {
return result;
}
} }
return result; return result;
@@ -4325,16 +4350,11 @@ _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean pr
return result; return result;
} }
jabber_conn_status_t conn_status = connection_get_status(); result = autocomplete_param_with_func(input, "/lastactivity set", prefs_autocomplete_boolean_choice, previous, NULL);
if (result) {
if (conn_status == JABBER_CONNECTED) { 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 get", roster_barejid_autocomplete, previous, NULL);
} }
result = _roster_jid_autocomplete(input, "/lastactivity get", previous);
return result; return result;
} }
@@ -4514,19 +4534,19 @@ _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous)
} else { } else {
char* unquoted = strip_arg_quotes(input); 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) { if (result) {
free(unquoted); free(unquoted);
return result; 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) { if (result) {
free(unquoted); free(unquoted);
return result; 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) { if (result) {
free(unquoted); free(unquoted);
return result; return result;

View File

@@ -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 _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran);
static gboolean static gboolean
_download_install_plugin(ProfWin* window, gchar* url, gchar* path); _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 static void
_vcard_editor_finished_cb(gchar* message, void* user_data) _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_CONSOLE:
case WIN_XML: case WIN_XML:
if (args[0]) { 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) { if (jid == NULL || jid->fulljid == NULL) {
cons_show("You must provide a full jid to the /caps command."); 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 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); auto_jid Jid* jid = jid_create(request);
@@ -3497,7 +3506,7 @@ cmd_software(ProfWin* window, const char* const command, gchar** args)
break; break;
case WIN_CHAT: case WIN_CHAT:
if (args[0]) { if (args[0]) {
_send_software_version_iq_to_fulljid(args[0]); _send_software_version_iq_to_fulljid(_resolve_contact_jid(args[0]));
break; break;
} else { } else {
ProfChatWin* chatwin = (ProfChatWin*)window; ProfChatWin* chatwin = (ProfChatWin*)window;
@@ -3521,7 +3530,7 @@ cmd_software(ProfWin* window, const char* const command, gchar** args)
} }
case WIN_CONSOLE: case WIN_CONSOLE:
if (args[0]) { if (args[0]) {
_send_software_version_iq_to_fulljid(args[0]); _send_software_version_iq_to_fulljid(_resolve_contact_jid(args[0]));
} else { } else {
cons_show("You must provide a jid to the /software command."); 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; 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) { if (g_strcmp0(args[0], "info") == 0) {
iq_disco_info_request(jid); iq_disco_info_request(jid);
@@ -5053,7 +5062,7 @@ cmd_lastactivity(ProfWin* window, const char* const command, gchar** args)
if (args[1] == NULL) { if (args[1] == NULL) {
iq_last_activity_request(connection_get_jid()->domainpart); iq_last_activity_request(connection_get_jid()->domainpart);
} else { } else {
iq_last_activity_request(args[1]); iq_last_activity_request(_resolve_contact_jid(args[1]));
} }
return TRUE; return TRUE;
} }
@@ -6406,17 +6415,17 @@ cmd_ping(ProfWin* window, const char* const command, gchar** args)
return TRUE; return TRUE;
} }
if (args[0] != NULL && caps_jid_has_feature(args[0], XMPP_FEATURE_PING) == FALSE) { if (args[0] != NULL) {
cons_show("%s does not support ping requests.", args[0]); const gchar* ping_target = _resolve_contact_jid(args[0]);
return TRUE; 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(args[0]); }
iq_send_ping(ping_target);
if (args[0] == NULL) { cons_show("Pinged %s…", ping_target);
cons_show("Pinged server…");
} else { } else {
cons_show("Pinged %s…", args[0]); iq_send_ping(NULL);
cons_show("Pinged server…");
} }
return TRUE; return TRUE;
} }