escapeshellarg improvements
Closed this issue · 2 comments
escapeshellarg (and so shelljoin) does not handle some cases. At my understanding these functions aim to produce strings that can be passed to os.execute bypassing all the shell pre-parsing. If this is the intent, the following example gives a wrong output:
print(require 'shelljoin' {' a\\"',' b\\', 'c $PATH'})
Indeed:
- '\' should be escaped to avoid unwanted escapes
- '$' should be escaped to avoid variable expansion
Moreover these functions work only with a posix shell. I think at least windows should be supported. Maybe you could use something like:
local function posix_quote_argument(str)
-- if not str:match("'") then
-- return "'" .. str .. "'"
-- end
str = str:gsub("[$`\"\\]", "\\%1")
return '"' .. str .. '"'
end
local function windows_quote_argument(str)
-- str = str:gsub('["\\]', '\\%1')
-- return '"'..str..'"'
str = str:gsub('[%%&\\^<>|]', '^%1')
str = str:gsub('"', "\\%1")
str = str:gsub('[ \t][ \t]*', '"%1"')
return str
end
local function detect_the_shell()
local shell = os.getenv('SHELL')
if shell then
if '/' == shell:sub(1,1) and 'sh' == shell:sub(-2, -1) then
return posix_quote_argument
end
end
return windows_quote_argument
end
(actually this came from some my old code that was also tested a bit). I did not a comit+pull request since I do not know if this is the desired behaviour.
At my understanding these functions aim to produce strings that can be passed to os.execute bypassing all the shell pre-parsing.
Thats right.
But does shelljoin not escape '' correct?
Moreover these functions work only with a posix shell.
I use OSX and Ubuntu and had no need for Windows Support. But you are right it would improve the function.
You can extend the existing tests with the problem cases and the expected output. I can add this function or do it yourself.
I can update the function by myself for sure. However I was guessing if it was better to actually involve a shell itself for the tests. Something like:
local escapeshellarg = require( "escapeshellarg" )
local t = require( "taptest" )
local function shcompare( got, expected )
local w = require( "writefile" )
local r = require( "readfile" )
w( 'tmp.lua', [[
for i=1,#arg do
require'writefile'('tmp'..i..'.txt',arg[i])
end
]] )
local i, luacmd = 0, 'lua'
while arg[i] do luacmd = arg[i] ; i = i -1 end
os.execute ( luacmd .. [[ tmp.lua ]] .. got .. ' ' )
return r( 'tmp1.txt' ) == expected
end
t( escapeshellarg( [[Hello's world]] ), [[Hello's world]], shcompare)
t( escapeshellarg( [[use a " to mark]] ), [[use a " to mark]], shcompare )
t( escapeshellarg( [[should escape \]] ), [[should escape \]], shcompare )
t( escapeshellarg( [[special $PATH]] ), [[special $PATH]], shcompare )
t( escapeshellarg( [[special %PATH%]] ), [[special %PATH%]], shcompare )
t( escapeshellarg( [[redirect>o.txt]] ), [[redirect>o.txt]], shcompare )
t( escapeshellarg( [[redirect<i.txt]] ), [[redirect<i.txt]], shcompare )
t( escapeshellarg( [[pipe|extcmd]] ), [[pipe|extcmd]], shcompare )
t()
Note: I can not test right now however I thing the current code will failing the '\' and '$' tests in the example.