fix: escape non-ASCII characters in OCI annotation values; fixes #31507

Signed-off-by: bassg0navy <bassgonavy@gmail.com>
pull/31591/head
bassg0navy 4 weeks ago
parent a75fcc2948
commit e77cafcada

@ -18,6 +18,7 @@ package registry // import "helm.sh/helm/v4/pkg/registry"
import (
"bytes"
"fmt"
"strings"
"time"
@ -65,14 +66,31 @@ annotations:
return ociAnnotations
}
func escapeNonASCII(s string) string {
var result strings.Builder
result.Grow(len(s)) // Pre-allocate for efficiency
for _, r := range s {
if r > 127 {
// Escape non-ASCII as \uXXXX (lowercase hex)
fmt.Fprintf(&result, "\\u%04x", r)
} else {
result.WriteRune(r)
}
}
return result.String()
}
// generateChartOCIAnnotations will generate OCI annotations from the provided chart
// Non-ASCII characters in annotation values are escaped as \uXXXX sequences
// to ensure compatibility with registries that strictly follow OCI spec recommendations.
func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string {
chartOCIAnnotations := map[string]string{}
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationDescription, meta.Description)
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationTitle, meta.Name)
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationVersion, meta.Version)
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationURL, meta.Home)
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationDescription, escapeNonASCII(meta.Description))
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationTitle, escapeNonASCII(meta.Name))
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationVersion, escapeNonASCII(meta.Version))
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationURL, escapeNonASCII(meta.Home))
if len(creationTime) == 0 {
creationTime = time.Now().UTC().Format(time.RFC3339)
@ -81,7 +99,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationCreated, creationTime)
if len(meta.Sources) > 0 {
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationSource, meta.Sources[0])
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationSource, escapeNonASCII(meta.Sources[0]))
}
if len(meta.Maintainers) > 0 {
@ -90,12 +108,12 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[
for maintainerIdx, maintainer := range meta.Maintainers {
if len(maintainer.Name) > 0 {
maintainerSb.WriteString(maintainer.Name)
maintainerSb.WriteString(escapeNonASCII(maintainer.Name))
}
if len(maintainer.Email) > 0 {
maintainerSb.WriteString(" (")
maintainerSb.WriteString(maintainer.Email)
maintainerSb.WriteString(escapeNonASCII(maintainer.Email))
maintainerSb.WriteString(")")
}

@ -272,3 +272,138 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) {
}
}
func TestEscapeNonASCII(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "ASCII only - no change",
input: "John Smith",
expected: "John Smith",
},
{
name: "German umlaut",
input: "Jan-Otto Kröpke",
expected: "Jan-Otto Kr\\u00f6pke",
},
{
name: "Multiple umlauts",
input: "Müller Schröder",
expected: "M\\u00fcller Schr\\u00f6der",
},
{
name: "French accents",
input: "François Müller",
expected: "Fran\\u00e7ois M\\u00fcller",
},
{
name: "Spanish tilde",
input: "José Muñoz",
expected: "Jos\\u00e9 Mu\\u00f1oz",
},
{
name: "Empty string",
input: "",
expected: "",
},
{
name: "Numbers and punctuation",
input: "v1.2.3-beta+build.123",
expected: "v1.2.3-beta+build.123",
},
{
name: "Email with umlaut in name",
input: "kröpke@example.com",
expected: "kr\\u00f6pke@example.com",
},
{
name: "Nordic characters",
input: "Øystein Ålander",
expected: "\\u00d8ystein \\u00c5lander",
},
{
name: "Mixed ASCII and non-ASCII",
input: "Abc Déf Ghï",
expected: "Abc D\\u00e9f Gh\\u00ef",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := escapeNonASCII(tt.input)
if result != tt.expected {
t.Errorf("escapeNonASCII(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestGenerateChartOCIAnnotations_WithNonASCII(t *testing.T) {
meta := &chart.Metadata{
Name: "test-chart",
Description: "A Helm chart for Kröpke",
Version: "1.0.0",
Home: "https://example.com",
Sources: []string{"https://github.com/example/test"},
Maintainers: []*chart.Maintainer{
{
Name: "Jan-Otto Kröpke",
Email: "github@jkroepke.de",
},
{
Name: "François Müller",
Email: "francois@example.com",
},
},
}
annotations := generateChartOCIAnnotations(meta, "2025-01-01T00:00:00Z")
// Check description is escaped
expectedDesc := "A Helm chart for Kr\\u00f6pke"
if annotations["org.opencontainers.image.description"] != expectedDesc {
t.Errorf("Description = %q, want %q",
annotations["org.opencontainers.image.description"], expectedDesc)
}
// Check authors are escaped
expectedAuthors := "Jan-Otto Kr\\u00f6pke (github@jkroepke.de), Fran\\u00e7ois M\\u00fcller (francois@example.com)"
if annotations["org.opencontainers.image.authors"] != expectedAuthors {
t.Errorf("Authors = %q, want %q",
annotations["org.opencontainers.image.authors"], expectedAuthors)
}
// Check title (ASCII only, should be unchanged)
if annotations["org.opencontainers.image.title"] != "test-chart" {
t.Errorf("Title = %q, want %q",
annotations["org.opencontainers.image.title"], "test-chart")
}
}
func TestGenerateChartOCIAnnotations_ASCIIOnly(t *testing.T) {
meta := &chart.Metadata{
Name: "test-chart",
Description: "A simple Helm chart",
Version: "1.0.0",
Maintainers: []*chart.Maintainer{
{
Name: "John Smith",
Email: "john@example.com",
},
},
}
annotations := generateChartOCIAnnotations(meta, "2025-01-01T00:00:00Z")
// ASCII-only values should be unchanged
if annotations["org.opencontainers.image.description"] != "A simple Helm chart" {
t.Errorf("Description should be unchanged for ASCII-only input")
}
if annotations["org.opencontainers.image.authors"] != "John Smith (john@example.com)" {
t.Errorf("Authors should be unchanged for ASCII-only input")
}
}
Loading…
Cancel
Save