From 7b81a44c87cf71dfb4f647ba107624e208ffbefe Mon Sep 17 00:00:00 2001 From: Ron Yorston <rmy@pobox.com> Date: Wed, 19 Apr 2023 12:51:19 +0100 Subject: win32: case-sensitivity in tab completion The tab-completion code treated all matches as case-insensitive because that's how Microsoft Windows handles filenames. This is now inadequate, as shell builtins, functions and aliases are case sensitive. Modify the treatment of case-sensitivity in tab completion: - Track whether each potential match is case-insensitive (filename) or case-sensitive (shell builtin, function or alias). - When comparing matches use a case-insensitive comparison if either value is a filename. Otherwise use a case-sensitive comparison. Adds 64 bytes. --- include/mingw.h | 1 - libbb/bb_qsort.c | 12 -------- libbb/lineedit.c | 93 +++++++++++++++++++++++++++++++++++++------------------- 3 files changed, 62 insertions(+), 44 deletions(-) diff --git a/include/mingw.h b/include/mingw.h index 8dd905c23..3cf0526f2 100644 --- a/include/mingw.h +++ b/include/mingw.h @@ -545,7 +545,6 @@ int kill_SIGTERM_by_handle(HANDLE process); char *is_prefixed_with_case(const char *string, const char *key) FAST_FUNC; char *is_suffixed_with_case(const char *string, const char *key) FAST_FUNC; -void qsort_string_vector_case(char **sv, unsigned count) FAST_FUNC; #define VT_OUTPUT 1 #define VT_INPUT 2 diff --git a/libbb/bb_qsort.c b/libbb/bb_qsort.c index 7afddf468..505045533 100644 --- a/libbb/bb_qsort.c +++ b/libbb/bb_qsort.c @@ -17,15 +17,3 @@ void FAST_FUNC qsort_string_vector(char **sv, unsigned count) { qsort(sv, count, sizeof(char*), bb_pstrcmp); } - -#if ENABLE_PLATFORM_MINGW32 -static int bb_pstrcasecmp(const void *a, const void *b) -{ - return strcasecmp(*(char**)a, *(char**)b); -} - -void FAST_FUNC qsort_string_vector_case(char **sv, unsigned count) -{ - qsort(sv, count, sizeof(char*), bb_pstrcasecmp); -} -#endif diff --git a/libbb/lineedit.c b/libbb/lineedit.c index 3abc97503..a6884c7e0 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -743,14 +743,6 @@ static void input_forward(void) //Also, perhaps "foo b<TAB> needs to complete to "foo bar" <cursor>, //not "foo bar <cursor>... -# if ENABLE_PLATFORM_MINGW32 -/* use case-insensitive comparisons for filenames */ -# define is_prefixed_with(s, k) is_prefixed_with_case(s, k) -# define qsort_string_vector(s, c) qsort_string_vector_case(s, c) -# define strcmp(s, t) strcasecmp(s, t) -# define strncmp(s, t, n) strncasecmp(s, t, n) -# endif - static void free_tab_completion_data(void) { if (matches) { @@ -761,8 +753,15 @@ static void free_tab_completion_data(void) } } -static void add_match(char *matched) +#if !ENABLE_PLATFORM_MINGW32 +# define add_match(m, s) add_match(m) +#endif + +static void add_match(char *matched, int sensitive) { +# if ENABLE_PLATFORM_MINGW32 + size_t len; +# endif unsigned char *p = (unsigned char*)matched; while (*p) { /* ESC attack fix: drop any string with control chars */ @@ -779,11 +778,26 @@ static void add_match(char *matched) } p++; } +# if ENABLE_PLATFORM_MINGW32 + /* The case-sensitivity flag is stored after NUL terminator */ + len = strlen(matched); + matched = xrealloc(matched, len + 2); + matched[len + 1] = sensitive; +# endif matches = xrealloc_vector(matches, 4, num_matches); matches[num_matches] = matched; num_matches++; } +# if ENABLE_PLATFORM_MINGW32 +static int is_case_sensitive(const char *p) +{ + while (*p++) + ; + return *p; +} +# endif + # if ENABLE_FEATURE_USERNAME_COMPLETION /* Replace "~user/..." with "/homedir/...". * The parameter is malloced, free it or return it @@ -835,7 +849,7 @@ static NOINLINE unsigned complete_username(const char *ud) while ((pw = getpwent()) != NULL) { /* Null usernames should result in all users as possible completions. */ if (/* !ud[0] || */ is_prefixed_with(pw->pw_name, ud)) { - add_match(xasprintf("~%s/", pw->pw_name)); + add_match(xasprintf("~%s/", pw->pw_name), TRUE); } } endpwent(); /* don't keep password file open */ @@ -944,7 +958,7 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) const char *p = applet_names; while (*p) { if (strncmp(basecmd, p, baselen) == 0 && is_applet_preferred(p)) - add_match(xstrdup(p)); + add_match(xstrdup(p), TRUE); while (*p++ != '\0') continue; } @@ -957,7 +971,7 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) if (!b) break; if (strncmp(basecmd, b, baselen) == 0) - add_match(xstrdup(b)); + add_match(xstrdup(b), TRUE); } } # endif @@ -991,7 +1005,11 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) if (!basecmd[0] && DOT_OR_DOTDOT(name_found)) continue; /* match? */ +# if ENABLE_PLATFORM_MINGW32 + if (strncasecmp(basecmd, name_found, baselen) != 0) +# else if (strncmp(basecmd, name_found, baselen) != 0) +# endif continue; /* no */ found = concat_path_file(lpath, name_found); @@ -1025,7 +1043,7 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) goto cont; } /* add it to the list */ - add_match(found); + add_match(found, FALSE); continue; cont: free(found); @@ -1297,11 +1315,17 @@ static NOINLINE void input_tab(smallint *lastWasTab) size_t len_found; /* Length of string used for matching */ unsigned match_pfx_len = match_pfx_len; -#if ENABLE_PLATFORM_MINGW32 +# if ENABLE_PLATFORM_MINGW32 + int chosen_index = 0; + int chosen_sens = FALSE; unsigned orig_pfx_len; char *target; const char *source; -#endif +# define first_match 0 +# else +# define chosen_index 0 +# define first_match 1 +# endif int find_type; # if ENABLE_UNICODE_SUPPORT /* cursor pos in command converted to multibyte form */ @@ -1366,9 +1390,9 @@ static NOINLINE void input_tab(smallint *lastWasTab) { const char *e = match_buf + strlen(match_buf); const char *s = e - match_pfx_len; -#if ENABLE_PLATFORM_MINGW32 +# if ENABLE_PLATFORM_MINGW32 orig_pfx_len = match_pfx_len; -#endif +# endif while (s < e) if (is_special_char(*s++)) match_pfx_len++; @@ -1399,15 +1423,30 @@ static NOINLINE void input_tab(smallint *lastWasTab) if (!matches) goto ret; /* no matches at all */ /* Find common prefix */ - chosen_match = xstrdup(matches[0]); +# if ENABLE_PLATFORM_MINGW32 + /* Any comparison involving a filename must be case-insensitive. + * The chosen match should be case-sensitive, if possible */ + for (unsigned i = 0; i < num_matches; ++i) { + if (is_case_sensitive(matches[i])) { + chosen_index = i; + chosen_sens = TRUE; + break; + } + } +# endif + chosen_match = xstrdup(matches[chosen_index]); for (cp = chosen_match; *cp; cp++) { unsigned n; - for (n = 1; n < num_matches; n++) { -# if !ENABLE_PLATFORM_MINGW32 - if (matches[n][cp - chosen_match] != *cp) { -# else - if (tolower(matches[n][cp - chosen_match]) != tolower(*cp)) { + for (n = first_match; n < num_matches; n++) { +# if ENABLE_PLATFORM_MINGW32 + if (!is_case_sensitive(matches[n]) || !chosen_sens) { + if (tolower(matches[n][cp - chosen_match]) != + tolower(*cp)) { + goto stop; + } + } else # endif + if (matches[n][cp - chosen_match] != *cp) { goto stop; } } @@ -1493,14 +1532,6 @@ static NOINLINE void input_tab(smallint *lastWasTab) free(chosen_match); free(match_buf); } - -# if ENABLE_PLATFORM_MINGW32 -# undef is_prefixed_with -# undef qsort_string_vector -# undef strcmp -# undef strncmp -# endif - #endif /* FEATURE_TAB_COMPLETION */ -- cgit v1.2.3-55-g6feb