mirror of
https://github.com/mattermost/mattermost.git
synced 2026-10-16 15:18:10 +08:00
MM-71178 Allow " --" inside double quoted slash command values
Rejecting " --" inside any quoted value was how a missing closing quote got caught, but it also rejected legitimate values such as --header "use --foo here". A double quoted value now ends at the next matching quote, and text glued to a closing quote is an error, so a missing closing quote is still reported. Single quotes double as apostrophes, so they keep the whitespace rule and the " --" restriction, and the error message suggests double quotes instead.
This commit is contained in:
@@ -112,7 +112,7 @@ func TestRemoteProviderDoRemove(t *testing.T) {
|
||||
"empty quotes": {`/secure-connection remove --connectionID ""`, "Missing or empty `connectionID`"},
|
||||
"malformed": {"/secure-connection remove --connectionID foo", "`foo` is not a valid `connectionID`"},
|
||||
"stray word": {"/secure-connection remove --connectionID foo bar", "Unexpected `bar`. Wrap a value that contains spaces in quotes, and use double quotes if the value also contains an apostrophe."},
|
||||
"open quote": {`/secure-connection remove --connectionID "foo`, "The quote at `\"foo` is never closed. A quoted value must end with a matching quote and cannot contain ` --`."},
|
||||
"open quote": {`/secure-connection remove --connectionID "foo`, "The quote at `\"foo` is never closed. A value in single quotes cannot contain ` --`, so use double quotes for that."},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
callsBefore := wrapper.deleteCalls.Load()
|
||||
|
||||
@@ -85,9 +85,8 @@ func (t commandToken) value() string {
|
||||
var closingQuotes = map[rune]rune{'"': '"', '\'': '\'', '“': '”', '‘': '’'}
|
||||
|
||||
// tokenizeCommand splits cmd on whitespace, except inside a quoted value. A quote only opens
|
||||
// a value at the start of a word and only closes it when followed by whitespace or the end
|
||||
// of input, so apostrophes such as O'Brien are kept. A quoted value may not contain " --",
|
||||
// so a missing closing quote is an error instead of swallowing the flags after it.
|
||||
// a value at the start of a word, so apostrophes such as O'Brien are kept. A double quoted
|
||||
// value ends at the next matching quote. See quotedToken for single quotes.
|
||||
func tokenizeCommand(T i18n.TranslateFunc, cmd string) ([]commandToken, error) {
|
||||
var tokens []commandToken
|
||||
rest := strings.TrimLeftFunc(cmd, unicode.IsSpace)
|
||||
@@ -99,6 +98,10 @@ func tokenizeCommand(T i18n.TranslateFunc, cmd string) ([]commandToken, error) {
|
||||
word, _ := nextWord(rest)
|
||||
return nil, errors.New(T("api.command.named_args.unterminated_quote", map[string]any{"Value": word}))
|
||||
}
|
||||
if next, _ := utf8.DecodeRuneInString(after); after != "" && !unicode.IsSpace(next) {
|
||||
word, _ := nextWord(after)
|
||||
return nil, errors.New(T("api.command.named_args.unexpected_value", map[string]any{"Value": word}))
|
||||
}
|
||||
tok, rest = quoted, after
|
||||
} else {
|
||||
tok.text, rest = nextWord(rest)
|
||||
@@ -117,7 +120,17 @@ func nextWord(s string) (string, string) {
|
||||
return s[:end], s[end:]
|
||||
}
|
||||
|
||||
// A single quote doubles as an apostrophe, so a single quoted value only ends at a matching
|
||||
// quote followed by whitespace or the end of input. It may not contain " --", so a missing
|
||||
// closing quote is an error instead of swallowing the flags after it.
|
||||
func quotedToken(s string, closeQuote rune) (commandToken, string, bool) {
|
||||
if closeQuote == '"' || closeQuote == '”' {
|
||||
end := strings.IndexRune(s, closeQuote)
|
||||
if end < 0 {
|
||||
return commandToken{}, "", false
|
||||
}
|
||||
return commandToken{text: s[:end], quoted: true}, s[end+utf8.RuneLen(closeQuote):], true
|
||||
}
|
||||
for i, r := range s {
|
||||
after := s[i+utf8.RuneLen(r):]
|
||||
if r == closeQuote {
|
||||
|
||||
@@ -41,7 +41,9 @@ func TestParseNamedArgs(t *testing.T) {
|
||||
{"quoted spaces with embedded quote", "remote add --arg1 \"Bob O'Brien's remote\"", map[string]string{ActionKey: "add", "arg1": "Bob O'Brien's remote"}},
|
||||
{"single quoted with embedded apostrophe", "remote add --arg1 'O'Brien'", map[string]string{ActionKey: "add", "arg1": "O'Brien"}},
|
||||
{"quoted value that looks like a flag", "remote add --arg1 \"--arg2\"", map[string]string{ActionKey: "add", "arg1": "--arg2"}},
|
||||
{"quote closes only before whitespace", "remote add --arg1 \"a\"b c\"", map[string]string{ActionKey: "add", "arg1": "a\"b c"}},
|
||||
{"single quote closes only before whitespace", "remote add --arg1 'a'b c'", map[string]string{ActionKey: "add", "arg1": "a'b c"}},
|
||||
{"double quoted value containing a flag", "remote add --arg1 \"use --arg2 here\" --arg3 val3", map[string]string{ActionKey: "add", "arg1": "use --arg2 here", "arg3": "val3"}},
|
||||
{"curly double quoted value containing a flag", "remote add --arg1 \u201cuse --arg2 here\u201d", map[string]string{ActionKey: "add", "arg1": "use --arg2 here"}},
|
||||
{"curly double quotes", "remote add --arg1 \u201cmy remote\u201d --arg2 val2", map[string]string{ActionKey: "add", "arg1": "my remote", "arg2": "val2"}},
|
||||
{"curly single quotes with curly apostrophe", "remote add --arg1 \u2018Bob\u2019s remote\u2019", map[string]string{ActionKey: "add", "arg1": "Bob\u2019s remote"}},
|
||||
}
|
||||
@@ -68,7 +70,9 @@ func TestParseNamedArgsErrors(t *testing.T) {
|
||||
{"apostrophe then space ends single quoted value", "remote add --arg1 'Engineers' Hub'", unexpectedValue},
|
||||
{"unterminated quote at end", "remote add --arg1 \"my remote", unterminatedQuote},
|
||||
{"unterminated quote before flag", "remote add --arg1 \"my remote --arg2 val2", unterminatedQuote},
|
||||
{"unterminated quote before quoted value", "remote add --arg1 \"foo --arg2 \"Bar Baz\" --arg3 val3", unterminatedQuote},
|
||||
{"unterminated quote before quoted value", "remote add --arg1 \"foo --arg2 \"Bar Baz\" --arg3 val3", unexpectedValue},
|
||||
{"text after closing double quote", "remote add --arg1 \"a\"b c\"", unexpectedValue},
|
||||
{"single quoted value containing a flag", "remote add --arg1 'use --arg2 here'", unterminatedQuote},
|
||||
{"mismatched curly quotes", "remote add --arg1 \u201cmy remote\" --arg2 val2", unterminatedQuote},
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1161,8 +1161,8 @@
|
||||
},
|
||||
{
|
||||
"id": "api.command.named_args.unterminated_quote",
|
||||
"translation": "The quote at `{{.Value}}` is never closed. A quoted value must end with a matching quote and cannot contain ` --`.",
|
||||
"description": "Slash command error when a quoted argument value has no closing quote before the next flag or the end of the command."
|
||||
"translation": "The quote at `{{.Value}}` is never closed. A value in single quotes cannot contain ` --`, so use double quotes for that.",
|
||||
"description": "Slash command error when a quoted argument value has no closing quote, or a single quoted value contains a flag."
|
||||
},
|
||||
{
|
||||
"id": "api.command.team_mismatch.app_error",
|
||||
|
||||
Reference in New Issue
Block a user