aboutsummaryrefslogtreecommitdiff
path: root/lua/muwiki/io.lua
blob: e86fb2e44ba8d70e2a95c6df7ad17ccaf08c3a93 (plain)
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
local config = require('muwiki.config')
local paths = require('muwiki.paths')

local M = {}

function M.exists(path)
  local stat = vim.uv.fs_stat(path)
  return stat and stat.type or nil
end

function M.file_exists(path)
  return M.exists(path) == 'file'
end

function M.dir_exists(path)
  return M.exists(path) == 'directory'
end

function M.create_dir_safely(dirpath, wiki_root)
  if not paths.is_within_wiki(dirpath, wiki_root) then
    return false, 'Directory is outside wiki root'
  end

  if M.dir_exists(dirpath) then
    return true, nil
  end

  if vim.fn.mkdir(dirpath, 'p') ~= 1 then
    return false, 'Failed to create directory'
  end

  if not paths.is_within_wiki(dirpath, wiki_root) then
    vim.fn.delete(dirpath, 'd')
    return false, 'Symlink attack detected'
  end

  return true, nil
end

function M.ensure_parent_dirs(filepath, wiki_root, mode, callback)
  local dirpath = vim.fs.dirname(filepath)

  if M.dir_exists(dirpath) then
    if callback then
      callback(true)
    end
    return true
  end

  local function do_create()
    local success, err = M.create_dir_safely(dirpath, wiki_root)
    if not success then
      vim.notify(string.format('Cannot create directory: %s', err), vim.log.levels.ERROR)
      if callback then
        callback(false)
      end
      return false
    end

    if mode == 'notify' then
      vim.notify(string.format('Created directory: %s', dirpath), vim.log.levels.INFO)
    end

    if callback then
      callback(true)
    end
    return true
  end

  if mode == 'prompt' then
    vim.ui.select({ 'Yes', 'No' }, {
      prompt = string.format('Directory does not exist. Create %s?', dirpath),
    }, function(choice)
      if choice == 'Yes' then
        do_create()
      else
        vim.notify('Directory creation cancelled', vim.log.levels.INFO)
        if callback then
          callback(false)
        end
      end
    end)
    return nil
  end

  return do_create()
end

function M.open_in_buffer(filepath)
  local bufnr = vim.fn.bufnr(filepath, true)
  vim.api.nvim_win_set_buf(0, bufnr)
end

function M.open_index(name)
  local wiki_path = config.wiki_path(name)
  if not wiki_path then
    return
  end

  local index_path = vim.fs.joinpath(wiki_path, config.options.index_file)
  M.open_in_buffer(index_path)
end

function M.resolve(filepath, wiki_root)
  if not wiki_root then
    error('wiki_root parameter is required for secure path resolution')
  end

  local path = paths.strip_file_protocol(filepath)
  local resolved = paths.resolve(path, wiki_root)
  return paths.validate_within_wiki(resolved, wiki_root, filepath)
end

function M.is_text_file(ext)
  local ext_lower = ext:lower()
  for _, text_ext in ipairs(config.options.text_extensions) do
    if ext_lower == text_ext then
      return true
    end
  end
  return false
end

function M.normalize_filename(text)
  local normalized = text:lower()
  normalized = normalized:gsub('%s+', '_')
  normalized = normalized:gsub('[^%w_%-%.]', '')
  return normalized
end

function M.open_wiki_file(filepath)
  local template = require('muwiki.template')
  local exists = M.file_exists(filepath)

  if not exists and config.options.create_missing_dirs then
    local wiki_root = config.wiki_root(0)
    if wiki_root then
      local mode = config.options.create_missing_dirs

      if mode == 'prompt' then
        M.ensure_parent_dirs(filepath, wiki_root, mode, function(success)
          if success then
            M.open_in_buffer(filepath)
            template.init_file(vim.api.nvim_get_current_buf(), filepath)
          end
        end)
        return
      else
        local success = M.ensure_parent_dirs(filepath, wiki_root, mode)
        if not success then
          return
        end
      end
    end
  end

  M.open_in_buffer(filepath)
  template.init_file(vim.api.nvim_get_current_buf(), filepath)
end

return M