From: glepnir Date: Wed, 29 Apr 2026 19:10:43 +0000 (+0000) Subject: patch 9.2.0418: wildcards in expanded env vars reinterpreted by glob X-Git-Tag: v9.2.0418^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=20e98ff1cc3ffc13d42579d2bf9b9567f98595a2;p=thirdparty%2Fvim.git patch 9.2.0418: wildcards in expanded env vars reinterpreted by glob Problem: With $d='[dir]', `:e $d/file.txt` opens the wrong file, `:e $d/` fails to complete, and `glob('$d/*')` returns nothing. Wildcard characters inside expanded environment variables get picked up by globbing again. Solution: Turn the 4th parameter of expand_env_esc() from a bool into a string of characters to escape in each expanded value. Callers that pass the result to wildcard expansion should include PATH_ESC_WILDCARDS in addition to " \t" (glepnir). closes: #20053 Signed-off-by: glepnir Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt index 34dacefd6e..40a961f328 100644 --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -2113,7 +2113,6 @@ es_ES.utf-8" gives an error and doesn't switch messages. (Dominique Pelle, When $HOME contains special characters, such as a comma, escape them when used in an option. (Michael Hordijk, 2009 May 5) -Turn "esc" argument of expand_env_esc() into string of chars to be escaped. Should make 'ignorecase' global-local, so that it makes sense setting it from a modeline. diff --git a/src/cmdexpand.c b/src/cmdexpand.c index 58a59bef67..b7a17bbdda 100644 --- a/src/cmdexpand.c +++ b/src/cmdexpand.c @@ -1254,7 +1254,7 @@ showmatches_oneline( // Expansion was done before and special characters // were escaped, need to halve backslashes. Also // $HOME has been replaced with ~/. - exp_path = expand_env_save_opt(matches[j], TRUE); + exp_path = expand_env_save_opt(matches[j], TRUE, NULL); path = exp_path != NULL ? exp_path : matches[j]; halved_slash = backslash_halve_save(path); isdir = mch_isdir(halved_slash != NULL ? halved_slash diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 74e32a74ed..7ea20b2628 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -5246,7 +5246,8 @@ expand_filename( || vim_strchr(eap->arg, '~') != NULL) { expand_env_esc(eap->arg, NameBuff, MAXPATHL, - TRUE, TRUE, NULL); + (char_u *)(" \t" PATH_ESC_WILDCARDS), + TRUE, NULL); has_wildcards = mch_has_wildcard(NameBuff); p = NameBuff; } diff --git a/src/filepath.c b/src/filepath.c index 492e0ad3ad..a41f8c27fc 100644 --- a/src/filepath.c +++ b/src/filepath.c @@ -4158,7 +4158,7 @@ gen_expand_wildcards( */ if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') { - p = expand_env_save_opt(p, TRUE); + p = expand_env_save_opt(p, TRUE, (char_u *)PATH_ESC_WILDCARDS); if (p == NULL) p = pat[i]; #ifdef UNIX diff --git a/src/findfile.c b/src/findfile.c index 0c5d1cf252..ed0ee76c49 100644 --- a/src/findfile.c +++ b/src/findfile.c @@ -1808,7 +1808,7 @@ find_file_in_path_option( // copy file name into NameBuff, expanding environment variables save_char = ptr[len]; ptr[len] = NUL; - file_to_findlen = expand_env_esc(ptr, NameBuff, MAXPATHL, FALSE, TRUE, NULL); + file_to_findlen = expand_env_esc(ptr, NameBuff, MAXPATHL, NULL, TRUE, NULL); ptr[len] = save_char; vim_free(*file_to_find); diff --git a/src/misc1.c b/src/misc1.c index a0f9f799e2..88e872d766 100644 --- a/src/misc1.c +++ b/src/misc1.c @@ -1398,7 +1398,7 @@ init_vimdir(void) char_u * expand_env_save(char_u *src) { - return expand_env_save_opt(src, FALSE); + return expand_env_save_opt(src, FALSE, NULL); } /* @@ -1406,13 +1406,13 @@ expand_env_save(char_u *src) * expand "~" at the start. */ char_u * -expand_env_save_opt(char_u *src, int one) +expand_env_save_opt(char_u *src, int one, char_u *esc_chars) { char_u *p; p = alloc(MAXPATHL); if (p != NULL) - expand_env_esc(src, p, MAXPATHL, FALSE, one, NULL); + expand_env_esc(src, p, MAXPATHL, esc_chars, one, NULL); return p; } @@ -1428,7 +1428,7 @@ expand_env( char_u *dst, // where to put the result int dstlen) // maximum length of the result { - return expand_env_esc(src, dst, dstlen, FALSE, FALSE, NULL); + return expand_env_esc(src, dst, dstlen, NULL, FALSE, NULL); } size_t @@ -1436,7 +1436,7 @@ expand_env_esc( char_u *srcp, // input string e.g. "$HOME/vim.hlp" char_u *dst, // where to put the result int dstlen, // maximum length of the result - int esc, // escape spaces in expanded variables + char_u *esc_chars, // chars to escape in expanded vars int one, // "srcp" is one file name char_u *startstr) // start again after this (can be NULL) { @@ -1655,11 +1655,13 @@ expand_env_esc( } #endif - // If "var" contains white space, escape it with a backslash. - // Required for ":e ~/tt" when $HOME includes a space. - if (esc && var != NULL && vim_strpbrk(var, (char_u *)" \t") != NULL) + // If "var" contains any character from "esc_chars", escape it + // with a backslash. The historical use is escaping spaces so + // that ":e ~/tt" works when $HOME contains a space. + if (esc_chars != NULL && var != NULL + && vim_strpbrk(var, esc_chars) != NULL) { - char_u *p = vim_strsave_escaped(var, (char_u *)" \t"); + char_u *p = vim_strsave_escaped(var, esc_chars); if (p != NULL) { diff --git a/src/option.c b/src/option.c index a1138a0e17..b44a3990ad 100644 --- a/src/option.c +++ b/src/option.c @@ -3264,7 +3264,8 @@ option_expand(int opt_idx, char_u *val) char_u ** var = (char_u **)options[opt_idx].var; int esc = var == &p_tags || var == &p_path; - expand_env_esc(val, NameBuff, MAXPATHL, esc, FALSE, + expand_env_esc(val, NameBuff, MAXPATHL, + esc ? (char_u *)" \t" : NULL, FALSE, #ifdef FEAT_SPELL var == &p_sps ? (char_u *)"file:" : #endif diff --git a/src/profiler.c b/src/profiler.c index aa127a419f..2aa5aa50ac 100644 --- a/src/profiler.c +++ b/src/profiler.c @@ -392,7 +392,7 @@ ex_profile(exarg_T *eap) if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) { VIM_CLEAR(profile_fname); - profile_fname = expand_env_save_opt(e, TRUE); + profile_fname = expand_env_save_opt(e, TRUE, NULL); do_profiling = PROF_YES; profile_zero(&prof_wait_time); set_vim_var_nr(VV_PROFILING, 1L); diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro index d24a61d151..706629490d 100644 --- a/src/proto/misc1.pro +++ b/src/proto/misc1.pro @@ -29,9 +29,9 @@ void free_homedir(void); void free_users(void); void init_vimdir(void); char_u *expand_env_save(char_u *src); -char_u *expand_env_save_opt(char_u *src, int one); +char_u *expand_env_save_opt(char_u *src, int one, char_u *esc_chars); size_t expand_env(char_u *src, char_u *dst, int dstlen); -size_t expand_env_esc(char_u *srcp, char_u *dst, int dstlen, int esc, int one, char_u *startstr); +size_t expand_env_esc(char_u *srcp, char_u *dst, int dstlen, char_u *esc_chars, int one, char_u *startstr); char_u *vim_getenv(char_u *name, int *mustfree); void vim_unsetenv(char_u *var); void vim_unsetenv_ext(char_u *var); diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim index 80a023d2c1..ee5f668bb2 100644 --- a/src/testdir/test_cmdline.vim +++ b/src/testdir/test_cmdline.vim @@ -5506,4 +5506,31 @@ func Test_wildmode_noinsert() delfunc T endfunc +func Test_cmdline_compl_env_var_wildcard() + CheckUnix + + let d = tempname() + call mkdir(d .. '/[x]', 'pR') + call writefile(['hello'], d .. '/[x]/file.txt') + let $XWILD = d .. '/[x]' + + call feedkeys(":e $XWILD/fi\\\"\", 'xt') + call assert_match('\[x\]/file\.txt$', @:) + call assert_equal([d .. '/[x]/file.txt'], glob('$XWILD/*', 0, 1)) + + edit $XWILD/file.txt + call assert_equal('hello', getline(1)) + bwipe! + + if has('profile') + let prof = d .. '/[x]/prof.out' + profile start $XWILD/prof.out + profile stop + call assert_true(filereadable(prof)) + call delete(prof) + endif + + unlet $XWILD +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 86f3c4ffd1..23de968780 100644 --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 418, /**/ 417, /**/ diff --git a/src/vim.h b/src/vim.h index c0c811d1d4..37183f130a 100644 --- a/src/vim.h +++ b/src/vim.h @@ -3098,4 +3098,17 @@ long elapsed(DWORD start_tick); // Flags used by getvcol() #define GETVCOL_END_EXCL_LBR 1 +// Used by expand_env_esc() callers that feed the result to +// wildcard expansion, so that such characters embedded in +// environment variable values are treated as literal. +#ifdef VMS +# define PATH_ESC_WILDCARDS "*?%" +#else +# ifdef MSWIN +# define PATH_ESC_WILDCARDS "*?[" +# else +# define PATH_ESC_WILDCARDS "*?[{" +# endif +#endif + #endif // VIM__H