123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- local M = {}
- --- Reads trust database from $XDG_STATE_HOME/nvim/trust.
- ---
- ---@return table<string, string> Contents of trust database, if it exists. Empty table otherwise.
- local function read_trust()
- local trust = {} ---@type table<string, string>
- 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
- --- Writes provided {trust} table to trust database at
- --- $XDG_STATE_HOME/nvim/trust.
- ---
- ---@param trust table<string, string> Trust table to write
- local function write_trust(trust)
- vim.validate('trust', trust, 'table')
- local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w'))
- local t = {} ---@type string[]
- 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.
- ---
- ---@since 11
- ---@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, 'string')
- local fullpath = vim.uv.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 ---@type string?
- 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
- --- @class vim.trust.opts
- --- @inlinedoc
- ---
- --- - `'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
- --- @field action 'allow'|'deny'|'remove'
- ---
- --- Path to a file to update. Mutually exclusive with {bufnr}.
- --- Cannot be used when {action} is "allow".
- --- @field path? string
- --- Buffer number to update. Mutually exclusive with {path}.
- --- @field bufnr? integer
- --- Manage the trust database.
- ---
- --- The trust database is located at |$XDG_STATE_HOME|/nvim/trust.
- ---
- ---@since 11
- ---@param opts vim.trust.opts
- ---@return boolean success true if operation was successful
- ---@return string msg full path if operation was successful, else error message
- function M.trust(opts)
- vim.validate('path', opts.path, 'string', true)
- vim.validate('bufnr', opts.bufnr, 'number', true)
- vim.validate('action', opts.action, function(m)
- return m == 'allow' or m == 'deny' or m == 'remove'
- end, [["allow" or "deny" or "remove"]])
- ---@cast opts vim.trust.opts
- 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 ---@type string?
- if path then
- fullpath = vim.uv.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.uv.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 --[[@as integer]], 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
|