local output_directory, private_output_directory = ...
local execute, popen = os.execute, io.popen
local find, gsub, sub = string.find, string.gsub, string.sub
local function mode()
return status.shell_escape
end
local function words(cmd)
local out, cur, i, n, inword = {}, {}, 1, #cmd, false
while i <= n do
local c = sub(cmd, i, i)
if c == "'" or c == '"' then
local j = find(cmd, c, i + 1, true) or (n + 1)
cur[#cur + 1] = sub(cmd, i + 1, j - 1)
inword, i = true, j + 1
elseif find(c, "^%s$") then
if inword then out[#out + 1] = table.concat(cur); cur, inword = {}, false end
i = i + 1
else
cur[#cur + 1] = c
inword, i = true, i + 1
end
end
if inword then out[#out + 1] = table.concat(cur) end
return out
end
local function allowed(name)
local list = kpse.var_value("shell_escape_commands") or ""
for c in list:gmatch("[^,%s]+") do
if c == name then return true end
end
return false
end
local function restricted(cmd)
if find(cmd, "'", 1, true) then return nil, "quote" end
local w = words(cmd)
if #w == 0 or not allowed(w[1]) then return nil end
for i = 1, #w do w[i] = "'" .. gsub(w[i], "'", "'\\''") .. "'" end
return table.concat(w, " ")
end
local env_set, getenv = {}, os.getenv
function os.setenv(key, value)
env_set[key] = value ~= nil and tostring(value) or false
return nil
end
function os.getenv(key)
local v = env_set[key]
if v ~= nil then return v or nil end
if key == "TEXMF_OUTPUT_DIRECTORY" then
local dir = output_directory()
if dir then return dir end
end
return getenv(key)
end
local aux_tools = {
makeindex = true, mendex = true, upmendex = true, xindy = true, texindy = true,
bibtex = true, bibtex8 = true, bibtexu = true, biber = true,
}
local function quote(s)
return "'" .. gsub(s, "'", "'\\''") .. "'"
end
local function with_output_directory(cmd)
local unset, prefix = {}, {}
for k, v in pairs(env_set) do
if find(k, "^[%a_][%w_]*$") then
if v then
prefix[#prefix + 1] = "export " .. k .. "=" .. quote(v) .. "; "
else
unset[#unset + 1] = "unset " .. k .. "; "
end
end
end
table.sort(unset)
table.sort(prefix)
prefix = { table.concat(unset), table.concat(prefix) }
local dir = output_directory()
if dir and env_set.TEXMF_OUTPUT_DIRECTORY == nil then
prefix[#prefix + 1] = "export TEXMF_OUTPUT_DIRECTORY=" .. quote(dir) .. "; "
end
local w = words(cmd)[1]
if dir and private_output_directory() and w and aux_tools[w] then
local cwd = getenv("PWD") or "."
for _, v in ipairs({ "INDEXSTYLE", "BIBINPUTS", "BSTINPUTS" }) do
prefix[#prefix + 1] = "export " .. v .. "=" .. quote(cwd .. ":") .. '"$' .. v .. '"; '
end
prefix[#prefix + 1] = "cd " .. quote(dir) .. " && "
end
return table.concat(prefix) .. cmd
end
local function wait_status(ok, how, code)
if how == "exit" then return code * 256 end
if how == "signal" then return code end
return ok and 0 or 256
end
function os.execute(cmd)
local m = mode()
if cmd == nil then return m end
if m == 0 then return nil, "All command execution disabled." end
if m == 2 then
local why
cmd, why = restricted(cmd)
if why then return nil, "Quoting error in system command line." end
if not cmd then return nil, "Command execution disabled via shell_escape='p'" end
end
return wait_status(execute(with_output_directory(cmd)))
end
local function do_popen(cmd, how, merge_stderr)
local m = mode()
if m == 0 then return nil, "all command execution is disabled" end
if m == 2 then
local why
cmd, why = restricted(cmd)
if why then return nil, "bad command line quoting" end
if not cmd then return nil, "specific command execution disabled" end
end
cmd = with_output_directory(cmd)
if merge_stderr then cmd = "{ " .. cmd .. "\n} 2>&1" end
return popen(cmd, how)
end
function io.popen(cmd, how)
return do_popen(cmd, how)
end
function os.retex_popen_all(cmd)
return do_popen(cmd, "r", true)
end