mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			193 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
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, 't' } })
 | 
						|
  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.
 | 
						|
---
 | 
						|
---@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.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
 | 
						|
---@field action string
 | 
						|
---@field path? string
 | 
						|
---@field bufnr? integer
 | 
						|
 | 
						|
--- 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 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, '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"]],
 | 
						|
    },
 | 
						|
  })
 | 
						|
 | 
						|
  ---@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
 |