diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go index 48f346ccf..0ff040eae 100644 --- a/internal/chart/v3/loader/load.go +++ b/internal/chart/v3/loader/load.go @@ -183,8 +183,20 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. func LoadValues(data io.Reader) (map[string]any, error) { + // Read fully first. YAMLReader/LineReader can drop a final unterminated + // line when its length is an exact multiple of bufio.Reader's default + // buffer (4096). Appending a trailing newline avoids that case. + // See https://github.com/helm/helm/issues/32506 + b, err := io.ReadAll(data) + if err != nil { + return nil, err + } + if len(b) > 0 && b[len(b)-1] != '\n' { + b = append(b, '\n') + } + values := map[string]any{} - reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(b))) for { currentMap := map[string]any{} raw, err := reader.Read() diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index dd5fdc8db..f105b248f 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -419,6 +419,29 @@ foo: } } + +func TestLoadValuesEOFBoundary(t *testing.T) { + // Reproduces #32506: a single logical line whose length is a multiple of + // bufio's default buffer (4096) and has no trailing newline used to be + // dropped entirely by YAMLReader, yielding empty values. + prefix := []byte(`{"foo":"`) + suffix := []byte(`"}`) + pad := 4096 - len(prefix) - len(suffix) + data := make([]byte, 0, 4096) + data = append(data, prefix...) + data = append(data, bytes.Repeat([]byte("x"), pad)...) + data = append(data, suffix...) + if len(data) != 4096 { + t.Fatalf("test setup: want data length 4096, got %d", len(data)) + } + + values, err := LoadValues(bytes.NewReader(data)) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "foo": string(bytes.Repeat([]byte("x"), pad)), + }, values) +} + func TestMergeValuesV3(t *testing.T) { nestedMap := map[string]any{ "foo": "bar", diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index 28115d062..d7b125b9b 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -210,8 +210,20 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. func LoadValues(data io.Reader) (map[string]any, error) { + // Read fully first. YAMLReader/LineReader can drop a final unterminated + // line when its length is an exact multiple of bufio.Reader's default + // buffer (4096). Appending a trailing newline avoids that case. + // See https://github.com/helm/helm/issues/32506 + b, err := io.ReadAll(data) + if err != nil { + return nil, err + } + if len(b) > 0 && b[len(b)-1] != '\n' { + b = append(b, '\n') + } + values := map[string]any{} - reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(b))) for { currentMap := map[string]any{} raw, err := reader.Read() diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index 8bf254321..b4836ee95 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -463,6 +463,29 @@ foo: } } + +func TestLoadValuesEOFBoundary(t *testing.T) { + // Reproduces #32506: a single logical line whose length is a multiple of + // bufio's default buffer (4096) and has no trailing newline used to be + // dropped entirely by YAMLReader, yielding empty values. + prefix := []byte(`{"foo":"`) + suffix := []byte(`"}`) + pad := 4096 - len(prefix) - len(suffix) + data := make([]byte, 0, 4096) + data = append(data, prefix...) + data = append(data, bytes.Repeat([]byte("x"), pad)...) + data = append(data, suffix...) + if len(data) != 4096 { + t.Fatalf("test setup: want data length 4096, got %d", len(data)) + } + + values, err := LoadValues(bytes.NewReader(data)) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "foo": string(bytes.Repeat([]byte("x"), pad)), + }, values) +} + func TestMergeValuesV2(t *testing.T) { nestedMap := map[string]any{ "foo": "bar",