fix(tools): implement backslash escaping for contact names

Implement backslash based escaping in the command parser and autocompletion
logic to handle contact names containing double quotes.
This stops the parser from splitting nicknames into multiple tokens, which
previously caused "Invalid usage" errors.

The parser now sees \ as an escape character for quotes and spaces in
_parse_args_helper, count_tokens and get_start. Autocomplete results are
escaped when they contain spaces, and search prefixes are unescaped before
matching. strip_arg_quotes() has also been updated to handle unescaping.

Fixes: https://github.com/profanity-im/profanity/issues/1844
Signed-off-by: Michael Vetter <jubalh@iodoru.org>
This commit is contained in:
Michael Vetter
2026-03-29 00:15:23 +01:00
parent 9b2f96fb45
commit bda5b0f844
8 changed files with 191 additions and 49 deletions

View File

@@ -390,6 +390,48 @@ parse_args_with_freetext__returns__second_and_third_arg_quoted(void** state)
g_strfreev(args);
}
void
parse_args__returns__escaped_quotes(void** state)
{
char* inp = "/cmd \"Thor \\\"The Thunderer\\\" Odinson\" arg2";
gboolean result = FALSE;
gchar** args = parse_args(inp, 2, 2, &result);
assert_true(result);
assert_int_equal(2, g_strv_length(args));
assert_string_equal("Thor \"The Thunderer\" Odinson", args[0]);
assert_string_equal("arg2", args[1]);
g_strfreev(args);
}
void
parse_args__returns__escaped_spaces(void** state)
{
char* inp = "/cmd Thor\\ The\\ Thunderer\\ Odinson arg2";
gboolean result = FALSE;
gchar** args = parse_args(inp, 2, 2, &result);
assert_true(result);
assert_int_equal(2, g_strv_length(args));
assert_string_equal("Thor The Thunderer Odinson", args[0]);
assert_string_equal("arg2", args[1]);
g_strfreev(args);
}
void
parse_args__returns__escaped_backslash(void** state)
{
char* inp = "/cmd \"Thor \\\\ Odinson\" arg2";
gboolean result = FALSE;
gchar** args = parse_args(inp, 2, 2, &result);
assert_true(result);
assert_int_equal(2, g_strv_length(args));
assert_string_equal("Thor \\ Odinson", args[0]);
assert_string_equal("arg2", args[1]);
g_strfreev(args);
}
void
count_tokens__returns__one_token(void** state)
{
@@ -453,6 +495,15 @@ count_tokens__returns__two_tokens_both_quoted(void** state)
assert_int_equal(2, result);
}
void
count_tokens__handles__escapes(void** state)
{
char* inp = "one\\ two \"three \\\" four\"";
int result = count_tokens(inp);
assert_int_equal(2, result);
}
void
get_start__returns__first_of_one(void** state)
{
@@ -513,6 +564,16 @@ get_start__returns__first_two_of_three_first_and_second_quoted(void** state)
g_free(result);
}
void
get_start__handles__escapes(void** state)
{
char* inp = "one\\ two three";
char* result = get_start(inp, 2);
assert_string_equal("one\\ two ", result);
g_free(result);
}
void
parse_options__returns__empty_hashmap_when_none(void** state)
{