Fix #2859; script error when cwd name contains %

The `string.gsub()` function in Lua always uses Lua patterns (which are
similar to regular expressions).  Cmder's custom prompt wants to perform
simple plain text find/replace operations on strings.  `string.gsub()`
is the right Lua function for that, but since it always uses Lua
patterns it's necessary to apply escaping to the input strings otherwise
they can get misinterpreted and cause runtime errors.

For example, if the current working directory name contains a percent
sign, such as literally "My%20Home".

This change fixes that.  It introduces a helper function `gsub_plain()`
which behaves like `string.gsub()` but applies appropriate escaping to
convert the plain text input strings into the corresponding Lua
patterns so that it can achieve plain text find/replace operations.

It also introduces separate helper functions for escaping the `find` and
`replace` parameters for `string.gsub()`, since they have different
escaping rules.
This commit is contained in:
Chris Antos 2023-07-10 18:07:53 -07:00
parent e9750ab73d
commit 84ee96c64f

58
vendor/clink.lua vendored
View File

@ -51,11 +51,37 @@ local function get_unknown_color()
end end
--- ---
-- Makes a string safe to use as the replacement in string.gsub -- Escapes special characters in a string.gsub `find` parameter, so that it
-- can be matched as a literal plain text string, i.e. disable Lua pattern
-- matching. See "Patterns" (https://www.lua.org/manual/5.2/manual.html#6.4.1).
-- @param {string} text Text to escape
-- @returns {string} Escaped text
--- ---
local function verbatim(s) local function escape_gsub_find_arg(text)
s = string.gsub(s, "%%", "%%%%") return text and text:gsub("([-+*?.%%()%[%]$^])", "%%%1") or ""
return s end
---
-- Escapes special characters in a string.gsub `replace` parameter, so that it
-- can be replaced as a literal plain text string, i.e. disable Lua pattern
-- matching. See "Patterns" (https://www.lua.org/manual/5.2/manual.html#6.4.1).
-- @param {string} text Text to escape
-- @returns {string} Escaped text
---
local function escape_gsub_replace_arg(text)
return text and text:gsub("%%", "%%%%") or ""
end
---
-- Perform string.sub, but disable Lua pattern matching and just treat both
-- the `find` and `replace` parameters as a literal plain text replacement.
-- @param {string} str Text in which to perform find and replace
-- @param {string} find Text to find (plain text; not a Lua pattern)
-- @param {string} replace Replacement text (plain text; not a Lua pattern)
-- @returns {string} Copy of the input `str` with `find` replaced by `replace`
---
local function gsub_plain(str, find, replace)
return string.gsub(str, escape_gsub_find_arg(find), escape_gsub_replace_arg(replace))
end end
-- Extracts only the folder name from the input Path -- Extracts only the folder name from the input Path
@ -153,7 +179,7 @@ local function set_prompt_filter()
end end
if prompt_useHomeSymbol and string.find(cwd, clink.get_env("HOME")) then if prompt_useHomeSymbol and string.find(cwd, clink.get_env("HOME")) then
cwd = string.gsub(cwd, clink.get_env("HOME"), prompt_homeSymbol) cwd = gsub_plain(cwd, clink.get_env("HOME"), prompt_homeSymbol)
end end
local uah = '' local uah = ''
@ -176,14 +202,14 @@ local function set_prompt_filter()
local version_control = prompt_includeVersionControl and "{git}{hg}{svn}" or "" local version_control = prompt_includeVersionControl and "{git}{hg}{svn}" or ""
local prompt = "{uah}{cwd}" .. version_control .. cr .. get_lamb_color() .. "{env}{lamb}\x1b[0m " local prompt = "{uah}{cwd}" .. version_control .. cr .. get_lamb_color() .. "{env}{lamb}\x1b[0m "
prompt = string.gsub(prompt, "{uah}", uah) prompt = gsub_plain(prompt, "{uah}", uah)
prompt = string.gsub(prompt, "{cwd}", cwd) prompt = gsub_plain(prompt, "{cwd}", cwd)
prompt = string.gsub(prompt, "{env}", env) prompt = gsub_plain(prompt, "{env}", env)
clink.prompt.value = string.gsub(prompt, "{lamb}", prompt_lambSymbol) clink.prompt.value = gsub_plain(prompt, "{lamb}", prompt_lambSymbol)
end end
local function percent_prompt_filter() local function percent_prompt_filter()
clink.prompt.value = string.gsub(clink.prompt.value, "{percent}", "%%") clink.prompt.value = gsub_plain(clink.prompt.value, "{percent}", "%")
end end
--- ---
@ -532,13 +558,13 @@ local function git_prompt_filter()
color = colors.conflict color = colors.conflict
end end
clink.prompt.value = string.gsub(clink.prompt.value, "{git}", " "..color.."("..verbatim(branch)..")") clink.prompt.value = gsub_plain(clink.prompt.value, "{git}", " "..color.."("..branch..")")
return false return false
end end
end end
-- No git present or not in git file -- No git present or not in git file
clink.prompt.value = string.gsub(clink.prompt.value, "{git}", "") clink.prompt.value = gsub_plain(clink.prompt.value, "{git}", "")
return false return false
end end
@ -577,13 +603,13 @@ local function hg_prompt_filter()
end end
local result = color .. "(" .. branch .. ")" local result = color .. "(" .. branch .. ")"
clink.prompt.value = string.gsub(clink.prompt.value, "{hg}", " "..verbatim(result)) clink.prompt.value = gsub_plain(clink.prompt.value, "{hg}", " "..result)
return false return false
end end
end end
-- No hg present or not in hg repo -- No hg present or not in hg repo
clink.prompt.value = string.gsub(clink.prompt.value, "{hg}", "") clink.prompt.value = gsub_plain(clink.prompt.value, "{hg}", "")
end end
local function svn_prompt_filter() local function svn_prompt_filter()
@ -636,13 +662,13 @@ local function svn_prompt_filter()
color = colors.dirty color = colors.dirty
end end
clink.prompt.value = string.gsub(clink.prompt.value, "{svn}", " "..color.."("..verbatim(branch)..")") clink.prompt.value = gsub_plain(clink.prompt.value, "{svn}", " "..color.."("..branch..")")
return false return false
end end
end end
-- No svn present or not in svn file -- No svn present or not in svn file
clink.prompt.value = string.gsub(clink.prompt.value, "{svn}", "") clink.prompt.value = gsub_plain(clink.prompt.value, "{svn}", "")
return false return false
end end