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
|