From dadf0f7e414550fecfbd5241416b2214635af5b0 Mon Sep 17 00:00:00 2001 From: Brian Duenas Date: Thu, 1 May 2025 10:39:22 -0700 Subject: [PATCH] parse quotes from file Signed-off-by: Brian Duenas --- pkg/kvfile/kvfile.go | 29 ++++++++++++++++++++---- pkg/kvfile/kvfile_test.go | 46 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/pkg/kvfile/kvfile.go b/pkg/kvfile/kvfile.go index f6ac8ef4e047..b0c9c3ed71e8 100644 --- a/pkg/kvfile/kvfile.go +++ b/pkg/kvfile/kvfile.go @@ -15,8 +15,8 @@ // // # Interpolation, substitution, and escaping // -// Both keys and values are used as-is; no interpolation, substitution or -// escaping is supported, and quotes are considered part of the key or value. +// Both keys and values are used as-is; no interpolation or substitution is supported. +// Quotes around values are removed if the value starts and ends with the same quote character. // Whitespace in values (including leading and trailing) is preserved. Given // that the file format is line-delimited, neither key, nor value, can contain // newlines. @@ -78,6 +78,20 @@ func ParseFromReader(r io.Reader, lookupFn func(key string) (value string, found const whiteSpaces = " \t" +// trimQuotes removes the quotes from a string if the string starts and ends with the same quote character. +func trimQuotes(value string) string { + if len(value) < 2 { + return value + } + lastIndex := len(value) - 1 + for _, char := range []byte{'\'', '"'} { + if value[0] == char && value[lastIndex] == char { + return value[1:lastIndex] + } + } + return value +} + func parseKeyValueFile(r io.Reader, lookupFn func(string) (string, bool)) ([]string, error) { lines := []string{} scanner := bufio.NewScanner(r) @@ -113,8 +127,13 @@ func parseKeyValueFile(r io.Reader, lookupFn func(string) (string, bool)) ([]str } if hasValue { - // key/value pair is valid and has a value; add the line as-is. - lines = append(lines, line) + // key/value pair is valid and has a value + // Split the line to get the key and value + key, value, _ := strings.Cut(line, "=") + // Trim quotes from the value + value = trimQuotes(value) + // Reconstruct the line with the trimmed value + lines = append(lines, key+"="+value) continue } @@ -122,6 +141,8 @@ func parseKeyValueFile(r io.Reader, lookupFn func(string) (string, bool)) ([]str // No value given; try to look up the value. The value may be // empty but if no value is found, the key is omitted. if value, found := lookupFn(line); found { + // Trim quotes from the value + value = trimQuotes(value) lines = append(lines, key+"="+value) } } diff --git a/pkg/kvfile/kvfile_test.go b/pkg/kvfile/kvfile_test.go index f1a0e88e6bf5..ce3529217115 100644 --- a/pkg/kvfile/kvfile_test.go +++ b/pkg/kvfile/kvfile_test.go @@ -24,9 +24,12 @@ VAR=VAR_VALUE EMPTY_VAR= UNDEFINED_VAR DEFINED_VAR +QUOTED_VAR +QUOTED_VALUE="this should be quoted" ` vars := map[string]string{ "DEFINED_VAR": "defined-value", + "QUOTED_VAR": "\"quoted value\"", } lookupFn := func(name string) (string, bool) { v, ok := vars[name] @@ -40,7 +43,13 @@ DEFINED_VAR variables, err := Parse(fileName, lookupFn) assert.NilError(t, err) - expectedLines := []string{"VAR=VAR_VALUE", "EMPTY_VAR=", "DEFINED_VAR=defined-value"} + expectedLines := []string{ + "VAR=VAR_VALUE", + "EMPTY_VAR=", + "DEFINED_VAR=defined-value", + "QUOTED_VAR=quoted value", + "QUOTED_VALUE=this should be quoted", + } assert.Check(t, is.DeepEqual(variables, expectedLines)) } @@ -118,9 +127,11 @@ VAR=VAR_VALUE EMPTY_VAR= UNDEFINED_VAR DEFINED_VAR +QUOTED_VAR ` vars := map[string]string{ "DEFINED_VAR": "defined-value", + "QUOTED_VAR": "\"quoted value\"", } lookupFn := func(name string) (string, bool) { v, ok := vars[name] @@ -130,7 +141,12 @@ DEFINED_VAR variables, err := ParseFromReader(strings.NewReader(content), lookupFn) assert.NilError(t, err) - expectedLines := []string{"VAR=VAR_VALUE", "EMPTY_VAR=", "DEFINED_VAR=defined-value"} + expectedLines := []string{ + "VAR=VAR_VALUE", + "EMPTY_VAR=", + "DEFINED_VAR=defined-value", + "QUOTED_VAR=quoted value", + } assert.Check(t, is.DeepEqual(variables, expectedLines)) } @@ -143,3 +159,29 @@ func TestParseFromReaderWithNoName(t *testing.T) { const expectedMessage = "no variable name on line '=blank variable names are an error case'" assert.Check(t, is.ErrorContains(err, expectedMessage)) } + +// Test ParseFromReader with quoted values +func TestParseFromReaderWithQuotes(t *testing.T) { + content := `# Test with quotes +DOUBLE_QUOTES="double quoted value" +SINGLE_QUOTES='single quoted value' +MIXED_QUOTES="'mixed' quotes" +NESTED_QUOTES='"nested" quotes' +UNBALANCED_QUOTES="unbalanced quotes +UNBALANCED_QUOTES2=unbalanced quotes" +QUOTES_IN_MIDDLE=value "with" quotes +` + variables, err := ParseFromReader(strings.NewReader(content), nil) + assert.NilError(t, err) + + expectedLines := []string{ + "DOUBLE_QUOTES=double quoted value", + "SINGLE_QUOTES=single quoted value", + "MIXED_QUOTES='mixed' quotes", + "NESTED_QUOTES=\"nested\" quotes", + "UNBALANCED_QUOTES=\"unbalanced quotes", + "UNBALANCED_QUOTES2=unbalanced quotes\"", + "QUOTES_IN_MIDDLE=value \"with\" quotes", + } + assert.Check(t, is.DeepEqual(variables, expectedLines)) +}