/middleware/commands.go

https://gitlab.com/klauer/caddy · Go · 120 lines · 92 code · 10 blank · 18 comment · 8 complexity · c2952f2385ade44ff2a563288f7c5bd2 MD5 · raw file

  1. package middleware
  2. import (
  3. "errors"
  4. "runtime"
  5. "unicode"
  6. "github.com/flynn/go-shlex"
  7. )
  8. var runtimeGoos = runtime.GOOS
  9. // SplitCommandAndArgs takes a command string and parses it
  10. // shell-style into the command and its separate arguments.
  11. func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
  12. var parts []string
  13. if runtimeGoos == "windows" {
  14. parts = parseWindowsCommand(command) // parse it Windows-style
  15. } else {
  16. parts, err = parseUnixCommand(command) // parse it Unix-style
  17. if err != nil {
  18. err = errors.New("error parsing command: " + err.Error())
  19. return
  20. }
  21. }
  22. if len(parts) == 0 {
  23. err = errors.New("no command contained in '" + command + "'")
  24. return
  25. }
  26. cmd = parts[0]
  27. if len(parts) > 1 {
  28. args = parts[1:]
  29. }
  30. return
  31. }
  32. // parseUnixCommand parses a unix style command line and returns the
  33. // command and its arguments or an error
  34. func parseUnixCommand(cmd string) ([]string, error) {
  35. return shlex.Split(cmd)
  36. }
  37. // parseWindowsCommand parses windows command lines and
  38. // returns the command and the arguments as an array. It
  39. // should be able to parse commonly used command lines.
  40. // Only basic syntax is supported:
  41. // - spaces in double quotes are not token delimiters
  42. // - double quotes are escaped by either backspace or another double quote
  43. // - except for the above case backspaces are path separators (not special)
  44. //
  45. // Many sources point out that escaping quotes using backslash can be unsafe.
  46. // Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 )
  47. //
  48. // This function has to be used on Windows instead
  49. // of the shlex package because this function treats backslash
  50. // characters properly.
  51. func parseWindowsCommand(cmd string) []string {
  52. const backslash = '\\'
  53. const quote = '"'
  54. var parts []string
  55. var part string
  56. var inQuotes bool
  57. var lastRune rune
  58. for i, ch := range cmd {
  59. if i != 0 {
  60. lastRune = rune(cmd[i-1])
  61. }
  62. if ch == backslash {
  63. // put it in the part - for now we don't know if it's an
  64. // escaping char or path separator
  65. part += string(ch)
  66. continue
  67. }
  68. if ch == quote {
  69. if lastRune == backslash {
  70. // remove the backslash from the part and add the escaped quote instead
  71. part = part[:len(part)-1]
  72. part += string(ch)
  73. continue
  74. }
  75. if lastRune == quote {
  76. // revert the last change of the inQuotes state
  77. // it was an escaping quote
  78. inQuotes = !inQuotes
  79. part += string(ch)
  80. continue
  81. }
  82. // normal escaping quotes
  83. inQuotes = !inQuotes
  84. continue
  85. }
  86. if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 {
  87. parts = append(parts, part)
  88. part = ""
  89. continue
  90. }
  91. part += string(ch)
  92. }
  93. if len(part) > 0 {
  94. parts = append(parts, part)
  95. part = ""
  96. }
  97. return parts
  98. }