Merge pull request #737 from zielmicha/quote-shell

quoteIfContainsWhite -> quoteShell
This commit is contained in:
Andreas Rumpf 2013-12-16 12:32:14 -08:00
commit 20c69dac9e
6 changed files with 96 additions and 34 deletions

View file

@ -463,9 +463,9 @@ proc getCompileOptions: string =
proc getLinkOptions: string = proc getLinkOptions: string =
result = linkOptions result = linkOptions
for linkedLib in items(cLinkedLibs): for linkedLib in items(cLinkedLibs):
result.add(cc[ccompiler].linkLibCmd % linkedLib.quoteIfContainsWhite) result.add(cc[ccompiler].linkLibCmd % linkedLib.quoteShell)
for libDir in items(cLibs): for libDir in items(cLibs):
result.add([cc[ccompiler].linkDirCmd, libDir.quoteIfContainsWhite]) result.add([cc[ccompiler].linkDirCmd, libDir.quoteShell])
proc needsExeExt(): bool {.inline.} = proc needsExeExt(): bool {.inline.} =
result = (optGenScript in gGlobalOptions and targetOS == osWindows) or result = (optGenScript in gGlobalOptions and targetOS == osWindows) or
@ -485,10 +485,10 @@ proc getCompileCFileCmd*(cfilename: string, isExternal = false): string =
var includeCmd, compilePattern: string var includeCmd, compilePattern: string
if not noAbsolutePaths(): if not noAbsolutePaths():
# compute include paths: # compute include paths:
includeCmd = cc[c].includeCmd & quoteIfContainsWhite(libpath) includeCmd = cc[c].includeCmd & quoteShell(libpath)
for includeDir in items(cIncludes): for includeDir in items(cIncludes):
includeCmd.add([cc[c].includeCmd, includeDir.quoteIfContainsWhite]) includeCmd.add([cc[c].includeCmd, includeDir.quoteShell])
compilePattern = JoinPath(ccompilerpath, exe) compilePattern = JoinPath(ccompilerpath, exe)
else: else:
@ -501,17 +501,17 @@ proc getCompileCFileCmd*(cfilename: string, isExternal = false): string =
toObjFile(cfile) toObjFile(cfile)
else: else:
completeCFilePath(toObjFile(cfile)) completeCFilePath(toObjFile(cfile))
cfile = quoteIfContainsWhite(AddFileExt(cfile, cExt)) cfile = quoteShell(AddFileExt(cfile, cExt))
objfile = quoteIfContainsWhite(objfile) objfile = quoteShell(objfile)
result = quoteIfContainsWhite(compilePattern % [ result = quoteShell(compilePattern % [
"file", cfile, "objfile", objfile, "options", options, "file", cfile, "objfile", objfile, "options", options,
"include", includeCmd, "nimrod", getPrefixDir(), "lib", libpath]) "include", includeCmd, "nimrod", getPrefixDir(), "lib", libpath])
add(result, ' ') add(result, ' ')
addf(result, cc[c].compileTmpl, [ addf(result, cc[c].compileTmpl, [
"file", cfile, "objfile", objfile, "file", cfile, "objfile", objfile,
"options", options, "include", includeCmd, "options", options, "include", includeCmd,
"nimrod", quoteIfContainsWhite(getPrefixDir()), "nimrod", quoteShell(getPrefixDir()),
"lib", quoteIfContainsWhite(libpath)]) "lib", quoteShell(libpath)])
proc footprint(filename: string): TCrc32 = proc footprint(filename: string): TCrc32 =
result = crcFromFile(filename) >< result = crcFromFile(filename) ><
@ -590,7 +590,7 @@ proc CallCCompiler*(projectfile: string) =
while it != nil: while it != nil:
let objFile = if noAbsolutePaths(): it.data.extractFilename else: it.data let objFile = if noAbsolutePaths(): it.data.extractFilename else: it.data
add(objfiles, ' ') add(objfiles, ' ')
add(objfiles, quoteIfContainsWhite( add(objfiles, quoteShell(
addFileExt(objFile, cc[ccompiler].objExt))) addFileExt(objFile, cc[ccompiler].objExt)))
it = PStrEntry(it.next) it = PStrEntry(it.next)
@ -602,8 +602,8 @@ proc CallCCompiler*(projectfile: string) =
var linkerExe = getConfigVar(c, ".linkerexe") var linkerExe = getConfigVar(c, ".linkerexe")
if len(linkerExe) == 0: linkerExe = cc[c].linkerExe if len(linkerExe) == 0: linkerExe = cc[c].linkerExe
if needsExeExt(): linkerExe = addFileExt(linkerExe, "exe") if needsExeExt(): linkerExe = addFileExt(linkerExe, "exe")
if noAbsolutePaths(): linkCmd = quoteIfContainsWhite(linkerExe) if noAbsolutePaths(): linkCmd = quoteShell(linkerExe)
else: linkCmd = quoteIfContainsWhite(JoinPath(ccompilerpath, linkerExe)) else: linkCmd = quoteShell(JoinPath(ccompilerpath, linkerExe))
if optGenGuiApp in gGlobalOptions: buildGui = cc[c].buildGui if optGenGuiApp in gGlobalOptions: buildGui = cc[c].buildGui
else: buildGui = "" else: buildGui = ""
var exefile: string var exefile: string
@ -617,17 +617,17 @@ proc CallCCompiler*(projectfile: string) =
exefile = options.outFile exefile = options.outFile
if not noAbsolutePaths(): if not noAbsolutePaths():
exefile = joinPath(splitFile(projectFile).dir, exefile) exefile = joinPath(splitFile(projectFile).dir, exefile)
exefile = quoteIfContainsWhite(exefile) exefile = quoteShell(exefile)
let linkOptions = getLinkOptions() let linkOptions = getLinkOptions()
linkCmd = quoteIfContainsWhite(linkCmd % ["builddll", builddll, linkCmd = quoteShell(linkCmd % ["builddll", builddll,
"buildgui", buildgui, "options", linkOptions, "objfiles", objfiles, "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles,
"exefile", exefile, "nimrod", getPrefixDir(), "lib", libpath]) "exefile", exefile, "nimrod", getPrefixDir(), "lib", libpath])
linkCmd.add ' ' linkCmd.add ' '
addf(linkCmd, cc[c].linkTmpl, ["builddll", builddll, addf(linkCmd, cc[c].linkTmpl, ["builddll", builddll,
"buildgui", buildgui, "options", linkOptions, "buildgui", buildgui, "options", linkOptions,
"objfiles", objfiles, "exefile", exefile, "objfiles", objfiles, "exefile", exefile,
"nimrod", quoteIfContainsWhite(getPrefixDir()), "nimrod", quoteShell(getPrefixDir()),
"lib", quoteIfContainsWhite(libpath)]) "lib", quoteShell(libpath)])
if optCompileOnly notin gGlobalOptions: execExternalProgram(linkCmd) if optCompileOnly notin gGlobalOptions: execExternalProgram(linkCmd)
else: else:
linkCmd = "" linkCmd = ""

View file

@ -13,9 +13,9 @@ when defined(gcc) and defined(windows):
else: else:
{.link: "icons/nimrod_icon.o".} {.link: "icons/nimrod_icon.o".}
import import
commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes, commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes,
extccomp, strutils, os, platform, main, parseopt, service extccomp, strutils, os, osproc, platform, main, parseopt, service
when hasTinyCBackend: when hasTinyCBackend:
import tccgen import tccgen
@ -23,7 +23,7 @@ when hasTinyCBackend:
when defined(profiler) or defined(memProfiler): when defined(profiler) or defined(memProfiler):
{.hint: "Profiling support is turned on!".} {.hint: "Profiling support is turned on!".}
import nimprof import nimprof
proc prependCurDir(f: string): string = proc prependCurDir(f: string): string =
when defined(unix): when defined(unix):
if os.isAbsolute(f): result = f if os.isAbsolute(f): result = f
@ -61,11 +61,11 @@ proc HandleCmdLine() =
tccgen.run() tccgen.run()
if optRun in gGlobalOptions: if optRun in gGlobalOptions:
if gCmd == cmdCompileToJS: if gCmd == cmdCompileToJS:
var ex = quoteIfContainsWhite( var ex = quoteShell(
completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir)) completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir))
execExternalProgram("node " & ex & ' ' & service.arguments) execExternalProgram("node " & ex & ' ' & service.arguments)
else: else:
var ex = quoteIfContainsWhite( var ex = quoteShell(
changeFileExt(gProjectFull, exeExt).prependCurDir) changeFileExt(gProjectFull, exeExt).prependCurDir)
execExternalProgram(ex & ' ' & service.arguments) execExternalProgram(ex & ' ' & service.arguments)

View file

@ -33,10 +33,10 @@ proc openDefaultBrowser*(url: string) =
else: else:
discard ShellExecuteA(0'i32, "open", url, nil, nil, SW_SHOWNORMAL) discard ShellExecuteA(0'i32, "open", url, nil, nil, SW_SHOWNORMAL)
elif defined(macosx): elif defined(macosx):
discard execShellCmd("open " & quoteIfContainsWhite(url)) discard execShellCmd("open " & quoteShell(url))
else: else:
const attempts = ["gnome-open ", "kde-open ", "xdg-open "] const attempts = ["gnome-open ", "kde-open ", "xdg-open "]
var u = quoteIfContainsWhite(url) var u = quoteShell(url)
for a in items(attempts): for a in items(attempts):
if execShellCmd(a & u) == 0: return if execShellCmd(a & u) == 0: return
for b in getEnv("BROWSER").string.split(PathSep): for b in getEnv("BROWSER").string.split(PathSep):

View file

@ -41,6 +41,58 @@ type
poStdErrToStdOut, ## merge stdout and stderr to the stdout stream poStdErrToStdOut, ## merge stdout and stderr to the stdout stream
poParentStreams ## use the parent's streams poParentStreams ## use the parent's streams
proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
## Quote s, so it can be safely passed to Windows API.
## Based on Python's subprocess.list2cmdline
## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
let needQuote = {' ', '\t'} in s or s.len == 0
result = ""
var backslashBuff = ""
if needQuote:
result.add("\"")
for c in s:
if c == '\\':
backslashBuff.add(c)
elif c == '\"':
result.add(backslashBuff)
result.add(backslashBuff)
backslashBuff.setLen(0)
result.add("\\\"")
else:
if backslashBuff.len != 0:
result.add(backslashBuff)
backslashBuff.setLen(0)
result.add(c)
if needQuote:
result.add("\"")
proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
## Quote s, so it can be safely passed to POSIX shell.
## Based on Python's pipes.quote
const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@',
'0'..'9', 'A'..'Z', 'a'..'z'}
if s.len == 0:
return "''"
let safe = s.allCharsInSet(safeUnixChars)
if safe:
return s
else:
return "'" & s.replace("'", "'\"'\"'") & "'"
proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
## Quote s, so it can be safely passed to shell.
when defined(Windows):
return quoteShellWindows(s)
elif defined(posix):
return quoteShellPosix(s)
else:
{.error:"quoteShell is not supported on your system".}
proc execProcess*(command: string, proc execProcess*(command: string,
options: set[TProcessOption] = {poStdErrToStdOut, options: set[TProcessOption] = {poStdErrToStdOut,
poUseShell}): TaintedString {. poUseShell}): TaintedString {.
@ -307,10 +359,10 @@ when defined(Windows) and not defined(useNimRtl):
result.writeDataImpl = hsWriteData result.writeDataImpl = hsWriteData
proc buildCommandLine(a: string, args: openarray[string]): cstring = proc buildCommandLine(a: string, args: openarray[string]): cstring =
var res = quoteIfContainsWhite(a) var res = quoteShell(a)
for i in 0..high(args): for i in 0..high(args):
res.add(' ') res.add(' ')
res.add(quoteIfContainsWhite(args[i])) res.add(quoteShell(args[i]))
result = cast[cstring](alloc0(res.len+1)) result = cast[cstring](alloc0(res.len+1))
copyMem(result, cstring(res), res.len) copyMem(result, cstring(res), res.len)
@ -510,10 +562,10 @@ elif not defined(useNimRtl):
writeIdx = 1 writeIdx = 1
proc addCmdArgs(command: string, args: openarray[string]): string = proc addCmdArgs(command: string, args: openarray[string]): string =
result = quoteIfContainsWhite(command) result = quoteShell(command)
for i in 0 .. high(args): for i in 0 .. high(args):
add(result, " ") add(result, " ")
add(result, quoteIfContainsWhite(args[i])) add(result, quoteShell(args[i]))
proc toCStringArray(b, a: openarray[string]): cstringArray = proc toCStringArray(b, a: openarray[string]): cstringArray =
result = cast[cstringArray](alloc0((a.len + b.len + 1) * sizeof(cstring))) result = cast[cstringArray](alloc0((a.len + b.len + 1) * sizeof(cstring)))
@ -792,5 +844,14 @@ proc execCmdEx*(command: string, options: set[TProcessOption] = {
close(p) close(p)
when isMainModule: when isMainModule:
var x = execProcess("gcc -v") assert quoteShellWindows("aaa") == "aaa"
echo "ECHO ", x assert quoteShellWindows("aaa\"") == "aaa\\\""
assert quoteShellWindows("") == "\"\""
assert quoteShellPosix("aaa") == "aaa"
assert quoteShellPosix("aaa a") == "'aaa a'"
assert quoteShellPosix("") == "''"
assert quoteShellPosix("a'a") == "'a'\"'\"'a'"
when defined(posix):
assert quoteShell("") == "''"

View file

@ -709,9 +709,11 @@ proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} =
if result != -1: return if result != -1: return
return -1 return -1
proc quoteIfContainsWhite*(s: string): string = proc quoteIfContainsWhite*(s: string): string {.deprecated.} =
## returns ``'"' & s & '"'`` if `s` contains a space and does not ## returns ``'"' & s & '"'`` if `s` contains a space and does not
## start with a quote, else returns `s` ## start with a quote, else returns `s`
## DEPRECATED as it was confused for shell quoting function.
## For this application use osproc.quoteShell.
if find(s, {' ', '\t'}) >= 0 and s[0] != '"': if find(s, {' ', '\t'}) >= 0 and s[0] != '"':
result = '"' & s & '"' result = '"' & s & '"'
else: else:

View file

@ -14,7 +14,7 @@ when haveZipLib:
import zipfiles import zipfiles
import import
os, strutils, parseopt, parsecfg, strtabs, streams, debcreation os, osproc, strutils, parseopt, parsecfg, strtabs, streams, debcreation
const const
maxOS = 20 # max number of OSes maxOS = 20 # max number of OSes
@ -486,7 +486,7 @@ proc setupDist(c: var TConfigData) =
if c.innoSetup.path.len == 0: if c.innoSetup.path.len == 0:
c.innoSetup.path = "iscc.exe" c.innoSetup.path = "iscc.exe"
var outcmd = if c.outdir.len == 0: "build" else: c.outdir var outcmd = if c.outdir.len == 0: "build" else: c.outdir
var cmd = "$# $# /O$# $#" % [quoteIfContainsWhite(c.innoSetup.path), var cmd = "$# $# /O$# $#" % [quoteShell(c.innoSetup.path),
c.innoSetup.flags, outcmd, n] c.innoSetup.flags, outcmd, n]
echo(cmd) echo(cmd)
if execShellCmd(cmd) == 0: if execShellCmd(cmd) == 0:
@ -587,4 +587,3 @@ if actionZip in c.actions:
quit("libzip is not installed") quit("libzip is not installed")
if actionDeb in c.actions: if actionDeb in c.actions:
debDist(c) debDist(c)