mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
This was implied with the "debug" preset and does nice things like turn util.set objects into "set{a,b,c}" instead of the quite verbose thing you get otherwise.
153 lines
4.9 KiB
Lua
153 lines
4.9 KiB
Lua
--
|
|
-- A string.format wrapper that gracefully handles invalid arguments since
|
|
-- certain format string and argument combinations may cause errors or other
|
|
-- issues like log spoofing
|
|
--
|
|
-- Provides some protection from e.g. CAPEC-135, CWE-117, CWE-134, CWE-93
|
|
|
|
local tostring = tostring;
|
|
local unpack = table.unpack;
|
|
local pack = table.pack;
|
|
local valid_utf8 = require "prosody.util.encodings".utf8.valid;
|
|
local type = type;
|
|
local dump = require"prosody.util.serialization".new({
|
|
preset = "compact";
|
|
fallback = function(v, why)
|
|
return "_[[" .. (why or tostring(v)) .. "]] ";
|
|
end;
|
|
freeze = true;
|
|
fatal = false;
|
|
maxdepth = 5;
|
|
});
|
|
local num_type = math.type;
|
|
|
|
-- In Lua 5.3+ these formats throw an error if given a float
|
|
local expects_integer = { c = true, d = true, i = true, o = true, u = true, X = true, x = true, };
|
|
-- In Lua 5.2 these throw an error given a negative number
|
|
local expects_positive = { o = true; u = true; x = true; X = true };
|
|
-- Printable Unicode replacements for control characters
|
|
local control_symbols = {
|
|
-- 0x00 .. 0x1F --> U+2400 .. U+241F, 0x7F --> U+2421
|
|
["\000"] = "\226\144\128", ["\001"] = "\226\144\129", ["\002"] = "\226\144\130",
|
|
["\003"] = "\226\144\131", ["\004"] = "\226\144\132", ["\005"] = "\226\144\133",
|
|
["\006"] = "\226\144\134", ["\007"] = "\226\144\135", ["\008"] = "\226\144\136",
|
|
["\009"] = "\226\144\137", ["\010"] = "\226\144\138", ["\011"] = "\226\144\139",
|
|
["\012"] = "\226\144\140", ["\013"] = "\226\144\141", ["\014"] = "\226\144\142",
|
|
["\015"] = "\226\144\143", ["\016"] = "\226\144\144", ["\017"] = "\226\144\145",
|
|
["\018"] = "\226\144\146", ["\019"] = "\226\144\147", ["\020"] = "\226\144\148",
|
|
["\021"] = "\226\144\149", ["\022"] = "\226\144\150", ["\023"] = "\226\144\151",
|
|
["\024"] = "\226\144\152", ["\025"] = "\226\144\153", ["\026"] = "\226\144\154",
|
|
["\027"] = "\226\144\155", ["\028"] = "\226\144\156", ["\029"] = "\226\144\157",
|
|
["\030"] = "\226\144\158", ["\031"] = "\226\144\159", ["\127"] = "\226\144\161",
|
|
};
|
|
local supports_p = pcall(string.format, "%p", ""); -- >= Lua 5.4
|
|
|
|
local function format(formatstring, ...)
|
|
local args = pack(...);
|
|
local args_length = args.n;
|
|
|
|
-- format specifier spec:
|
|
-- 1. Start: '%%'
|
|
-- 2. Flags: '[%-%+ #0]'
|
|
-- 3. Width: '%d?%d?'
|
|
-- 4. Precision: '%.?%d?%d?'
|
|
-- 5. Option: '[cdiouxXaAeEfgGqs%%]'
|
|
--
|
|
-- The options c, d, E, e, f, g, G, i, o, u, X, and x all expect a number as argument, whereas q and s expect a string.
|
|
-- This function does not accept string values containing embedded zeros, except as arguments to the q option.
|
|
-- a and A are only in Lua 5.2+
|
|
-- Lua 5.4 adds a p format that produces a pointer
|
|
|
|
|
|
-- process each format specifier
|
|
local i = 0;
|
|
formatstring = formatstring:gsub("%%[^cdiouxXaAeEfgGpqs%%]*[cdiouxXaAeEfgGpqs%%]", function(spec)
|
|
if spec == "%%" then return end
|
|
i = i + 1;
|
|
local arg = args[i];
|
|
|
|
if arg == nil then
|
|
args[i] = "nil";
|
|
return "(%s)";
|
|
end
|
|
|
|
local option = spec:sub(-1);
|
|
local t = type(arg);
|
|
|
|
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
|
|
elseif t == "number" then
|
|
if option == "g" or (option == "d" and num_type(arg) == "integer") then return end
|
|
elseif option == "s" and t ~= "string" then
|
|
arg = tostring(arg);
|
|
t = "string";
|
|
end
|
|
|
|
if option ~= "s" and option ~= "q" and option ~= "p" then
|
|
-- all other options expect numbers
|
|
if t ~= "number" then
|
|
-- arg isn't number as expected?
|
|
arg = tostring(arg);
|
|
option = "s";
|
|
spec = "[%s]";
|
|
t = "string";
|
|
elseif expects_integer[option] and num_type(arg) ~= "integer" then
|
|
args[i] = tostring(arg);
|
|
return "[%s]";
|
|
elseif expects_positive[option] and arg < 0 then
|
|
args[i] = tostring(arg);
|
|
return "[%s]";
|
|
else
|
|
return -- acceptable number
|
|
end
|
|
end
|
|
|
|
|
|
if option == "p" and not supports_p then
|
|
arg = tostring(arg);
|
|
option = "s";
|
|
spec = "[%s]";
|
|
t = "string";
|
|
end
|
|
|
|
if t == "string" and option ~= "p" then
|
|
if not valid_utf8(arg) then
|
|
option = "q";
|
|
elseif option ~= "q" then -- gets fully escaped in the next block
|
|
-- Prevent funny things with ASCII control characters and ANSI escape codes (CWE-117)
|
|
-- Also ensure embedded newlines can't look like another log line (CWE-93)
|
|
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
|
|
|
|
if option == "p" and (t == "boolean" or t == "number") then
|
|
args[i] = tostring(arg);
|
|
return "[%s]";
|
|
end
|
|
end);
|
|
|
|
-- process extra args
|
|
while i < args_length do
|
|
i = i + 1;
|
|
local arg = args[i];
|
|
if arg == nil then
|
|
args[i] = "(nil)";
|
|
else
|
|
args[i] = tostring(arg):gsub("[%z\1-\8\11-\31\127]", control_symbols):gsub("\n\t?", "\n\t");
|
|
end
|
|
formatstring = formatstring .. " [%s]"
|
|
end
|
|
|
|
return formatstring:format(unpack(args));
|
|
end
|
|
|
|
return {
|
|
format = format;
|
|
};
|