fix(engine): override toPrettyJson to disable HTML escaping

Sprig's toPrettyJson uses json.MarshalIndent which HTML-escapes &, <, >
to \u0026, \u003c, \u003e. This breaks URLs and other strings containing
these characters when used in Helm chart templates.

Override toPrettyJson and mustToPrettyJson in the engine FuncMap with
implementations that use json.Encoder with SetEscapeHTML(false), matching
Sprig's two-space indentation for backward compatibility.

Signed-off-by: piotr.laczykowski <piotr.laczykowski@example.com>
pull/31964/head
piotr.laczykowski 3 weeks ago
parent b3927b3900
commit 7d5bcac49b

@ -49,18 +49,20 @@ func funcMap() template.FuncMap {
// Add some extra functionality
extra := template.FuncMap{
"toToml": toTOML,
"mustToToml": mustToTOML,
"fromToml": fromTOML,
"toYaml": toYAML,
"mustToYaml": mustToYAML,
"toYamlPretty": toYAMLPretty,
"fromYaml": fromYAML,
"fromYamlArray": fromYAMLArray,
"toJson": toJSON,
"mustToJson": mustToJSON,
"fromJson": fromJSON,
"fromJsonArray": fromJSONArray,
"toToml": toTOML,
"mustToToml": mustToTOML,
"fromToml": fromTOML,
"toYaml": toYAML,
"mustToYaml": mustToYAML,
"toYamlPretty": toYAMLPretty,
"fromYaml": fromYAML,
"fromYamlArray": fromYAMLArray,
"toJson": toJSON,
"mustToJson": mustToJSON,
"toPrettyJson": toPrettyJSON,
"mustToPrettyJson": mustToPrettyJSON,
"fromJson": fromJSON,
"fromJsonArray": fromJSONArray,
// This is a placeholder for the "include" function, which is
// late-bound to a template. By declaring it here, we preserve the
@ -220,6 +222,48 @@ func mustToJSON(v any) string {
return string(data)
}
// encodePrettyJSON encodes v as indented JSON without HTML-escaping special
// characters (&, <, >). It uses two-space indentation to match the behavior
// of Sprig's toPrettyJson.
func encodePrettyJSON(v any) (string, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
if err := enc.Encode(v); err != nil {
return "", err
}
return strings.TrimSuffix(buf.String(), "\n"), nil
}
// toPrettyJSON takes an interface, marshals it to indented JSON without
// HTML-escaping special characters (&, <, >), and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func toPrettyJSON(v any) string {
s, err := encodePrettyJSON(v)
if err != nil {
// Swallow errors inside of a template.
return ""
}
return s
}
// mustToPrettyJSON takes an interface, marshals it to indented JSON without
// HTML-escaping special characters (&, <, >), and returns a string.
// It will panic if there is an error.
//
// This is designed to be called from a template when you need to ensure that
// the output JSON is valid.
func mustToPrettyJSON(v any) string {
s, err := encodePrettyJSON(v)
if err != nil {
panic(err)
}
return s
}
// fromJSON converts a JSON document into a map[string]interface{}.
//
// This is not a general-purpose JSON parser, and will not parse all valid

@ -69,6 +69,15 @@ keyInElement1 = "valueInElement1"`,
tpl: `{{ toJson . }}`,
expect: `{"foo":"bar"}`,
vars: map[string]any{"foo": "bar"},
}, {
// toPrettyJson must not HTML-escape &, <, > and must use 2-space indent
tpl: "{{ toPrettyJson . }}",
expect: "{\n \"url\": \"https://example.com?a=1&b=2<>\"\n}",
vars: map[string]any{"url": "https://example.com?a=1&b=2<>"},
}, {
tpl: "{{ toPrettyJson . }}",
expect: "{\n \"foo\": \"bar\"\n}",
vars: map[string]any{"foo": "bar"},
}, {
tpl: `{{ fromYaml . }}`,
expect: "map[hello:world]",
@ -151,6 +160,17 @@ keyInElement1 = "valueInElement1"`,
}, {
tpl: `{{ mustToJson . }}`,
vars: loopMap,
}, {
tpl: `{{ mustToPrettyJson . }}`,
vars: loopMap, // circular reference must panic
}, {
tpl: `{{ mustToPrettyJson . }}`,
expect: "{\n \"foo\": \"bar\"\n}",
vars: map[string]any{"foo": "bar"},
}, {
tpl: `{{ toPrettyJson . }}`,
expect: "", // circular reference must swallow error and return ""
vars: loopMap,
}, {
tpl: `{{ toYaml . }}`,
expect: "", // should return empty string and swallow error

Loading…
Cancel
Save