summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/extccomp.nim32
-rw-r--r--compiler/nimrod.nim12
-rw-r--r--lib/pure/browsers.nim4
-rw-r--r--lib/pure/osproc.nim73
-rw-r--r--lib/pure/strutils.nim4
-rw-r--r--tools/niminst/niminst.nim5
6 files changed, 96 insertions, 34 deletions
diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim
index 13eb972f6..fe1bea3ff 100644
--- a/compiler/extccomp.nim
+++ b/compiler/extccomp.nim
@@ -463,9 +463,9 @@ proc getCompileOptions: string =
 proc getLinkOptions: string =
   result = linkOptions
   for linkedLib in items(cLinkedLibs):
-    result.add(cc[ccompiler].linkLibCmd % linkedLib.quoteIfContainsWhite)
+    result.add(cc[ccompiler].linkLibCmd % linkedLib.quoteShell)
   for libDir in items(cLibs):
-    result.add([cc[ccompiler].linkDirCmd, libDir.quoteIfContainsWhite])
+    result.add([cc[ccompiler].linkDirCmd, libDir.quoteShell])
 
 proc needsExeExt(): bool {.inline.} =
   result = (optGenScript in gGlobalOptions and targetOS == osWindows) or
@@ -485,10 +485,10 @@ proc getCompileCFileCmd*(cfilename: string, isExternal = false): string =
   var includeCmd, compilePattern: string
   if not noAbsolutePaths(): 
     # compute include paths:
-    includeCmd = cc[c].includeCmd & quoteIfContainsWhite(libpath)
+    includeCmd = cc[c].includeCmd & quoteShell(libpath)
 
     for includeDir in items(cIncludes):
-      includeCmd.add([cc[c].includeCmd, includeDir.quoteIfContainsWhite])
+      includeCmd.add([cc[c].includeCmd, includeDir.quoteShell])
 
     compilePattern = JoinPath(ccompilerpath, exe)
   else: 
@@ -501,17 +501,17 @@ proc getCompileCFileCmd*(cfilename: string, isExternal = false): string =
                   toObjFile(cfile) 
                 else: 
                   completeCFilePath(toObjFile(cfile))
-  cfile = quoteIfContainsWhite(AddFileExt(cfile, cExt))
-  objfile = quoteIfContainsWhite(objfile)
-  result = quoteIfContainsWhite(compilePattern % [
+  cfile = quoteShell(AddFileExt(cfile, cExt))
+  objfile = quoteShell(objfile)
+  result = quoteShell(compilePattern % [
     "file", cfile, "objfile", objfile, "options", options, 
     "include", includeCmd, "nimrod", getPrefixDir(), "lib", libpath])
   add(result, ' ')
   addf(result, cc[c].compileTmpl, [
     "file", cfile, "objfile", objfile, 
     "options", options, "include", includeCmd, 
-    "nimrod", quoteIfContainsWhite(getPrefixDir()), 
-    "lib", quoteIfContainsWhite(libpath)])
+    "nimrod", quoteShell(getPrefixDir()), 
+    "lib", quoteShell(libpath)])
 
 proc footprint(filename: string): TCrc32 =
   result = crcFromFile(filename) ><
@@ -590,7 +590,7 @@ proc CallCCompiler*(projectfile: string) =
     while it != nil:
       let objFile = if noAbsolutePaths(): it.data.extractFilename else: it.data
       add(objfiles, ' ')
-      add(objfiles, quoteIfContainsWhite(
+      add(objfiles, quoteShell(
           addFileExt(objFile, cc[ccompiler].objExt)))
       it = PStrEntry(it.next)
 
@@ -602,8 +602,8 @@ proc CallCCompiler*(projectfile: string) =
       var linkerExe = getConfigVar(c, ".linkerexe")
       if len(linkerExe) == 0: linkerExe = cc[c].linkerExe
       if needsExeExt(): linkerExe = addFileExt(linkerExe, "exe")
-      if noAbsolutePaths(): linkCmd = quoteIfContainsWhite(linkerExe)
-      else: linkCmd = quoteIfContainsWhite(JoinPath(ccompilerpath, linkerExe))
+      if noAbsolutePaths(): linkCmd = quoteShell(linkerExe)
+      else: linkCmd = quoteShell(JoinPath(ccompilerpath, linkerExe))
       if optGenGuiApp in gGlobalOptions: buildGui = cc[c].buildGui
       else: buildGui = ""
       var exefile: string
@@ -617,17 +617,17 @@ proc CallCCompiler*(projectfile: string) =
         exefile = options.outFile
       if not noAbsolutePaths():
         exefile = joinPath(splitFile(projectFile).dir, exefile)
-      exefile = quoteIfContainsWhite(exefile)
+      exefile = quoteShell(exefile)
       let linkOptions = getLinkOptions()
-      linkCmd = quoteIfContainsWhite(linkCmd % ["builddll", builddll,
+      linkCmd = quoteShell(linkCmd % ["builddll", builddll,
           "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles,
           "exefile", exefile, "nimrod", getPrefixDir(), "lib", libpath])
       linkCmd.add ' '
       addf(linkCmd, cc[c].linkTmpl, ["builddll", builddll,
           "buildgui", buildgui, "options", linkOptions,
           "objfiles", objfiles, "exefile", exefile,
-          "nimrod", quoteIfContainsWhite(getPrefixDir()),
-          "lib", quoteIfContainsWhite(libpath)])
+          "nimrod", quoteShell(getPrefixDir()),
+          "lib", quoteShell(libpath)])
       if optCompileOnly notin gGlobalOptions: execExternalProgram(linkCmd)
   else:
     linkCmd = ""
diff --git a/compiler/nimrod.nim b/compiler/nimrod.nim
index 2f10e32e3..8e3c0e61e 100644
--- a/compiler/nimrod.nim
+++ b/compiler/nimrod.nim
@@ -13,9 +13,9 @@ when defined(gcc) and defined(windows):
   else:
     {.link: "icons/nimrod_icon.o".}
 
-import 
-  commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes, 
-  extccomp, strutils, os, platform, main, parseopt, service
+import
+  commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes,
+  extccomp, strutils, os, osproc, platform, main, parseopt, service
 
 when hasTinyCBackend:
   import tccgen
@@ -23,7 +23,7 @@ when hasTinyCBackend:
 when defined(profiler) or defined(memProfiler):
   {.hint: "Profiling support is turned on!".}
   import nimprof
-  
+
 proc prependCurDir(f: string): string =
   when defined(unix):
     if os.isAbsolute(f): result = f
@@ -61,11 +61,11 @@ proc HandleCmdLine() =
           tccgen.run()
       if optRun in gGlobalOptions:
         if gCmd == cmdCompileToJS:
-          var ex = quoteIfContainsWhite(
+          var ex = quoteShell(
             completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir))
           execExternalProgram("node " & ex & ' ' & service.arguments)
         else:
-          var ex = quoteIfContainsWhite(
+          var ex = quoteShell(
             changeFileExt(gProjectFull, exeExt).prependCurDir)
           execExternalProgram(ex & ' ' & service.arguments)
 
diff --git a/lib/pure/browsers.nim b/lib/pure/browsers.nim
index 6f5bf7ddb..b44f406c5 100644
--- a/lib/pure/browsers.nim
+++ b/lib/pure/browsers.nim
@@ -33,10 +33,10 @@ proc openDefaultBrowser*(url: string) =
     else:
       discard ShellExecuteA(0'i32, "open", url, nil, nil, SW_SHOWNORMAL)
   elif defined(macosx):
-    discard execShellCmd("open " & quoteIfContainsWhite(url))
+    discard execShellCmd("open " & quoteShell(url))
   else:
     const attempts = ["gnome-open ", "kde-open ", "xdg-open "]
-    var u = quoteIfContainsWhite(url)
+    var u = quoteShell(url)
     for a in items(attempts):
       if execShellCmd(a & u) == 0: return
     for b in getEnv("BROWSER").string.split(PathSep):
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index 754e34b85..61b940ce8 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -41,6 +41,58 @@ type
     poStdErrToStdOut,    ## merge stdout and stderr to the stdout stream
     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,
                   options: set[TProcessOption] = {poStdErrToStdOut,
                                                   poUseShell}): TaintedString {.
@@ -307,10 +359,10 @@ when defined(Windows) and not defined(useNimRtl):
     result.writeDataImpl = hsWriteData
 
   proc buildCommandLine(a: string, args: openarray[string]): cstring =
-    var res = quoteIfContainsWhite(a)
+    var res = quoteShell(a)
     for i in 0..high(args):
       res.add(' ')
-      res.add(quoteIfContainsWhite(args[i]))
+      res.add(quoteShell(args[i]))
     result = cast[cstring](alloc0(res.len+1))
     copyMem(result, cstring(res), res.len)
 
@@ -510,10 +562,10 @@ elif not defined(useNimRtl):
     writeIdx = 1
 
   proc addCmdArgs(command: string, args: openarray[string]): string =
-    result = quoteIfContainsWhite(command)
+    result = quoteShell(command)
     for i in 0 .. high(args):
       add(result, " ")
-      add(result, quoteIfContainsWhite(args[i]))
+      add(result, quoteShell(args[i]))
 
   proc toCStringArray(b, a: openarray[string]): cstringArray =
     result = cast[cstringArray](alloc0((a.len + b.len + 1) * sizeof(cstring)))
@@ -792,5 +844,14 @@ proc execCmdEx*(command: string, options: set[TProcessOption] = {
   close(p)
 
 when isMainModule:
-  var x = execProcess("gcc -v")
-  echo "ECHO ", x
+  assert quoteShellWindows("aaa") == "aaa"
+  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("") == "''"
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index a4aa81578..fe71cb77b 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -709,9 +709,11 @@ proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} =
     if result != -1: return
   return -1
 

-proc quoteIfContainsWhite*(s: string): string =

+proc quoteIfContainsWhite*(s: string): string {.deprecated.} =
   ## returns ``'"' & s & '"'`` if `s` contains a space and does not

   ## 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] != '"':

     result = '"' & s & '"'

   else:

diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim
index 25ec0d283..0c9717e12 100644
--- a/tools/niminst/niminst.nim
+++ b/tools/niminst/niminst.nim
@@ -14,7 +14,7 @@ when haveZipLib:
   import zipfiles
 
 import
-  os, strutils, parseopt, parsecfg, strtabs, streams, debcreation
+  os, osproc, strutils, parseopt, parsecfg, strtabs, streams, debcreation
 
 const
   maxOS = 20 # max number of OSes
@@ -486,7 +486,7 @@ proc setupDist(c: var TConfigData) =
     if c.innoSetup.path.len == 0:
       c.innoSetup.path = "iscc.exe"
     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]
     echo(cmd)
     if execShellCmd(cmd) == 0:
@@ -587,4 +587,3 @@ if actionZip in c.actions:
     quit("libzip is not installed")
 if actionDeb in c.actions:
   debDist(c)
-