mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
fix(trust): support for trusting directories #33617
Problem: Directories that are "trusted" by `vim.secure.read()`, are not detectable later (they will prompt again). https://github.com/neovim/neovim/discussions/33587#discussioncomment-12925887 Solution: `vim.secure.read()` returns `true` if the user trusts a directory. Also fix other bugs: - If `f:read('*a')` returns `nil`, we treat that as a successful read of the file, and hash it. `f:read` returns `nil` for directories, but it's also documented as returning `nil` "if it cannot read data with the specified format". I reworked the implementation so we explicitly treat directories differently. Rather than hashing `nil` to put in the trust database, we now put "directory" in there explicitly*. - `vim.secure.trust` (used by `:trust`) didn't actually work for directories, as it would blindly read the contents of a netrw buffer and hash it. Now it uses the same codepath as `vim.secure.read`, and as a result, works correctly for directories.
This commit is contained in:

committed by
GitHub

parent
0ab0cdb2da
commit
272dba7f07
@@ -21,6 +21,50 @@ local function read_trust()
|
||||
return trust
|
||||
end
|
||||
|
||||
--- If {fullpath} is a file, read the contents of {fullpath} (or the contents of {bufnr}
|
||||
--- if given) and returns the contents and a hash of the contents.
|
||||
---
|
||||
--- If {fullpath} is a directory, then nothing is read from the filesystem, and
|
||||
--- `contents = true` and `hash = "directory"` is returned instead.
|
||||
---
|
||||
---@param fullpath (string) Path to a file or directory to read.
|
||||
---@param bufnr (number?) The number of the buffer.
|
||||
---@return string|boolean? contents the contents of the file, or true if it's a directory
|
||||
---@return string? hash the hash of the contents, or "directory" if it's a directory
|
||||
local function compute_hash(fullpath, bufnr)
|
||||
local contents ---@type string|boolean?
|
||||
local hash ---@type string
|
||||
if vim.fn.isdirectory(fullpath) == 1 then
|
||||
return true, 'directory'
|
||||
end
|
||||
|
||||
if bufnr then
|
||||
local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n'
|
||||
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
|
||||
else
|
||||
do
|
||||
local f = io.open(fullpath, 'r')
|
||||
if not f then
|
||||
return nil, nil
|
||||
end
|
||||
contents = f:read('*a')
|
||||
f:close()
|
||||
end
|
||||
|
||||
if not contents then
|
||||
return nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
hash = vim.fn.sha256(contents)
|
||||
|
||||
return contents, hash
|
||||
end
|
||||
|
||||
--- Writes provided {trust} table to trust database at
|
||||
--- $XDG_STATE_HOME/nvim/trust.
|
||||
---
|
||||
@@ -37,17 +81,22 @@ local function write_trust(trust)
|
||||
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
|
||||
--- If {path} is a file: attempt to read the file, prompting the user if the file should be
|
||||
--- trusted.
|
||||
---
|
||||
--- If {path} is a directory: return true if the directory is trusted (non-recursive), prompting
|
||||
--- the user as necessary.
|
||||
---
|
||||
--- 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.
|
||||
---@param path (string) Path to a file or directory to read.
|
||||
---
|
||||
---@return (string|nil) The contents of the given file if it exists and is
|
||||
--- trusted, or nil otherwise.
|
||||
---@return (boolean|string|nil) If {path} is not trusted or does not exist, returns `nil`. Otherwise,
|
||||
--- returns the contents of {path} if it is a file, or true if {path} is a directory.
|
||||
function M.read(path)
|
||||
vim.validate('path', path, 'string')
|
||||
local fullpath = vim.uv.fs_realpath(vim.fs.normalize(path))
|
||||
@@ -62,26 +111,25 @@ function M.read(path)
|
||||
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()
|
||||
local contents, hash = compute_hash(fullpath, nil)
|
||||
if not contents then
|
||||
return nil
|
||||
end
|
||||
|
||||
local hash = vim.fn.sha256(contents)
|
||||
if trust[fullpath] == hash then
|
||||
-- File already exists in trust database
|
||||
return contents
|
||||
end
|
||||
|
||||
local dir_msg = ''
|
||||
if hash == 'directory' then
|
||||
dir_msg = ' DIRECTORY trust is decided only by its name, not its 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),
|
||||
string.format('%s is not trusted.%s', fullpath, dir_msg),
|
||||
'&ignore\n&view\n&deny\n&allow',
|
||||
1
|
||||
)
|
||||
@@ -169,13 +217,10 @@ function M.trust(opts)
|
||||
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
|
||||
local contents, hash = compute_hash(fullpath, bufnr)
|
||||
if not contents then
|
||||
return false, string.format('could not read path: %s', fullpath)
|
||||
end
|
||||
local hash = vim.fn.sha256(contents)
|
||||
|
||||
trust[fullpath] = hash
|
||||
elseif action == 'deny' then
|
||||
|
Reference in New Issue
Block a user