From e6c99c6d6ce30cbf704eeeb8e5e24a49b54fe05a Mon Sep 17 00:00:00 2001 From: Rory Date: Mon, 18 Sep 2023 17:58:38 +0100 Subject: [PATCH] feat: relative path in NVIM_APPNAME Problem: NVIM_APPNAME does not allow path separators in the name, which means relative paths can't be used: NVIM_APPNAME="neovim-configs/first-config" nvim NVIM_APPNAME="neovim-configs/second-config" nvim Solution: - Let NVIM_APPNAME be a relative path. Absolute paths are not supported. - Also change tempname() to replace "/" with "%" instead of "_", for consistency. fix #23056 fix #24966 --- runtime/doc/news.txt | 2 ++ runtime/doc/starting.txt | 15 +++++++------- src/nvim/fileio.c | 7 ++++++- src/nvim/os/stdpaths.c | 17 +++++++++------ test/functional/core/fileio_spec.lua | 10 +++++++++ test/functional/options/defaults_spec.lua | 25 +++++++++++++++++------ 6 files changed, 56 insertions(+), 20 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 05a2d35f9aaaf4..ff0ccfb08faad9 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -169,6 +169,8 @@ The following new APIs and features were added. • |vim.lsp.util.locations_to_items()| sets the `user_data` of each item to the original LSP `Location` or `LocationLink`. +• |$NVIM_APPNAME| can be set to a relative path instead of only a name. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 17d5b4668d10e3..d38712b067a256 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1366,6 +1366,9 @@ The $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, $XDG_STATE_HOME, $XDG_CACHE_HOME, $XDG_CONFIG_DIRS and $XDG_DATA_DIRS environment variables are used if defined, else default values (listed below) are used. +Throughout the help pages these defaults are used as placeholders, e.g. +"~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config". + CONFIG DIRECTORY (DEFAULT) ~ *$XDG_CONFIG_HOME* Nvim: stdpath("config") Unix: ~/.config ~/.config/nvim @@ -1392,10 +1395,12 @@ CACHE DIRECTORY (DEFAULT) ~ Windows: ~/AppData/Local/Temp ~/AppData/Local/Temp/nvim-data LOG FILE (DEFAULT) ~ - `$NVIM_LOG_FILE` Nvim: stdpath("log") + `$NVIM_LOG_FILE` Nvim: stdpath("log")/log Unix: ~/.local/state/nvim ~/.local/state/nvim/log Windows: ~/AppData/Local/nvim-data ~/AppData/Local/nvim-data/log +Note that stdpath("log") is currently an alias for stdpath("state"). + ADDITIONAL CONFIGS DIRECTORY (DEFAULT) ~ *$XDG_CONFIG_DIRS* Nvim: stdpath("config_dirs") Unix: /etc/xdg/ /etc/xdg/nvim @@ -1407,18 +1412,14 @@ ADDITIONAL DATA DIRECTORY (DEFAULT) ~ /usr/share /usr/share/nvim Windows: Not applicable Not applicable -Note: Throughout the help pages these defaults are used as placeholders, e.g. -"~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config". - -Note: The log file directory is controlled by `$XDG_STATE_HOME`. - NVIM_APPNAME *$NVIM_APPNAME* The standard directories can be further configured by the `$NVIM_APPNAME` environment variable. This variable controls the sub-directory that Nvim will read from (and auto-create) in each of the base directories. For example, setting `$NVIM_APPNAME` to "foo" before starting will cause Nvim to look for configuration files in `$XDG_CONFIG_HOME/foo` instead of -`$XDG_CONFIG_HOME/nvim`. +`$XDG_CONFIG_HOME/nvim`. `$NVIM_APPNAME` must be a name, such as "foo", or a +relative path, such as "foo/bar". One use-case for $NVIM_APPNAME is to "isolate" Nvim applications. Alternatively, for true isolation, on Linux you can use cgroups namespaces: > diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7ff3e0ec6e92a8..7609eb12b4e232 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3279,12 +3279,18 @@ static void vim_mktempdir(void) char tmp[TEMP_FILE_PATH_MAXLEN]; char path[TEMP_FILE_PATH_MAXLEN]; char user[40] = { 0 }; + char appname[40] = { 0 }; (void)os_get_username(user, sizeof(user)); // Usernames may contain slashes! #19240 memchrsub(user, '/', '_', sizeof(user)); memchrsub(user, '\\', '_', sizeof(user)); + // Appname may be a relative path, replace slashes to make it name-like. + xstrlcpy(appname, get_appname(), sizeof(appname)); + memchrsub(appname, '/', '%', sizeof(appname)); + memchrsub(appname, '\\', '%', sizeof(appname)); + // Make sure the umask doesn't remove the executable bit. // "repl" has been reported to use "0177". mode_t umask_save = umask(0077); @@ -3298,7 +3304,6 @@ static void vim_mktempdir(void) // "/tmp/" exists, now try to create "/tmp/nvim./". add_pathsep(tmp); - const char *appname = get_appname(); xstrlcat(tmp, appname, sizeof(tmp)); xstrlcat(tmp, ".", sizeof(tmp)); xstrlcat(tmp, user, sizeof(tmp)); diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index 53ddda22fa3220..ed6261d74918cf 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -69,15 +69,20 @@ const char *get_appname(void) return env_val; } -/// Ensure that APPNAME is valid. In particular, it cannot contain directory separators. +/// Ensure that APPNAME is valid. Must be a name or relative path. bool appname_is_valid(void) { const char *appname = get_appname(); - const size_t appname_len = strlen(appname); - for (size_t i = 0; i < appname_len; i++) { - if (appname[i] == PATHSEP) { - return false; - } + if (path_is_absolute(appname) + || strequal(appname, ".") + || strequal(appname, "..") +#ifdef BACKSLASH_IN_FILENAME + || strstr(appname, "\\..") != NULL + || strstr(appname, "..\\") != NULL +#endif + || strstr(appname, "/..") != NULL + || strstr(appname, "../") != NULL) { + return false; } return true; } diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index 8de78234ce6995..6967a27a6d3b1f 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -377,4 +377,14 @@ describe('tmpdir', function() rm_tmpdir() eq('E5431: tempdir disappeared (3 times)', meths.get_vvar('errmsg')) end) + + it('$NVIM_APPNAME relative path', function() + clear({ env={ + NVIM_APPNAME='a/b', + NVIM_LOG_FILE=testlog, + TMPDIR=os_tmpdir, + } }) + matches([=[.*[/\\]a%b%.[^/\\]+]=], funcs.tempname()) + end) + end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 9b531bcafc2813..7858b626de2869 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -598,8 +598,7 @@ describe('stdpath()', function() end) it('reacts to $NVIM_APPNAME', function() - local appname = "NVIM_APPNAME_TEST____________________________________" .. - "______________________________________________________________________" + local appname = 'NVIM_APPNAME_TEST' .. ('_'):rep(106) clear({env={ NVIM_APPNAME=appname }}) eq(appname, funcs.fnamemodify(funcs.stdpath('config'), ':t')) eq(appname, funcs.fnamemodify(funcs.stdpath('cache'), ':t')) @@ -616,10 +615,24 @@ describe('stdpath()', function() -- Check that Nvim rejects invalid APPNAMEs -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. - eq(1, exec_lua([[ - local child = vim.fn.jobstart({ vim.v.progpath }, { env = { NVIM_APPNAME = 'a/b\\c' } }) - return vim.fn.jobwait({ child }, 3000)[1] - ]])) + local function test_appname(testAppname, expected_exitcode) + local lua_code = string.format([[ + local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '+qall!' }, { env = { NVIM_APPNAME = %q } }) + return vim.fn.jobwait({ child }, %d)[1] + ]], alter_slashes(testAppname), 3000) + eq(expected_exitcode, exec_lua(lua_code)) + end + -- Invalid appnames: + test_appname('a/../b', 1) + test_appname('../a', 1) + test_appname('a/..', 1) + test_appname('..', 1) + test_appname('.', 1) + test_appname('/', 1) + test_appname(is_os('win') and 'C:/a/b' or '/a/b', 1) + -- Valid appnames: + test_appname('a/b', 0) + test_appname('a/b\\c', 0) end) describe('returns a String', function()