diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/doc/lua.txt | 16 | ||||
-rw-r--r-- | runtime/doc/news.txt | 3 | ||||
-rw-r--r-- | runtime/lua/vim/_editor.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/secure.lua | 106 |
4 files changed, 126 insertions, 0 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index cab2f49d94..c5ede97725 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2354,4 +2354,20 @@ parents({start}) *vim.fs.parents()* Return: ~ (function) Iterator + +============================================================================== +Lua module: secure *lua-secure* + +read({path}) *vim.secure.read()* + Attempt to read the file at {path} prompting the user if the file should + be trusted. The user's choice is persisted in a trust database at + $XDG_STATE_HOME/nvim/trust. + + Parameters: ~ + • {path} (string) Path to a file to read. + + Return: ~ + (string|nil) The contents of the given file if it exists and is + trusted, or nil otherwise. + vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index d339df8479..2aa4bea73b 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -39,6 +39,9 @@ NEW FEATURES *news-features* The following new APIs or features were added. +• |vim.secure.read()| reads a file and prompts the user if it should be + trusted and, if so, returns the file's contents. + • When using Nvim inside tmux 3.2 or later, the default clipboard provider will now copy to the system clipboard. |provider-clipboard| diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 0013f38d89..ad4dc20efb 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -36,6 +36,7 @@ for k, v in pairs({ ui = true, health = true, fs = true, + secure = true, }) do vim._submodules[k] = v end diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua new file mode 100644 index 0000000000..341ff8df05 --- /dev/null +++ b/runtime/lua/vim/secure.lua @@ -0,0 +1,106 @@ +local M = {} + +--- Attempt to read the file at {path} prompting the user if the file should be +--- trusted. The user's choice is persisted in a trust database at +--- $XDG_STATE_HOME/nvim/trust. +--- +---@param path (string) Path to a file to read. +--- +---@return (string|nil) The contents of the given file if it exists and is +--- trusted, or nil otherwise. +function M.read(path) + vim.validate({ path = { path, 's' } }) + local fullpath = vim.loop.fs_realpath(vim.fs.normalize(path)) + if not fullpath then + return nil + end + + local trust = {} + do + local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r') + if f then + local contents = f:read('*a') + if contents then + for line in vim.gsplit(contents, '\n') do + local hash, file = string.match(line, '^(%S+) (.+)$') + if hash and file then + trust[file] = hash + end + end + end + f:close() + end + end + + if trust[fullpath] == '!' then + -- File is denied + return nil + end + + local contents + do + local f = io.open(fullpath, 'r') + if not f then + return nil + end + contents = f:read('*a') + f:close() + end + + local hash = vim.fn.sha256(contents) + if trust[fullpath] == hash then + -- File already exists in trust database + return contents + end + + -- File either does not exist in trust database or the hash does not match + local choice = vim.fn.confirm( + string.format('%s is not trusted.', fullpath), + '&ignore\n&view\n&deny\n&allow', + 1 + ) + + if choice == 0 or choice == 1 then + -- Cancelled or ignored + return nil + elseif choice == 2 then + -- View + vim.cmd('new') + local buf = vim.api.nvim_get_current_buf() + local lines = vim.split(string.gsub(contents, '\n$', ''), '\n') + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + vim.bo[buf].bufhidden = 'hide' + vim.bo[buf].buftype = 'nofile' + vim.bo[buf].swapfile = false + vim.bo[buf].modeline = false + vim.bo[buf].buflisted = false + vim.bo[buf].readonly = true + vim.bo[buf].modifiable = false + return nil + elseif choice == 3 then + -- Deny + trust[fullpath] = '!' + contents = nil + elseif choice == 4 then + -- Allow + trust[fullpath] = hash + end + + do + local f, err = io.open(vim.fn.stdpath('state') .. '/trust', 'w') + if not f then + error(err) + end + + local t = {} + for p, h in pairs(trust) do + t[#t + 1] = string.format('%s %s\n', h, p) + end + f:write(table.concat(t)) + f:close() + end + + return contents +end + +return M |