diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 18:31:31 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 18:31:31 +0000 |
commit | 9243becbedbb6a1592208051f8fa2b090dcc5e7d (patch) | |
tree | 607c2a862ec3f4399b8766383f6f8e04c4aa43b4 /runtime/lua/vim/secure.lua | |
parent | 9e40b6e9e1bc67f2d856adb837ee64dd0e25b717 (diff) | |
parent | 3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff) | |
download | rneovim-usermarks.tar.gz rneovim-usermarks.tar.bz2 rneovim-usermarks.zip |
Merge remote-tracking branch 'upstream/master' into usermarksusermarks
Diffstat (limited to 'runtime/lua/vim/secure.lua')
-rw-r--r-- | runtime/lua/vim/secure.lua | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua new file mode 100644 index 0000000000..443b152273 --- /dev/null +++ b/runtime/lua/vim/secure.lua @@ -0,0 +1,188 @@ +local M = {} + +---@private +--- Reads trust database from $XDG_STATE_HOME/nvim/trust. +--- +---@return (table) Contents of trust database, if it exists. Empty table otherwise. +local function read_trust() + local trust = {} + 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 + return trust +end + +---@private +--- Writes provided {trust} table to trust database at +--- $XDG_STATE_HOME/nvim/trust. +--- +---@param trust (table) Trust table to write +local function write_trust(trust) + vim.validate({ trust = { trust, 't' } }) + local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w')) + + 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 + +--- 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. +--- +---@see |: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 = read_trust() + + 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 ok, result = pcall( + vim.fn.confirm, + string.format('%s is not trusted.', fullpath), + '&ignore\n&view\n&deny\n&allow', + 1 + ) + + if not ok and result ~= 'Keyboard interrupt' then + error(result) + elseif not ok or result == 0 or result == 1 then + -- Cancelled or ignored + return nil + elseif result == 2 then + -- View + vim.cmd('sview ' .. fullpath) + return nil + elseif result == 3 then + -- Deny + trust[fullpath] = '!' + contents = nil + elseif result == 4 then + -- Allow + trust[fullpath] = hash + end + + write_trust(trust) + + return contents +end + +--- Manage the trust database. +--- +--- The trust database is located at |$XDG_STATE_HOME|/nvim/trust. +--- +---@param opts (table): +--- - action (string): "allow" to add a file to the trust database and trust it, +--- "deny" to add a file to the trust database and deny it, +--- "remove" to remove file from the trust database +--- - path (string|nil): Path to a file to update. Mutually exclusive with {bufnr}. +--- Cannot be used when {action} is "allow". +--- - bufnr (number|nil): Buffer number to update. Mutually exclusive with {path}. +---@return (boolean, string) success, msg: +--- - true and full path of target file if operation was successful +--- - false and error message on failure +function M.trust(opts) + vim.validate({ + path = { opts.path, 's', true }, + bufnr = { opts.bufnr, 'n', true }, + action = { + opts.action, + function(m) + return m == 'allow' or m == 'deny' or m == 'remove' + end, + [["allow" or "deny" or "remove"]], + }, + }) + + local path = opts.path + local bufnr = opts.bufnr + local action = opts.action + + assert(not path or not bufnr, '"path" and "bufnr" are mutually exclusive') + + if action == 'allow' then + assert(not path, '"path" is not valid when action is "allow"') + end + + local fullpath + if path then + fullpath = vim.loop.fs_realpath(vim.fs.normalize(path)) + elseif bufnr then + local bufname = vim.api.nvim_buf_get_name(bufnr) + if bufname == '' then + return false, 'buffer is not associated with a file' + end + fullpath = vim.loop.fs_realpath(vim.fs.normalize(bufname)) + else + error('one of "path" or "bufnr" is required') + end + + if not fullpath then + return false, string.format('invalid path: %s', path) + end + + local trust = read_trust() + + if action == 'allow' then + local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n' + local contents = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), newline) + if vim.bo[bufnr].endofline then + contents = contents .. newline + end + local hash = vim.fn.sha256(contents) + + trust[fullpath] = hash + elseif action == 'deny' then + trust[fullpath] = '!' + elseif action == 'remove' then + trust[fullpath] = nil + end + + write_trust(trust) + return true, fullpath +end + +return M |