diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index c34c28f23a..9abea5cf62 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -199,6 +199,8 @@ function M.open(path, opt) if open_cmd[1] == 'xdg-open' then job_opt.stdout = false job_opt.stderr = false + elseif open_cmd[1] == 'cmd.exe' and is_uri then + path = path:gsub('([&|<>^%%!])', '^%1') -- Escape cmd.exe special chars. #41337 end cmd = vim.list_extend(open_cmd, { path }) end diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua index 8cd371b714..bcd7ff7419 100644 --- a/test/functional/lua/ui_spec.lua +++ b/test/functional/lua/ui_spec.lua @@ -129,6 +129,28 @@ describe('vim.ui', function() ) end) + it('escapes cmd.exe metacharacters in URIs #41337', function() + eq( + { 'cmd.exe', '/c', 'start', '', 'https://example.com/?q=^&^|^<^>^^^%^!' }, + exec_lua(function() + vim.fn.has = function(feat) + return feat == 'win32' and 1 or 0 + end + local captured --- @type string[] + vim.system = function(cmd) + captured = cmd + return { + wait = function() + return { code = 0 } + end, + } + end + vim.ui.open('https://example.com/?q=&|<>^%!') + return captured + end) + ) + end) + it('opt.cmd #29490', function() t.matches( 'ENOENT: no such file or directory',