diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 26bc0fcf2..c59925522 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -36,6 +36,10 @@ var ErrNotList = errors.New("not a list") // The default value 65536 = 1024 * 64 var MaxIndex = 65536 +// MaxNestedNameLevel is the maximum level of nesting for a value name that +// will be allowed. +var MaxNestedNameLevel = 30 + // ToYAML takes a string of arguments and converts to a YAML document. func ToYAML(s string) (string, error) { m, err := Parse(s) @@ -155,7 +159,7 @@ func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesVa func (t *parser) parse() error { for { - err := t.key(t.data) + err := t.key(t.data, 0) if err == nil { continue } @@ -174,7 +178,7 @@ func runeSet(r []rune) map[rune]bool { return s } -func (t *parser) key(data map[string]interface{}) (reterr error) { +func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) { defer func() { if r := recover(); r != nil { reterr = fmt.Errorf("unable to parse key: %s", r) @@ -204,7 +208,7 @@ func (t *parser) key(data map[string]interface{}) (reterr error) { } // Now we need to get the value after the ]. - list, err = t.listItem(list, i) + list, err = t.listItem(list, i, nestedNameLevel) set(data, kk, list) return err case last == '=': @@ -261,6 +265,12 @@ func (t *parser) key(data map[string]interface{}) (reterr error) { set(data, string(k), "") return errors.Errorf("key %q has no value (cannot end with ,)", string(k)) case last == '.': + // Check value name is within the maximum nested name level + nestedNameLevel++ + if nestedNameLevel > MaxNestedNameLevel { + return fmt.Errorf("value name nested level is greater than maximum supported nested level of %d", MaxNestedNameLevel) + } + // First, create or find the target map. inner := map[string]interface{}{} if _, ok := data[string(k)]; ok { @@ -268,11 +278,13 @@ func (t *parser) key(data map[string]interface{}) (reterr error) { } // Recurse - e := t.key(inner) - if len(inner) == 0 { + e := t.key(inner, nestedNameLevel) + if e == nil && len(inner) == 0 { return errors.Errorf("key map %q has no value", string(k)) } - set(data, string(k), inner) + if len(inner) != 0 { + set(data, string(k), inner) + } return e } } @@ -322,7 +334,7 @@ func (t *parser) keyIndex() (int, error) { return strconv.Atoi(string(v)) } -func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { +func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) { if i < 0 { return list, fmt.Errorf("negative %d index not allowed", i) } @@ -395,7 +407,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { } } // Now we need to get the value after the ]. - list2, err := t.listItem(crtList, nextI) + list2, err := t.listItem(crtList, nextI, nestedNameLevel) if err != nil { return list, err } @@ -414,7 +426,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { } // Recurse - e := t.key(inner) + e := t.key(inner, nestedNameLevel) if e != nil { return list, e } diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index f7eba7830..925aa97c6 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -16,6 +16,7 @@ limitations under the License. package strvals import ( + "fmt" "testing" "sigs.k8s.io/yaml" @@ -754,3 +755,64 @@ func TestToYAML(t *testing.T) { t.Errorf("Expected %q, got %q", expect, o) } } + +func TestParseSetNestedLevels(t *testing.T) { + var keyMultipleNestedLevels string + for i := 1; i <= MaxNestedNameLevel+2; i++ { + tmpStr := fmt.Sprintf("name%d", i) + if i <= MaxNestedNameLevel+1 { + tmpStr = tmpStr + "." + } + keyMultipleNestedLevels += tmpStr + } + tests := []struct { + str string + expect map[string]interface{} + err bool + errStr string + }{ + { + "outer.middle.inner=value", + map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + false, + "", + }, + { + str: keyMultipleNestedLevels + "=value", + err: true, + errStr: fmt.Sprintf("value name nested level is greater than maximum supported nested level of %d", + MaxNestedNameLevel), + }, + } + + for _, tt := range tests { + got, err := Parse(tt.str) + if err != nil { + if tt.err { + if tt.errStr != "" { + if err.Error() != tt.errStr { + t.Errorf("Expected error: %s. Got error: %s", tt.errStr, err.Error()) + } + } + continue + } + t.Fatalf("%s: %s", tt.str, err) + } + if tt.err { + t.Errorf("%s: Expected error. Got nil", tt.str) + } + + y1, err := yaml.Marshal(tt.expect) + if err != nil { + t.Fatal(err) + } + y2, err := yaml.Marshal(got) + if err != nil { + t.Fatalf("Error serializing parsed value: %s", err) + } + + if string(y1) != string(y2) { + t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) + } + } +}