]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
tpl/encoding: Add noHTMLEscape option to jsonify
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 24 Oct 2022 13:28:03 +0000 (15:28 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 24 Oct 2022 15:21:42 +0000 (17:21 +0200)
docs/content/en/functions/jsonify.md
tpl/encoding/encoding.go
tpl/encoding/encoding_test.go

index 28b90534c30308c4ca15260ad8d2f3efc200cf18..3aa38c8c4e2ea1a9ad981697284a61981bc48f60 100644 (file)
@@ -32,6 +32,17 @@ more copies of *indent* according to the indentation nesting.
 {{ dict "title" .Title "content" .Plain | jsonify (dict "prefix" " " "indent" "  ") }}
 ```
 
+## Jsonify options
+
+indent ("")
+: Indendation to use.
+
+prefix ("")
+: Indentation prefix.
+
+noHTMLEscape (false)
+: Disable escaping of problematic HTML characters inside JSON quoted strings. The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e to avoid certain safety problems that can arise when embedding JSON in HTML.
+
 See also the `.PlainWords`, `.Plain`, and `.RawContent` [page variables][pagevars].
 
 [pagevars]: /variables/page/
index 272503e0c055032104152c7c2704779de9fcbfed..0510f86e2e72424bbc52395bc54e673df36c0bde 100644 (file)
@@ -20,7 +20,10 @@ import (
        "errors"
        "html/template"
 
+       bp "github.com/gohugoio/hugo/bufferpool"
+
        "github.com/gohugoio/hugo/common/maps"
+       "github.com/mitchellh/mapstructure"
        "github.com/spf13/cast"
 )
 
@@ -60,24 +63,27 @@ func (ns *Namespace) Base64Encode(content any) (string, error) {
 // to the indentation nesting.
 func (ns *Namespace) Jsonify(args ...any) (template.HTML, error) {
        var (
-               b   []byte
-               err error
+               b    []byte
+               err  error
+               obj  any
+               opts jsonifyOpts
        )
 
        switch len(args) {
        case 0:
                return "", nil
        case 1:
-               b, err = json.Marshal(args[0])
+               obj = args[0]
        case 2:
-               var opts map[string]string
-
-               opts, err = maps.ToStringMapStringE(args[0])
+               var m map[string]any
+               m, err = maps.ToStringMapE(args[0])
                if err != nil {
                        break
                }
-
-               b, err = json.MarshalIndent(args[1], opts["prefix"], opts["indent"])
+               if err = mapstructure.WeakDecode(m, &opts); err != nil {
+                       break
+               }
+               obj = args[1]
        default:
                err = errors.New("too many arguments to jsonify")
        }
@@ -86,5 +92,25 @@ func (ns *Namespace) Jsonify(args ...any) (template.HTML, error) {
                return "", err
        }
 
+       buff := bp.GetBuffer()
+       defer bp.PutBuffer(buff)
+       e := json.NewEncoder(buff)
+       e.SetEscapeHTML(!opts.NoHTMLEscape)
+       e.SetIndent(opts.Prefix, opts.Indent)
+       if err = e.Encode(obj); err != nil {
+               return "", err
+       }
+       b = buff.Bytes()
+       // See https://github.com/golang/go/issues/37083
+       // Hugo changed from MarshalIndent/Marshal. To make the output
+       // the same, we need to trim the trailing newline.
+       b = b[:len(b)-1]
+
        return template.HTML(b), nil
 }
+
+type jsonifyOpts struct {
+       Prefix       string
+       Indent       string
+       NoHTMLEscape bool
+}
index e7c82e3befcdd37a17d247afa7ffc4d396e4bb93..8e6e2da4893a63ffcbf3e8a80d8d4d59b53d9dd0 100644 (file)
@@ -82,7 +82,7 @@ func TestJsonify(t *testing.T) {
        c := qt.New(t)
        ns := New()
 
-       for _, test := range []struct {
+       for i, test := range []struct {
                opts   any
                v      any
                expect any
@@ -91,6 +91,9 @@ func TestJsonify(t *testing.T) {
                {map[string]string{"indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<i>\"a\",\n<i>\"b\"\n]")},
                {map[string]string{"prefix": "<p>"}, []string{"a", "b"}, template.HTML("[\n<p>\"a\",\n<p>\"b\"\n<p>]")},
                {map[string]string{"prefix": "<p>", "indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<p><i>\"a\",\n<p><i>\"b\"\n<p>]")},
+               {map[string]string{"indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<i>\"a\",\n<i>\"b\"\n]")},
+               {map[string]any{"noHTMLEscape": false}, []string{"<a>", "<b>"}, template.HTML("[\"\\u003ca\\u003e\",\"\\u003cb\\u003e\"]")},
+               {map[string]any{"noHTMLEscape": true}, []string{"<a>", "<b>"}, template.HTML("[\"<a>\",\"<b>\"]")},
                {nil, tstNoStringer{}, template.HTML("{}")},
                {nil, nil, template.HTML("null")},
                // errors
@@ -108,11 +111,11 @@ func TestJsonify(t *testing.T) {
                result, err := ns.Jsonify(args...)
 
                if b, ok := test.expect.(bool); ok && !b {
-                       c.Assert(err, qt.Not(qt.IsNil))
+                       c.Assert(err, qt.Not(qt.IsNil), qt.Commentf("#%d", i))
                        continue
                }
 
                c.Assert(err, qt.IsNil)
-               c.Assert(result, qt.Equals, test.expect)
+               c.Assert(result, qt.Equals, test.expect, qt.Commentf("#%d", i))
        }
 }