util.format: Ensure sanitation of strings passed to wrong format

Ie. log("debug", "%d", "\1\2\3") should not result in garbage.

Also optimizing for the common case of ASCII string passed to %s and
early returns everywhere.

Returning nil from a gsub callback keeps the original substring.
This commit is contained in:
Kim Alvefur 2021-12-11 13:30:34 +01:00
parent d4c1451794
commit 1eca4e8870
2 changed files with 38 additions and 21 deletions

View file

@ -18,6 +18,7 @@ describe("util.format", function()
it("escapes ascii control stuff", function () it("escapes ascii control stuff", function ()
assert.equal("", format("%s", "\1")); assert.equal("", format("%s", "\1"));
assert.equal("[␁]", format("%d", "\1"));
end); end);
it("escapes invalid UTF-8", function () it("escapes invalid UTF-8", function ()

View file

@ -49,36 +49,52 @@ local function format(formatstring, ...)
-- process each format specifier -- process each format specifier
local i = 0; local i = 0;
formatstring = formatstring:gsub("%%[^cdiouxXaAeEfgGqs%%]*[cdiouxXaAeEfgGqs%%]", function(spec) formatstring = formatstring:gsub("%%[^cdiouxXaAeEfgGqs%%]*[cdiouxXaAeEfgGqs%%]", function(spec)
if spec ~= "%%" then if spec == "%%" then return end
i = i + 1; i = i + 1;
local arg = args[i]; local arg = args[i];
local option = spec:sub(-1); if arg == nil then
if arg == nil then args[i] = "nil";
args[i] = "nil"; return "(%s)";
spec = "(%s)"; end
elseif option == "q" then
args[i] = dump(arg); local option = spec:sub(-1);
spec = "%s"; local t = type(arg);
elseif option == "s" then
if option == "s" and t == "string" and not arg:find("[%z\1-\31\128-\255]") then
-- No UTF-8 or control characters, assumed to be the common case.
return
end
if option ~= "s" and option ~= "q" then
-- all other options expect numbers
if t ~= "number" then
-- arg isn't number as expected?
arg = tostring(arg); arg = tostring(arg);
if arg:find("[\128-\255]") and not valid_utf8(arg) then
args[i] = dump(arg);
else
args[i] = arg:gsub("[%z\1-\8\11-\31\127]", control_symbols):gsub("\n\t?", "\n\t");
end
elseif type(arg) ~= "number" then -- arg isn't number as expected?
args[i] = tostring(arg);
spec = "[%s]";
option = "s"; option = "s";
spec = "[%s]"; spec = "[%s]";
t = "string"; t = "string";
elseif expects_integer[option] and num_type(arg) ~= "integer" then elseif expects_integer[option] and num_type(arg) ~= "integer" then
args[i] = tostring(arg); args[i] = tostring(arg);
spec = "[%s]"; return "[%s]";
else
return -- acceptable number
end end
end end
return spec;
if t == "string" then
if not valid_utf8(arg) then
option = "q";
else
args[i] = arg:gsub("[%z\1-\8\11-\31\127]", control_symbols):gsub("\n\t?", "\n\t");
return spec;
end
end
if option == "q" then
args[i] = dump(arg);
return "%s";
end
end); end);
-- process extra args -- process extra args