aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/secure.lua
blob: 341ff8df052fc700018a31f0d58cff8960c29351 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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