commands.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // Copyright 2015 Light Code Labs, LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package caddy
  15. import (
  16. "errors"
  17. "runtime"
  18. "unicode"
  19. "github.com/flynn/go-shlex"
  20. )
  21. var runtimeGoos = runtime.GOOS
  22. // SplitCommandAndArgs takes a command string and parses it shell-style into the
  23. // command and its separate arguments.
  24. func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
  25. var parts []string
  26. if runtimeGoos == "windows" {
  27. parts = parseWindowsCommand(command) // parse it Windows-style
  28. } else {
  29. parts, err = parseUnixCommand(command) // parse it Unix-style
  30. if err != nil {
  31. err = errors.New("error parsing command: " + err.Error())
  32. return
  33. }
  34. }
  35. if len(parts) == 0 {
  36. err = errors.New("no command contained in '" + command + "'")
  37. return
  38. }
  39. cmd = parts[0]
  40. if len(parts) > 1 {
  41. args = parts[1:]
  42. }
  43. return
  44. }
  45. // parseUnixCommand parses a unix style command line and returns the
  46. // command and its arguments or an error
  47. func parseUnixCommand(cmd string) ([]string, error) {
  48. return shlex.Split(cmd)
  49. }
  50. // parseWindowsCommand parses windows command lines and
  51. // returns the command and the arguments as an array. It
  52. // should be able to parse commonly used command lines.
  53. // Only basic syntax is supported:
  54. // - spaces in double quotes are not token delimiters
  55. // - double quotes are escaped by either backspace or another double quote
  56. // - except for the above case backspaces are path separators (not special)
  57. //
  58. // Many sources point out that escaping quotes using backslash can be unsafe.
  59. // Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 )
  60. //
  61. // This function has to be used on Windows instead
  62. // of the shlex package because this function treats backslash
  63. // characters properly.
  64. func parseWindowsCommand(cmd string) []string {
  65. const backslash = '\\'
  66. const quote = '"'
  67. var parts []string
  68. var part string
  69. var inQuotes bool
  70. var lastRune rune
  71. for i, ch := range cmd {
  72. if i != 0 {
  73. lastRune = rune(cmd[i-1])
  74. }
  75. if ch == backslash {
  76. // put it in the part - for now we don't know if it's an
  77. // escaping char or path separator
  78. part += string(ch)
  79. continue
  80. }
  81. if ch == quote {
  82. if lastRune == backslash {
  83. // remove the backslash from the part and add the escaped quote instead
  84. part = part[:len(part)-1]
  85. part += string(ch)
  86. continue
  87. }
  88. if lastRune == quote {
  89. // revert the last change of the inQuotes state
  90. // it was an escaping quote
  91. inQuotes = !inQuotes
  92. part += string(ch)
  93. continue
  94. }
  95. // normal escaping quotes
  96. inQuotes = !inQuotes
  97. continue
  98. }
  99. if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 {
  100. parts = append(parts, part)
  101. part = ""
  102. continue
  103. }
  104. part += string(ch)
  105. }
  106. if len(part) > 0 {
  107. parts = append(parts, part)
  108. }
  109. return parts
  110. }