1

I want to execute an external program in lua. Usually this can be done with

os.execute("run '"..arg0.."' 'arg1' arg2")

The problem with this approach is if I want to pass user input as string to an external program, user input could be '; evil 'h4ck teh system' ' and the script from above would execute like this:

/bin/bash -c "run ''; evil 'h4ck teh system' '' 'arg1' arg2"

Another problem occurs when I have '$var' as argument and the shell replaces this with its environment variable. In my particular case I have something like [[program 'set title "$My Title$"']] – so nested strings – and program parses "$My Title$" (with escape sequences) differently than '$My Title$' (as it is). Because I want to set the title as it, the best way is to have arguments like this: 'My Title'. But now the command have to be:

os.execute([[run "set title '$My Title$'"]])

But now – as I said – $My will be replaced with an empty string, because the environment does not know any variable named $My and because, I never wanted it to be replaced.

So I am looking for the usual approach with

execv("run", {"set title '"..arg0.."'", arg1, arg2})
sivizius
  • 450
  • 2
  • 14

1 Answers1

2
local safe_unquoted = "^[-~_/.%w%%+,:@^]*$"
local function q(text, expand)   -- quoting under *nix shells
   -- "expand"
   --    false/nil: $var and `cmd` must NOT be expanded (use single quotes)
   --    true:      $var and `cmd` must be expanded (use double quotes)
   if text == "" then
      text = '""'
   elseif not text:match(safe_unquoted) then
      if expand then
         text = '"'..text:gsub('["\\]', '\\%0')..'"'
      else
         local new_text = {}
         for s in (text.."'"):gmatch"(.-)'" do
            new_text[#new_text + 1] = s:match(safe_unquoted) or "'"..s.."'"
         end
         text = table.concat(new_text, "\\'")
      end
   end
   return text
end

function execute_commands(...)
   local all_commands = {}
   for k, command in ipairs{...} do
      for j = 1, #command do
         if not command[j]:match"^[-~_%w/%.]+$" then
            command[j] = q(command[j], command.expand)
         end
      end
      all_commands[k] = table.concat(command, " ") -- space is arguments delimiter
   end
   all_commands = table.concat(all_commands, ";")  -- semicolon is commands delimiter
   return os.execute("/bin/bash -c "..q(all_commands))
end

Usage examples:

-- Usage example #1:
execute_commands(
   {"your/program", "arg 1", "$arg2", "arg-3", "~/arg4.txt"},
   {expand=true, "echo", "Your program finished with exit code $?"},
   {"ls", "-l"}
)
-- The following command will be executed:
-- /bin/bash -c 'your/program '\''arg 1'\'' '\''$arg2'\'' arg-3 ~/arg4.txt;echo "Your program finished with exit code $?";ls -l'

$arg2 will NOT be expanded into value because of single quotes around it, as you required.
Unfortunately, "Your program finished with exit code $?" will NOT be expanded too (unless you explicitly set expand=true).

-- Usage example #2:
execute_commands{"run", "set title '$My Title$'", "arg1", "arg2"}
-- the generated command is not trivial, but it does exactly what you need :-)
-- /bin/bash -c 'run '\''set title '\''\'\'\''$My Title$'\''\'\'' arg1 arg2'
sivizius
  • 450
  • 2
  • 14
Egor Skriptunoff
  • 23,359
  • 2
  • 34
  • 64
  • e.g. `execute_commands({"echo", [[set my '$program$'; cd "finished";]]})` will execute `/bin/bash 'echo '\\''set my '\\''\\'\\'''\\''$program$'\\''\\'\\'''\\''; cd "finished";'\\'''-c `which result in this error: `set sh: 1: cd: can't cd to finished sh: 1: \\: not found`; it was never intended to execute cd – sivizius Nov 11 '18 at 11:52
  • @sivizius - How a text appears inserted between `/bin/bash` and `-c` ? My code can't produce such output. – Egor Skriptunoff Nov 11 '18 at 12:23
  • just copy-paste fail, the `-c` is indeed after `/bin/bash`. and this was with the old version of your code – sivizius Nov 11 '18 at 12:25
  • This solution works, but I do not like it. I am used to have a real argument list. This solution adds some layers just to removed again by bash later. I still have some security concerns with this. But thank you, at least, this helps somehow. – sivizius Nov 11 '18 at 12:47
  • 1
    @sivizius - Can you give an example of "security concerns" with this solution? – Egor Skriptunoff Nov 12 '18 at 07:21