From 84ee96c64fe643651eb6220717ad2250ec169a10 Mon Sep 17 00:00:00 2001 From: Chris Antos Date: Mon, 10 Jul 2023 18:07:53 -0700 Subject: [PATCH] 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. --- vendor/clink.lua | 58 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/vendor/clink.lua b/vendor/clink.lua index 83ef0c0..0593ae1 100644 --- a/vendor/clink.lua +++ b/vendor/clink.lua @@ -51,11 +51,37 @@ local function get_unknown_color() 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) - s = string.gsub(s, "%%", "%%%%") - return s +local function escape_gsub_find_arg(text) + return text and text:gsub("([-+*?.%%()%[%]$^])", "%%%1") or "" +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 -- Extracts only the folder name from the input Path @@ -153,7 +179,7 @@ local function set_prompt_filter() end 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 local uah = '' @@ -176,14 +202,14 @@ local function set_prompt_filter() local version_control = prompt_includeVersionControl and "{git}{hg}{svn}" or "" local prompt = "{uah}{cwd}" .. version_control .. cr .. get_lamb_color() .. "{env}{lamb}\x1b[0m " - prompt = string.gsub(prompt, "{uah}", uah) - prompt = string.gsub(prompt, "{cwd}", cwd) - prompt = string.gsub(prompt, "{env}", env) - clink.prompt.value = string.gsub(prompt, "{lamb}", prompt_lambSymbol) + prompt = gsub_plain(prompt, "{uah}", uah) + prompt = gsub_plain(prompt, "{cwd}", cwd) + prompt = gsub_plain(prompt, "{env}", env) + clink.prompt.value = gsub_plain(prompt, "{lamb}", prompt_lambSymbol) end local function percent_prompt_filter() - clink.prompt.value = string.gsub(clink.prompt.value, "{percent}", "%%") + clink.prompt.value = gsub_plain(clink.prompt.value, "{percent}", "%") end --- @@ -532,13 +558,13 @@ local function git_prompt_filter() color = colors.conflict 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 end end -- 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 end @@ -577,13 +603,13 @@ local function hg_prompt_filter() end 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 end end -- 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 local function svn_prompt_filter() @@ -636,13 +662,13 @@ local function svn_prompt_filter() color = colors.dirty 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 end end -- 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 end