mirror of
https://github.com/neovim/neovim.git
synced 2024-12-23 20:55:18 -07:00
feat: add vim.secure.read()
This function accepts a path to a file and prompts the user if the file is trusted. If the user confirms that the file is trusted, the contents of the file are returned. The user's decision is stored in a trust database at $XDG_STATE_HOME/nvim/trust. When this function is invoked with a path that is already marked as trusted in the trust database, the user is not prompted for a response.
This commit is contained in:
parent
9736605672
commit
f1922e78a1
@ -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:
|
||||
|
@ -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|
|
||||
|
||||
|
@ -36,6 +36,7 @@ for k, v in pairs({
|
||||
ui = true,
|
||||
health = true,
|
||||
fs = true,
|
||||
secure = true,
|
||||
}) do
|
||||
vim._submodules[k] = v
|
||||
end
|
||||
|
106
runtime/lua/vim/secure.lua
Normal file
106
runtime/lua/vim/secure.lua
Normal file
@ -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
|
@ -131,6 +131,7 @@ CONFIG = {
|
||||
'filetype.lua',
|
||||
'keymap.lua',
|
||||
'fs.lua',
|
||||
'secure.lua',
|
||||
],
|
||||
'files': [
|
||||
'runtime/lua/vim/_editor.lua',
|
||||
@ -140,6 +141,7 @@ CONFIG = {
|
||||
'runtime/lua/vim/filetype.lua',
|
||||
'runtime/lua/vim/keymap.lua',
|
||||
'runtime/lua/vim/fs.lua',
|
||||
'runtime/lua/vim/secure.lua',
|
||||
],
|
||||
'file_patterns': '*.lua',
|
||||
'fn_name_prefix': '',
|
||||
@ -166,6 +168,7 @@ CONFIG = {
|
||||
'filetype': 'vim.filetype',
|
||||
'keymap': 'vim.keymap',
|
||||
'fs': 'vim.fs',
|
||||
'secure': 'vim.secure',
|
||||
},
|
||||
'append_only': [
|
||||
'shared.lua',
|
||||
|
171
test/functional/lua/secure_spec.lua
Normal file
171
test/functional/lua/secure_spec.lua
Normal file
@ -0,0 +1,171 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
|
||||
local eq = helpers.eq
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
local pathsep = helpers.get_pathsep()
|
||||
local iswin = helpers.iswin()
|
||||
local curbufmeths = helpers.curbufmeths
|
||||
local exec_lua = helpers.exec_lua
|
||||
local feed_command = helpers.feed_command
|
||||
local feed = helpers.feed
|
||||
local funcs = helpers.funcs
|
||||
local pcall_err = helpers.pcall_err
|
||||
|
||||
describe('vim.secure', function()
|
||||
describe('read()', function()
|
||||
local xstate = 'Xstate'
|
||||
|
||||
setup(function()
|
||||
helpers.mkdir_p(xstate .. pathsep .. (iswin and 'nvim-data' or 'nvim'))
|
||||
end)
|
||||
|
||||
teardown(function()
|
||||
helpers.rmdir(xstate)
|
||||
end)
|
||||
|
||||
before_each(function()
|
||||
helpers.write_file('Xfile', [[
|
||||
let g:foobar = 42
|
||||
]])
|
||||
clear{env={XDG_STATE_HOME=xstate}}
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove('Xfile')
|
||||
helpers.rmdir(xstate)
|
||||
end)
|
||||
|
||||
it('works', function()
|
||||
local screen = Screen.new(80, 8)
|
||||
screen:attach()
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {bold = true, foreground = Screen.colors.Blue1},
|
||||
[2] = {bold = true, reverse = true},
|
||||
[3] = {bold = true, foreground = Screen.colors.SeaGreen},
|
||||
[4] = {reverse = true},
|
||||
})
|
||||
|
||||
local cwd = funcs.getcwd()
|
||||
|
||||
-- Need to use feed_command instead of exec_lua because of the confirmation prompt
|
||||
feed_command([[lua vim.secure.read('Xfile')]])
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
:lua vim.secure.read('Xfile') |
|
||||
{3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
|
||||
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
|
||||
]]}
|
||||
feed('d')
|
||||
screen:expect{grid=[[
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]]}
|
||||
|
||||
local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
|
||||
eq(string.format('! %s', cwd .. pathsep .. 'Xfile'), vim.trim(trust))
|
||||
eq(helpers.NIL, exec_lua([[return vim.secure.read('Xfile')]]))
|
||||
|
||||
os.remove(funcs.stdpath('state') .. pathsep .. 'trust')
|
||||
|
||||
feed_command([[lua vim.secure.read('Xfile')]])
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
:lua vim.secure.read('Xfile') |
|
||||
{3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
|
||||
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
|
||||
]]}
|
||||
feed('a')
|
||||
screen:expect{grid=[[
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]]}
|
||||
|
||||
local hash = funcs.sha256(helpers.read_file('Xfile'))
|
||||
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
|
||||
eq(string.format('%s %s', hash, cwd .. pathsep .. 'Xfile'), vim.trim(trust))
|
||||
eq(helpers.NIL, exec_lua([[vim.secure.read('Xfile')]]))
|
||||
|
||||
os.remove(funcs.stdpath('state') .. pathsep .. 'trust')
|
||||
|
||||
feed_command([[lua vim.secure.read('Xfile')]])
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
:lua vim.secure.read('Xfile') |
|
||||
{3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
|
||||
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
|
||||
]]}
|
||||
feed('i')
|
||||
screen:expect{grid=[[
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]]}
|
||||
|
||||
-- Trust database is not updated
|
||||
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
|
||||
eq(nil, trust)
|
||||
|
||||
feed_command([[lua vim.secure.read('Xfile')]])
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
:lua vim.secure.read('Xfile') |
|
||||
{3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
|
||||
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
|
||||
]]}
|
||||
feed('v')
|
||||
screen:expect{grid=[[
|
||||
^ let g:foobar = 42 |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:]] .. cwd .. pathsep .. [[Xfile [RO]{MATCH:%s+}|
|
||||
|
|
||||
{1:~ }|
|
||||
{4:[No Name] }|
|
||||
|
|
||||
]]}
|
||||
|
||||
-- Trust database is not updated
|
||||
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
|
||||
eq(nil, trust)
|
||||
|
||||
-- Cannot write file
|
||||
pcall_err(command, 'write')
|
||||
eq(false, curbufmeths.get_option('modifiable'))
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user