From 3d4fa7f65d8bc22c96047b642d0c037403c4d4f9 Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Wed, 4 Oct 2017 10:20:33 +0200 Subject: [PATCH] Update github.com/hashicorp/go-discover to pull in new config parser This patch updates the go-discover library to use the new config parser which uses a different encoding scheme for the go-discover config DSL. Values are no longer URL encoded but taken literally unless they contain spaces, backslashes or double quotes. To support keys or values with these special characters the string needs to be double quoted and usual escaping rules apply. Fixes #3417 --- .../hashicorp/go-discover/README.md | 16 +- .../hashicorp/go-discover/config.go | 213 ++++++++++++++++-- vendor/vendor.json | 2 +- 3 files changed, 200 insertions(+), 31 deletions(-) diff --git a/vendor/github.com/hashicorp/go-discover/README.md b/vendor/github.com/hashicorp/go-discover/README.md index 15646f7b2..5375ed3df 100644 --- a/vendor/github.com/hashicorp/go-discover/README.md +++ b/vendor/github.com/hashicorp/go-discover/README.md @@ -5,16 +5,20 @@ ip addresses of nodes in cloud environments based on meta information like tags provided by the environment. -The configuration for the providers is provided as a list of `key=val -key=val ...` tuples where the values can be URL encoded. The provider is -determined through the `provider` key. Effectively, only spaces have to -be encoded with a `+` and on the command line you have to observe -quoting rules with your shell. +The configuration for the providers is provided as a list of `key=val key=val +...` tuples. If either the key or the value contains a space (` `), a backslash +(`\`) or double quotes (`"`) then it needs to be quoted with double quotes. +Within a quoted string you can use the backslash to escape double quotes or the +backslash itself, e.g. `key=val "some key"="some value"` + +Duplicate keys are reported as error and the provider is determined through the +`provider` key. ### Supported Providers The following cloud providers have implementations in the go-discover/provider -sub packages. Additional providers can be added through the [Register](https://godoc.org/github.com/hashicorp/go-discover#Register) +sub packages. Additional providers can be added through the +[Register](https://godoc.org/github.com/hashicorp/go-discover#Register) function. * Amazon AWS [Config options](http://godoc.org/github.com/hashicorp/go-discover/provider/aws) diff --git a/vendor/github.com/hashicorp/go-discover/config.go b/vendor/github.com/hashicorp/go-discover/config.go index e9e4ff05d..1853c9fa1 100644 --- a/vendor/github.com/hashicorp/go-discover/config.go +++ b/vendor/github.com/hashicorp/go-discover/config.go @@ -2,8 +2,8 @@ package discover import ( "fmt" - "net/url" "sort" + "strconv" "strings" ) @@ -11,28 +11,12 @@ import ( // functions to use. type Config map[string]string -// Parse parses a "key=val key=val ..." string into -// a config map. Values are URL escaped. +// Parse parses a "key=val key=val ..." string into a config map. Keys +// and values which contain spaces, backslashes or double-quotes must be +// quoted with double quotes. Use the backslash to escape special +// characters within quoted strings, e.g. "some key"="some \"value\"". func Parse(s string) (Config, error) { - s = strings.TrimSpace(s) - if s == "" { - return nil, nil - } - - c := Config{} - for _, v := range strings.Fields(s) { - p := strings.SplitN(v, "=", 2) - if len(p) != 2 { - return nil, fmt.Errorf("invalid format: %s", v) - } - key := p[0] - val, err := url.QueryUnescape(p[1]) - if err != nil { - return nil, fmt.Errorf("invalid format: %s", v) - } - c[key] = val - } - return c, nil + return parse(s) } // String formats a config map into the "key=val key=val ..." @@ -48,14 +32,195 @@ func (c Config) String() string { sort.Strings(keys) keys = append([]string{"provider"}, keys...) + quote := func(s string) string { + if strings.ContainsAny(s, ` "\`) { + return strconv.Quote(s) + } + return s + } + var vals []string for _, k := range keys { v := c[k] if v == "" { continue } - v = k + "=" + url.QueryEscape(v) - vals = append(vals, v) + vals = append(vals, quote(k)+"="+quote(v)) } return strings.Join(vals, " ") } + +func parse(in string) (Config, error) { + m := Config{} + s := []rune(strings.TrimSpace(in)) + state := stateKey + key := "" + for { + // exit condition + if len(s) == 0 { + break + } + + // get the next token + item, val, n := lex(s) + s = s[n:] + // fmt.Printf("parse: state: %q item: %q val: '%s' n: %d rest: '%s'\n", state, item, val, n, string(s)) + + switch state { + + case stateKey: + switch item { + case itemText: + key = val + if _, exists := m[key]; exists { + return nil, fmt.Errorf("%s: duplicate key", key) + } + state = stateEqual + default: + return nil, fmt.Errorf("%s: %s", key, val) + } + + case stateEqual: + switch item { + case itemEqual: + state = stateVal + default: + return nil, fmt.Errorf("%s: missing '='", key) + } + + case stateVal: + switch item { + case itemText: + m[key] = val + state = stateKey + case itemError: + return nil, fmt.Errorf("%s: %s", key, val) + default: + return nil, fmt.Errorf("%s: missing value", key) + } + } + } + + //fmt.Printf("parse: state: %q rest: '%s'\n", state, string(s)) + switch state { + case stateEqual: + return nil, fmt.Errorf("%s: missing '='", key) + case stateVal: + return nil, fmt.Errorf("%s: missing value", key) + } + if len(m) == 0 { + return nil, nil + } + return m, nil +} + +type itemType string + +const ( + itemText itemType = "TEXT" + itemEqual = "EQUAL" + itemError = "ERROR" +) + +func (t itemType) String() string { + return string(t) +} + +type state string + +const ( + + // lexer states + stateStart state = "start" + stateEqual = "equal" + stateText = "text" + stateQText = "qtext" + stateQTextEnd = "qtextend" + stateQTextEsc = "qtextesc" + + // parser states + stateKey = "key" + stateVal = "val" +) + +func lex(s []rune) (itemType, string, int) { + isEqual := func(r rune) bool { return r == '=' } + isEscape := func(r rune) bool { return r == '\\' } + isQuote := func(r rune) bool { return r == '"' } + isSpace := func(r rune) bool { return r == ' ' } + + unquote := func(r []rune) (string, error) { + v := strings.TrimSpace(string(r)) + return strconv.Unquote(v) + } + + var quote rune + state := stateStart + for i, r := range s { + // fmt.Println("lex:", "i:", i, "r:", string(r), "state:", string(state), "head:", string(s[:i]), "tail:", string(s[i:])) + switch state { + case stateStart: + switch { + case isSpace(r): + // state = stateStart + case isEqual(r): + state = stateEqual + case isQuote(r): + quote = r + state = stateQText + default: + state = stateText + } + + case stateEqual: + return itemEqual, "", i + + case stateText: + switch { + case isEqual(r) || isSpace(r): + v := strings.TrimSpace(string(s[:i])) + return itemText, v, i + default: + // state = stateText + } + + case stateQText: + switch { + case r == quote: + state = stateQTextEnd + case isEscape(r): + state = stateQTextEsc + default: + // state = stateQText + } + + case stateQTextEsc: + state = stateQText + + case stateQTextEnd: + v, err := unquote(s[:i]) + if err != nil { + return itemError, err.Error(), i + } + return itemText, v, i + } + } + + // fmt.Println("lex:", "state:", string(state)) + switch state { + case stateEqual: + return itemEqual, "", len(s) + case stateQText: + return itemError, "unbalanced quotes", len(s) + case stateQTextEsc: + return itemError, "unterminated escape sequence", len(s) + case stateQTextEnd: + v, err := unquote(s) + if err != nil { + return itemError, err.Error(), len(s) + } + return itemText, v, len(s) + default: + return itemText, strings.TrimSpace(string(s)), len(s) + } +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 21164a299..40878b5c2 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -25,7 +25,7 @@ {"path":"github.com/hashicorp/errwrap","checksumSHA1":"cdOCt0Yb+hdErz8NAQqayxPmRsY=","revision":"7554cd9344cec97297fa6649b055a8c98c2a1e55","revisionTime":"2014-10-28T05:47:10Z"}, {"path":"github.com/hashicorp/go-checkpoint","checksumSHA1":"nd3S1qkFv7zZxA9be0bw4nT0pe0=","revision":"e4b2dc34c0f698ee04750bf2035d8b9384233e1b","revisionTime":"2015-10-22T18:15:14Z"}, {"path":"github.com/hashicorp/go-cleanhttp","checksumSHA1":"b8F628srIitj5p7Y130xc9k0QWs=","revision":"3573b8b52aa7b37b9358d966a898feb387f62437","revisionTime":"2017-02-11T01:34:15Z"}, - {"path":"github.com/hashicorp/go-discover","checksumSHA1":"uNoQWG5h2hzWHjaLi376ZXVaCr4=","revision":"21b26b722865b64ae5809a532d2c18f3a3800129","revisionTime":"2017-08-16T16:53:52Z"}, + {"path":"github.com/hashicorp/go-discover","checksumSHA1":"tWQmb13hmUBoWi0ZHnc4CLBqRc0=","revision":"745a463b035b25a4d0c12d056e208e3850fdd1be","revisionTime":"2017-10-04T16:46:45Z"}, {"path":"github.com/hashicorp/go-discover/provider/aws","checksumSHA1":"lyPRg8aZKgGiNkMILk/VKwOqMy4=","revision":"25e4565347de14cea0a0e0730374c9fcffa7bab0","revisionTime":"2017-09-25T01:06:15Z","tree":true}, {"path":"github.com/hashicorp/go-discover/provider/azure","checksumSHA1":"r97P32e+VmNMh2vwLkZa1zPEDQU=","revision":"b518491d039b6782035b8881502b4f5e9fcc887b","revisionTime":"2017-08-01T15:32:04Z","tree":true}, {"path":"github.com/hashicorp/go-discover/provider/gce","checksumSHA1":"KC/MepQsQF17904UShiM61jmaEs=","revision":"b518491d039b6782035b8881502b4f5e9fcc887b","revisionTime":"2017-08-01T15:32:04Z","tree":true},