unquote.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package shellquote
  2. import (
  3. "bytes"
  4. "errors"
  5. "strings"
  6. "unicode/utf8"
  7. )
  8. var (
  9. UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string")
  10. UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string")
  11. UnterminatedEscapeError = errors.New("Unterminated backslash-escape")
  12. )
  13. var (
  14. splitChars = " \n\t"
  15. singleChar = '\''
  16. doubleChar = '"'
  17. escapeChar = '\\'
  18. doubleEscapeChars = "$`\"\n\\"
  19. )
  20. // Split splits a string according to /bin/sh's word-splitting rules. It
  21. // supports backslash-escapes, single-quotes, and double-quotes. Notably it does
  22. // not support the $'' style of quoting. It also doesn't attempt to perform any
  23. // other sort of expansion, including brace expansion, shell expansion, or
  24. // pathname expansion.
  25. //
  26. // If the given input has an unterminated quoted string or ends in a
  27. // backslash-escape, one of UnterminatedSingleQuoteError,
  28. // UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned.
  29. func Split(input string) (words []string, err error) {
  30. var buf bytes.Buffer
  31. words = make([]string, 0)
  32. for len(input) > 0 {
  33. // skip any splitChars at the start
  34. c, l := utf8.DecodeRuneInString(input)
  35. if strings.ContainsRune(splitChars, c) {
  36. input = input[l:]
  37. continue
  38. }
  39. var word string
  40. word, input, err = splitWord(input, &buf)
  41. if err != nil {
  42. return
  43. }
  44. words = append(words, word)
  45. }
  46. return
  47. }
  48. func splitWord(input string, buf *bytes.Buffer) (word string, remainder string, err error) {
  49. buf.Reset()
  50. raw:
  51. {
  52. cur := input
  53. for len(cur) > 0 {
  54. c, l := utf8.DecodeRuneInString(cur)
  55. cur = cur[l:]
  56. if c == singleChar {
  57. buf.WriteString(input[0 : len(input)-len(cur)-l])
  58. input = cur
  59. goto single
  60. } else if c == doubleChar {
  61. buf.WriteString(input[0 : len(input)-len(cur)-l])
  62. input = cur
  63. goto double
  64. } else if c == escapeChar {
  65. buf.WriteString(input[0 : len(input)-len(cur)-l])
  66. input = cur
  67. goto escape
  68. } else if strings.ContainsRune(splitChars, c) {
  69. buf.WriteString(input[0 : len(input)-len(cur)-l])
  70. return buf.String(), cur, nil
  71. }
  72. }
  73. if len(input) > 0 {
  74. buf.WriteString(input)
  75. input = ""
  76. }
  77. goto done
  78. }
  79. escape:
  80. {
  81. if len(input) == 0 {
  82. return "", "", UnterminatedEscapeError
  83. }
  84. c, l := utf8.DecodeRuneInString(input)
  85. if c == '\n' {
  86. // a backslash-escaped newline is elided from the output entirely
  87. } else {
  88. buf.WriteString(input[:l])
  89. }
  90. input = input[l:]
  91. }
  92. goto raw
  93. single:
  94. {
  95. i := strings.IndexRune(input, singleChar)
  96. if i == -1 {
  97. return "", "", UnterminatedSingleQuoteError
  98. }
  99. buf.WriteString(input[0:i])
  100. input = input[i+1:]
  101. goto raw
  102. }
  103. double:
  104. {
  105. cur := input
  106. for len(cur) > 0 {
  107. c, l := utf8.DecodeRuneInString(cur)
  108. cur = cur[l:]
  109. if c == doubleChar {
  110. buf.WriteString(input[0 : len(input)-len(cur)-l])
  111. input = cur
  112. goto raw
  113. } else if c == escapeChar {
  114. // bash only supports certain escapes in double-quoted strings
  115. c2, l2 := utf8.DecodeRuneInString(cur)
  116. cur = cur[l2:]
  117. if strings.ContainsRune(doubleEscapeChars, c2) {
  118. buf.WriteString(input[0 : len(input)-len(cur)-l-l2])
  119. if c2 == '\n' {
  120. // newline is special, skip the backslash entirely
  121. } else {
  122. buf.WriteRune(c2)
  123. }
  124. input = cur
  125. }
  126. }
  127. }
  128. return "", "", UnterminatedDoubleQuoteError
  129. }
  130. done:
  131. return buf.String(), input, nil
  132. }