]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Pull in the latest code from Go's template packages (#11771)
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 4 Dec 2023 11:07:54 +0000 (12:07 +0100)
committerGitHub <noreply@github.com>
Mon, 4 Dec 2023 11:07:54 +0000 (12:07 +0100)
Fixes #10707
Fixes #11507

25 files changed:
config/allconfig/load.go
config/security/securityConfig.go
config/security/securityConfig_test.go
scripts/fork_go_templates/main.go
tpl/internal/go_templates/fmtsort/sort_test.go
tpl/internal/go_templates/htmltemplate/context.go
tpl/internal/go_templates/htmltemplate/error.go
tpl/internal/go_templates/htmltemplate/escape.go
tpl/internal/go_templates/htmltemplate/escape_test.go
tpl/internal/go_templates/htmltemplate/exec_test.go
tpl/internal/go_templates/htmltemplate/hugo_template.go
tpl/internal/go_templates/htmltemplate/js.go
tpl/internal/go_templates/htmltemplate/state_string.go
tpl/internal/go_templates/htmltemplate/transition.go
tpl/internal/go_templates/testenv/exec.go
tpl/internal/go_templates/testenv/testenv.go
tpl/internal/go_templates/testenv/testenv_notunix.go
tpl/internal/go_templates/testenv/testenv_test.go
tpl/internal/go_templates/testenv/testenv_unix.go
tpl/internal/go_templates/texttemplate/doc.go
tpl/internal/go_templates/texttemplate/exec.go
tpl/internal/go_templates/texttemplate/exec_test.go
tpl/internal/go_templates/texttemplate/funcs.go
tpl/template.go
tpl/tplimpl/integration_test.go

index e7dae18066e3ec0487cd4d7ad2193d321ec98f4c..7d706c7e3d7fdd6947160a26905f39dc47db2dda 100644 (file)
@@ -34,7 +34,6 @@ import (
        hglob "github.com/gohugoio/hugo/hugofs/glob"
        "github.com/gohugoio/hugo/modules"
        "github.com/gohugoio/hugo/parser/metadecoders"
-       "github.com/gohugoio/hugo/tpl"
        "github.com/spf13/afero"
 )
 
@@ -91,9 +90,6 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
                return nil, fmt.Errorf("failed to init config: %w", err)
        }
 
-       // This is unfortunate, but these are global settings.
-       tpl.SetSecurityAllowActionJSTmpl(configs.Base.Security.GoTemplates.AllowActionJSTmpl)
-
        loggers.InitGlobalLogger(d.Logger.Level(), configs.Base.PanicOnWarning)
 
        return configs, nil
index 16f8c23d876e70a64d1071c0b413a461c0065e1a..e3bffedcabd39fdfb10469d8779bf31a428b8d04 100644 (file)
@@ -68,9 +68,6 @@ type Config struct {
 
        // Allow inline shortcodes
        EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
-
-       // Go templates related security config.
-       GoTemplates GoTemplates `json:"goTemplates"`
 }
 
 // Exec holds os/exec policies.
@@ -96,15 +93,6 @@ type HTTP struct {
        MediaTypes Whitelist `json:"mediaTypes"`
 }
 
-type GoTemplates struct {
-
-       // Enable to allow template actions inside bakcticks in ES6 template literals.
-       // This was blocked in Hugo 0.114.0 for security reasons and you now get errors on the form
-       // "... appears in a JS template literal" if you have this in your templates.
-       // See https://github.com/golang/go/issues/59234
-       AllowActionJSTmpl bool
-}
-
 // ToTOML converts c to TOML with [security] as the root.
 func (c Config) ToTOML() string {
        sec := c.ToSecurityMap()
@@ -127,7 +115,6 @@ func (c Config) CheckAllowedExec(name string) error {
                }
        }
        return nil
-
 }
 
 func (c Config) CheckAllowedGetEnv(name string) error {
@@ -176,7 +163,6 @@ func (c Config) ToSecurityMap() map[string]any {
                "security": m,
        }
        return sec
-
 }
 
 // DecodeConfig creates a privacy Config from a given Hugo configuration.
@@ -206,15 +192,14 @@ func DecodeConfig(cfg config.Provider) (Config, error) {
        }
 
        return sc, nil
-
 }
 
 func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
        return func(
                f reflect.Type,
                t reflect.Type,
-               data any) (any, error) {
-
+               data any,
+       ) (any, error) {
                if t != reflect.TypeOf(Whitelist{}) {
                        return data, nil
                }
@@ -222,7 +207,6 @@ func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
                wl := types.ToStringSlicePreserveString(data)
 
                return NewWhitelist(wl...)
-
        }
 }
 
index cdfbe63412b04b884d10d061ceaccdbeaba9dc1a..3d58288c9db8a194310a4ff5348cc9119852bdc7 100644 (file)
@@ -53,7 +53,6 @@ getEnv=["a", "b"]
                c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
                c.Assert(pc.Funcs.Getenv.Accept("a"), qt.IsTrue)
                c.Assert(pc.Funcs.Getenv.Accept("c"), qt.IsFalse)
-
        })
 
        c.Run("String whitelist", func(c *qt.C) {
@@ -80,7 +79,6 @@ osEnv="b"
                c.Assert(pc.Exec.Allow.Accept("d"), qt.IsFalse)
                c.Assert(pc.Exec.OsEnv.Accept("b"), qt.IsTrue)
                c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
-
        })
 
        c.Run("Default exec.osEnv", func(c *qt.C) {
@@ -105,7 +103,6 @@ allow="a"
                c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
                c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
                c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
-
        })
 
        c.Run("Enable inline shortcodes, legacy", func(c *qt.C) {
@@ -129,9 +126,7 @@ osEnv="b"
                pc, err := DecodeConfig(cfg)
                c.Assert(err, qt.IsNil)
                c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
-
        })
-
 }
 
 func TestToTOML(t *testing.T) {
@@ -140,7 +135,7 @@ func TestToTOML(t *testing.T) {
        got := DefaultConfig.ToTOML()
 
        c.Assert(got, qt.Equals,
-               "[security]\n  enableInlineShortcodes = false\n\n  [security.exec]\n    allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$']\n    osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$']\n\n  [security.funcs]\n    getenv = ['^HUGO_', '^CI$']\n\n  [security.goTemplates]\n    AllowActionJSTmpl = false\n\n  [security.http]\n    methods = ['(?i)GET|POST']\n    urls = ['.*']",
+               "[security]\n  enableInlineShortcodes = false\n\n  [security.exec]\n    allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$']\n    osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$']\n\n  [security.funcs]\n    getenv = ['^HUGO_', '^CI$']\n\n  [security.http]\n    methods = ['(?i)GET|POST']\n    urls = ['.*']",
        )
 }
 
@@ -169,5 +164,4 @@ func TestDecodeConfigDefault(t *testing.T) {
        c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
        c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
        c.Assert(pc.Exec.OsEnv.Accept("MYSECRET"), qt.IsFalse)
-
 }
index 4ab89a54732ac39df62108a9f57ae03cedeeb5a7..8e14813ec72d233c0ab8a03f459dfe5f1b2c2304 100644 (file)
@@ -16,7 +16,7 @@ import (
 )
 
 func main() {
-       // The current is built with 2c1e5b05fe39fc5e6c730dd60e82946b8e67c6ba, tag: go1.21.1.
+       // The current is built with 446a5dcf5a3230ce9832682d8f521071d8a34a2b (go 1.22 dev. Thu Oct 5 12:20:11 2023 -0700)
        fmt.Println("Forking ...")
        defer fmt.Println("Done ...")
 
index 064b091073058ea13be332ac21d1b275fe68c2d3..fef952fa6095beb402965e2dd9865ae1eb2e5c1c 100644 (file)
@@ -6,13 +6,14 @@ package fmtsort_test
 
 import (
        "fmt"
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
        "math"
        "reflect"
        "sort"
        "strings"
        "testing"
        "unsafe"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
 )
 
 var compareTests = [][]reflect.Value{
@@ -38,7 +39,7 @@ var compareTests = [][]reflect.Value{
        ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]),
        ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}),
        ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}),
-       ct(reflect.TypeOf(any(any(0))), iFace, 1, 2, 3),
+       ct(reflect.TypeOf(any(0)), iFace, 1, 2, 3),
 }
 
 var iFace any
@@ -190,12 +191,15 @@ func sprintKey(key reflect.Value) string {
 var (
        ints  [3]int
        chans = makeChans()
+       // pin   runtime.Pinner
 )
 
 func makeChans() []chan int {
        cs := []chan int{make(chan int), make(chan int), make(chan int)}
        // Order channels by address. See issue #49431.
-       // TODO: pin these pointers once pinning is available (#46787).
+       for i := range cs {
+               reflect.ValueOf(cs[i]).UnsafePointer()
+       }
        sort.Slice(cs, func(i, j int) bool {
                return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer())
        })
index 061f17ddbdf98d39143f0e84b26de2a3c45a7adf..3acb9bdf0e23180e6b0d64075b238f3f27dfc4ea 100644 (file)
@@ -22,10 +22,15 @@ type context struct {
        delim   delim
        urlPart urlPart
        jsCtx   jsCtx
-       attr    attr
-       element element
-       n       parse.Node // for range break/continue
-       err     *Error
+       // jsBraceDepth contains the current depth, for each JS template literal
+       // string interpolation expression, of braces we've seen. This is used to
+       // determine if the next } will close a JS template literal string
+       // interpolation expression or not.
+       jsBraceDepth []int
+       attr         attr
+       element      element
+       n            parse.Node // for range break/continue
+       err          *Error
 }
 
 func (c context) String() string {
@@ -121,8 +126,8 @@ const (
        stateJSDqStr
        // stateJSSqStr occurs inside a JavaScript single quoted string.
        stateJSSqStr
-       // stateJSBqStr occurs inside a JavaScript back quoted string.
-       stateJSBqStr
+       // stateJSTmplLit occurs inside a JavaScript back quoted string.
+       stateJSTmplLit
        // stateJSRegexp occurs inside a JavaScript regexp literal.
        stateJSRegexp
        // stateJSBlockCmt occurs inside a JavaScript /* block comment */.
@@ -176,14 +181,14 @@ func isInTag(s state) bool {
 }
 
 // isInScriptLiteral returns true if s is one of the literal states within a
-// <script> tag, and as such occurances of "<!--", "<script", and "</script"
+// <script> tag, and as such occurrences of "<!--", "<script", and "</script"
 // need to be treated specially.
 func isInScriptLiteral(s state) bool {
        // Ignore the comment states (stateJSBlockCmt, stateJSLineCmt,
        // stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already
        // omitted from the output.
        switch s {
-       case stateJSDqStr, stateJSSqStr, stateJSBqStr, stateJSRegexp:
+       case stateJSDqStr, stateJSSqStr, stateJSTmplLit, stateJSRegexp:
                return true
        }
        return false
index a4450a4a983e7f1a664112268cac47d957c1c56d..de4b4de1e5306c684fb4bc1df41d8a42db555988 100644 (file)
@@ -222,6 +222,10 @@ const (
        // Discussion:
        //   Package html/template does not support actions inside of JS template
        //   literals.
+       //
+       // Deprecated: ErrJSTemplate is no longer returned when an action is present
+       // in a JS template literal. Actions inside of JS template literals are now
+       // escaped as expected.
        ErrJSTemplate
 )
 
index 5d47a83d3527dd9e5cb04f0aeee3ed9c23b6b497..334bbce0f3af5cfbc7fe6c95fd45e52614a44797 100644 (file)
@@ -8,8 +8,6 @@ import (
        "bytes"
        "fmt"
        "html"
-
-       //"internal/godebug"
        "io"
        "regexp"
 
@@ -64,22 +62,23 @@ func evalArgs(args ...any) string {
 
 // funcMap maps command names to functions that render their inputs safe.
 var funcMap = template.FuncMap{
-       "_html_template_attrescaper":     attrEscaper,
-       "_html_template_commentescaper":  commentEscaper,
-       "_html_template_cssescaper":      cssEscaper,
-       "_html_template_cssvaluefilter":  cssValueFilter,
-       "_html_template_htmlnamefilter":  htmlNameFilter,
-       "_html_template_htmlescaper":     htmlEscaper,
-       "_html_template_jsregexpescaper": jsRegexpEscaper,
-       "_html_template_jsstrescaper":    jsStrEscaper,
-       "_html_template_jsvalescaper":    jsValEscaper,
-       "_html_template_nospaceescaper":  htmlNospaceEscaper,
-       "_html_template_rcdataescaper":   rcdataEscaper,
-       "_html_template_srcsetescaper":   srcsetFilterAndEscaper,
-       "_html_template_urlescaper":      urlEscaper,
-       "_html_template_urlfilter":       urlFilter,
-       "_html_template_urlnormalizer":   urlNormalizer,
-       "_eval_args_":                    evalArgs,
+       "_html_template_attrescaper":      attrEscaper,
+       "_html_template_commentescaper":   commentEscaper,
+       "_html_template_cssescaper":       cssEscaper,
+       "_html_template_cssvaluefilter":   cssValueFilter,
+       "_html_template_htmlnamefilter":   htmlNameFilter,
+       "_html_template_htmlescaper":      htmlEscaper,
+       "_html_template_jsregexpescaper":  jsRegexpEscaper,
+       "_html_template_jsstrescaper":     jsStrEscaper,
+       "_html_template_jstmpllitescaper": jsTmplLitEscaper,
+       "_html_template_jsvalescaper":     jsValEscaper,
+       "_html_template_nospaceescaper":   htmlNospaceEscaper,
+       "_html_template_rcdataescaper":    rcdataEscaper,
+       "_html_template_srcsetescaper":    srcsetFilterAndEscaper,
+       "_html_template_urlescaper":       urlEscaper,
+       "_html_template_urlfilter":        urlFilter,
+       "_html_template_urlnormalizer":    urlNormalizer,
+       "_eval_args_":                     evalArgs,
 }
 
 // escaper collects type inferences about templates and changes needed to make
@@ -164,7 +163,6 @@ func (e *escaper) escape(c context, n parse.Node) context {
        panic("escaping " + n.String() + " is unimplemented")
 }
 
-// Modified by Hugo.
 // var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")
 
 // escapeAction escapes an action template node.
@@ -230,16 +228,8 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
                c.jsCtx = jsCtxDivOp
        case stateJSDqStr, stateJSSqStr:
                s = append(s, "_html_template_jsstrescaper")
-       case stateJSBqStr:
-               if SecurityAllowActionJSTmpl.Load() {
-                       // debugAllowActionJSTmpl.IncNonDefault()
-                       s = append(s, "_html_template_jsstrescaper")
-               } else {
-                       return context{
-                               state: stateError,
-                               err:   errorf(ErrJSTemplate, n, n.Line, "%s appears in a JS template literal", n),
-                       }
-               }
+       case stateJSTmplLit:
+               s = append(s, "_html_template_jstmpllitescaper")
        case stateJSRegexp:
                s = append(s, "_html_template_jsregexpescaper")
        case stateCSS:
@@ -398,6 +388,9 @@ var redundantFuncs = map[string]map[string]bool{
        "_html_template_jsstrescaper": {
                "_html_template_attrescaper": true,
        },
+       "_html_template_jstmpllitescaper": {
+               "_html_template_attrescaper": true,
+       },
        "_html_template_urlescaper": {
                "_html_template_urlnormalizer": true,
        },
index 4ad5316fb87340e6e58d31cd43e8df9341cc8c53..b202afde0bc1f12e14ae7bf6b80906178ad489f0 100644 (file)
@@ -35,14 +35,14 @@ func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
 
 func TestEscape(t *testing.T) {
        data := struct {
-               F, T    bool
-               C, G, H string
-               A, E    []string
-               B, M    json.Marshaler
-               N       int
-               U       any  // untyped nil
-               Z       *int // typed nil
-               W       htmltemplate.HTML
+               F, T       bool
+               C, G, H, I string
+               A, E       []string
+               B, M       json.Marshaler
+               N          int
+               U          any  // untyped nil
+               Z          *int // typed nil
+               W          htmltemplate.HTML
        }{
                F: false,
                T: true,
@@ -57,6 +57,7 @@ func TestEscape(t *testing.T) {
                U: nil,
                Z: nil,
                W: htmltemplate.HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
+               I: "${ asd `` }",
        }
        pdata := &data
 
@@ -723,6 +724,21 @@ func TestEscape(t *testing.T) {
                        "<p name=\"{{.U}}\">",
                        "<p name=\"\">",
                },
+               {
+                       "JS template lit special characters",
+                       "<script>var a = `{{.I}}`</script>",
+                       "<script>var a = `\\u0024\\u007b asd \\u0060\\u0060 \\u007d`</script>",
+               },
+               {
+                       "JS template lit special characters, nested lit",
+                       "<script>var a = `${ `{{.I}}` }`</script>",
+                       "<script>var a = `${ `\\u0024\\u007b asd \\u0060\\u0060 \\u007d` }`</script>",
+               },
+               {
+                       "JS template lit, nested JS",
+                       "<script>var a = `${ var a = \"{{\"a \\\" d\"}}\" }`</script>",
+                       "<script>var a = `${ var a = \"a \\u0022 d\" }`</script>",
+               },
        }
 
        for _, test := range tests {
@@ -981,6 +997,31 @@ func TestErrors(t *testing.T) {
                        "<script>var a = `${a+b}`</script>`",
                        "",
                },
+               {
+                       "<script>var tmpl = `asd`;</script>",
+                       ``,
+               },
+               {
+                       "<script>var tmpl = `${1}`;</script>",
+                       ``,
+               },
+               {
+                       "<script>var tmpl = `${return ``}`;</script>",
+                       ``,
+               },
+               {
+                       "<script>var tmpl = `${return {{.}} }`;</script>",
+                       ``,
+               },
+               {
+                       "<script>var tmpl = `${ let a = {1:1} {{.}} }`;</script>",
+                       ``,
+               },
+               {
+                       "<script>var tmpl = `asd ${return \"{\"}`;</script>",
+                       ``,
+               },
+
                // Error cases.
                {
                        "{{if .Cond}}<a{{end}}",
@@ -1127,10 +1168,6 @@ func TestErrors(t *testing.T) {
                        // html is allowed since it is the last command in the pipeline, but urlquery is not.
                        `predefined escaper "urlquery" disallowed in template`,
                },
-               {
-                       "<script>var tmpl = `asd {{.}}`;</script>",
-                       `{{.}} appears in a JS template literal`,
-               },
        }
        for _, test := range tests {
                buf := new(bytes.Buffer)
@@ -1354,7 +1391,7 @@ func TestEscapeText(t *testing.T) {
                },
                {
                        "<a onclick=\"`foo",
-                       context{state: stateJSBqStr, delim: delimDoubleQuote, attr: attrScript},
+                       context{state: stateJSTmplLit, delim: delimDoubleQuote, attr: attrScript},
                },
                {
                        `<A ONCLICK="'`,
@@ -1696,6 +1733,94 @@ func TestEscapeText(t *testing.T) {
                        `<svg:a svg:onclick="x()">`,
                        context{},
                },
+               {
+                       "<script>var a = `",
+                       context{state: stateJSTmplLit, element: elementScript},
+               },
+               {
+                       "<script>var a = `${",
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       "<script>var a = `${}",
+                       context{state: stateJSTmplLit, element: elementScript},
+               },
+               {
+                       "<script>var a = `${`",
+                       context{state: stateJSTmplLit, element: elementScript},
+               },
+               {
+                       "<script>var a = `${var a = \"",
+                       context{state: stateJSDqStr, element: elementScript},
+               },
+               {
+                       "<script>var a = `${var a = \"`",
+                       context{state: stateJSDqStr, element: elementScript},
+               },
+               {
+                       "<script>var a = `${var a = \"}",
+                       context{state: stateJSDqStr, element: elementScript},
+               },
+               {
+                       "<script>var a = `${``",
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       "<script>var a = `${`}",
+                       context{state: stateJSTmplLit, element: elementScript},
+               },
+               {
+                       "<script>`${ {} } asd`</script><script>`${ {} }",
+                       context{state: stateJSTmplLit, element: elementScript},
+               },
+               {
+                       "<script>var foo = `${ (_ => { return \"x\" })() + \"${",
+                       context{state: stateJSDqStr, element: elementScript},
+               },
+               {
+                       "<script>var a = `${ {</script><script>var b = `${ x }",
+                       context{state: stateJSTmplLit, element: elementScript, jsCtx: jsCtxDivOp},
+               },
+               {
+                       "<script>var foo = `x` + \"${",
+                       context{state: stateJSDqStr, element: elementScript},
+               },
+               {
+                       "<script>function f() { var a = `${}`; }",
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       "<script>{`${}`}",
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       "<script>`${ function f() { return `${1}` }() }`",
+                       context{state: stateJS, element: elementScript, jsCtx: jsCtxDivOp},
+               },
+               {
+                       "<script>function f() {`${ function f() { `${1}` } }`}",
+                       context{state: stateJS, element: elementScript, jsCtx: jsCtxDivOp},
+               },
+               {
+                       "<script>`${ { `` }",
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       "<script>`${ { }`",
+                       context{state: stateJSTmplLit, element: elementScript},
+               },
+               {
+                       "<script>var foo = `${ foo({ a: { c: `${",
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       "<script>var foo = `${ foo({ a: { c: `${ {{.}} }` }, b: ",
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       "<script>`${ `}",
+                       context{state: stateJSTmplLit, element: elementScript},
+               },
        }
 
        for _, test := range tests {
index 8f9b893b4ecaf3cc301450736da2cba3ec2dc0fe..e91aeafa15706a194b5c8413537e13ca86d72df6 100644 (file)
@@ -325,12 +325,16 @@ var execTests = []execTest{
        {"$.U.V", "{{$.U.V}}", "v", tVal, true},
        {"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
        {"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
-       {"nested assignment",
+       {
+               "nested assignment",
                "{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
-               "3", tVal, true},
-       {"nested assignment changes the last declaration",
+               "3", tVal, true,
+       },
+       {
+               "nested assignment changes the last declaration",
                "{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
-               "1", tVal, true},
+               "1", tVal, true,
+       },
 
        // Type with String method.
        {"V{6666}.String()", "-{{.V0}}-", "-{6666}-", tVal, true}, //  NOTE: -<6666>- in text/template
@@ -377,15 +381,21 @@ var execTests = []execTest{
        {".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: &lt;nil&gt;-", tVal, true},
        {".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: &lt;nil&gt;-", tVal, true},
        {"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
-       {"method on chained var",
+       {
+               "method on chained var",
                "{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
-               "true", tVal, true},
-       {"chained method",
+               "true", tVal, true,
+       },
+       {
+               "chained method",
                "{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
-               "true", tVal, true},
-       {"chained method on variable",
+               "true", tVal, true,
+       },
+       {
+               "chained method on variable",
                "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
-               "true", tVal, true},
+               "true", tVal, true,
+       },
        {".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
        {".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
        {"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
@@ -471,10 +481,14 @@ var execTests = []execTest{
        {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
 
        // HTML.
-       {"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
-               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
-       {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
-               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
+       {
+               "html", `{{html "<script>alert(\"XSS\");</script>"}}`,
+               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true,
+       },
+       {
+               "html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
+               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true,
+       },
        {"html", `{{html .PS}}`, "a string", tVal, true},
        {"html typed nil", `{{html .NIL}}`, "&lt;nil&gt;", tVal, true},
        {"html untyped nil", `{{html .Empty0}}`, "&lt;nil&gt;", tVal, true}, // NOTE: "&lt;no value&gt;" in text/template
@@ -838,7 +852,7 @@ var delimPairs = []string{
 
 func TestDelims(t *testing.T) {
        const hello = "Hello, world"
-       var value = struct{ Str string }{hello}
+       value := struct{ Str string }{hello}
        for i := 0; i < len(delimPairs); i += 2 {
                text := ".Str"
                left := delimPairs[i+0]
@@ -861,7 +875,7 @@ func TestDelims(t *testing.T) {
                if err != nil {
                        t.Fatalf("delim %q text %q parse err %s", left, text, err)
                }
-               var b = new(strings.Builder)
+               b := new(strings.Builder)
                err = tmpl.Execute(b, value)
                if err != nil {
                        t.Fatalf("delim %q exec err %s", left, err)
@@ -962,7 +976,7 @@ const treeTemplate = `
 `
 
 func TestTree(t *testing.T) {
-       var tree = &Tree{
+       tree := &Tree{
                1,
                &Tree{
                        2, &Tree{
@@ -1213,7 +1227,7 @@ var cmpTests = []cmpTest{
 
 func TestComparison(t *testing.T) {
        b := new(strings.Builder)
-       var cmpStruct = struct {
+       cmpStruct := struct {
                Uthree, Ufour  uint
                NegOne, Three  int
                Ptr, NilPtr    *int
index 98e03ad3c469c432960043143842b1fed1c5bd17..99edf8f68167477411740e5ff13f9ad14f1a1356 100644 (file)
 package template
 
 import (
-       "sync/atomic"
-
        template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
 )
 
-// See https://github.com/golang/go/issues/59234
-// Moved here to avoid dependency on Go's internal/debug package.
-var SecurityAllowActionJSTmpl atomic.Bool
-
 /*
 
 This files contains the Hugo related addons. All the other files in this
index 3b5178eb12174f39b0edb8f54b79f0728ea6a580..79d9102e56579c64334fffdab381a4ad0d51a08f 100644 (file)
@@ -239,6 +239,11 @@ func jsStrEscaper(args ...any) string {
        return replace(s, jsStrReplacementTable)
 }
 
+func jsTmplLitEscaper(args ...any) string {
+       s, _ := stringify(args...)
+       return replace(s, jsBqStrReplacementTable)
+}
+
 // jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
 // specials so the result is treated literally when included in a regular
 // expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
@@ -325,6 +330,31 @@ var jsStrReplacementTable = []string{
        '\\': `\\`,
 }
 
+// jsBqStrReplacementTable is like jsStrReplacementTable except it also contains
+// the special characters for JS template literals: $, {, and }.
+var jsBqStrReplacementTable = []string{
+       0:    `\u0000`,
+       '\t': `\t`,
+       '\n': `\n`,
+       '\v': `\u000b`, // "\v" == "v" on IE 6.
+       '\f': `\f`,
+       '\r': `\r`,
+       // Encode HTML specials as hex so the output can be embedded
+       // in HTML attributes without further encoding.
+       '"':  `\u0022`,
+       '`':  `\u0060`,
+       '&':  `\u0026`,
+       '\'': `\u0027`,
+       '+':  `\u002b`,
+       '/':  `\/`,
+       '<':  `\u003c`,
+       '>':  `\u003e`,
+       '\\': `\\`,
+       '$':  `\u0024`,
+       '{':  `\u007b`,
+       '}':  `\u007d`,
+}
+
 // jsStrNormReplacementTable is like jsStrReplacementTable but does not
 // overencode existing escapes since this table has no entry for `\`.
 var jsStrNormReplacementTable = []string{
@@ -345,6 +375,7 @@ var jsStrNormReplacementTable = []string{
        '<':  `\u003c`,
        '>':  `\u003e`,
 }
+
 var jsRegexpReplacementTable = []string{
        0:    `\u0000`,
        '\t': `\t`,
index be7a9205111f238c6d86fabd40a750247251e96a..eed1e8bcc018c22bb99abb70f2265d956e9fdcdf 100644 (file)
@@ -21,7 +21,7 @@ func _() {
        _ = x[stateJS-10]
        _ = x[stateJSDqStr-11]
        _ = x[stateJSSqStr-12]
-       _ = x[stateJSBqStr-13]
+       _ = x[stateJSTmplLit-13]
        _ = x[stateJSRegexp-14]
        _ = x[stateJSBlockCmt-15]
        _ = x[stateJSLineCmt-16]
@@ -39,9 +39,9 @@ func _() {
        _ = x[stateDead-28]
 }
 
-const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSBqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead"
+const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSTmplLitstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead"
 
-var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 154, 167, 182, 196, 214, 233, 241, 254, 267, 280, 293, 304, 320, 335, 345, 354}
+var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 156, 169, 184, 198, 216, 235, 243, 256, 269, 282, 295, 306, 322, 337, 347, 356}
 
 func (i state) String() string {
        if i >= state(len(_state_index)-1) {
index 432c365d3c381904a8fa1221bdeeb5dd80a04ea1..d5a05f66da406da9f4eb3ebfc36499115c7e42a1 100644 (file)
@@ -27,8 +27,8 @@ var transitionFunc = [...]func(context, []byte) (context, int){
        stateJS:             tJS,
        stateJSDqStr:        tJSDelimited,
        stateJSSqStr:        tJSDelimited,
-       stateJSBqStr:        tJSDelimited,
        stateJSRegexp:       tJSDelimited,
+       stateJSTmplLit:      tJSTmpl,
        stateJSBlockCmt:     tBlockCmt,
        stateJSLineCmt:      tLineCmt,
        stateJSHTMLOpenCmt:  tLineCmt,
@@ -270,7 +270,7 @@ func tURL(c context, s []byte) (context, int) {
 
 // tJS is the context transition function for the JS state.
 func tJS(c context, s []byte) (context, int) {
-       i := bytes.IndexAny(s, "\"`'/<-#")
+       i := bytes.IndexAny(s, "\"`'/{}<-#")
        if i == -1 {
                // Entire input is non string, comment, regexp tokens.
                c.jsCtx = nextJSCtx(s, c.jsCtx)
@@ -283,7 +283,7 @@ func tJS(c context, s []byte) (context, int) {
        case '\'':
                c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
        case '`':
-               c.state, c.jsCtx = stateJSBqStr, jsCtxRegexp
+               c.state, c.jsCtx = stateJSTmplLit, jsCtxRegexp
        case '/':
                switch {
                case i+1 < len(s) && s[i+1] == '/':
@@ -320,12 +320,66 @@ func tJS(c context, s []byte) (context, int) {
                if i+1 < len(s) && s[i+1] == '!' {
                        c.state, i = stateJSLineCmt, i+1
                }
+       case '{':
+               // We only care about tracking brace depth if we are inside of a
+               // template literal.
+               if len(c.jsBraceDepth) == 0 {
+                       return c, i + 1
+               }
+               c.jsBraceDepth[len(c.jsBraceDepth)-1]++
+       case '}':
+               if len(c.jsBraceDepth) == 0 {
+                       return c, i + 1
+               }
+               // There are no cases where a brace can be escaped in the JS context
+               // that are not syntax errors, it seems. Because of this we can just
+               // count "\}" as "}" and move on, the script is already broken as
+               // fully fledged parsers will just fail anyway.
+               c.jsBraceDepth[len(c.jsBraceDepth)-1]--
+               if c.jsBraceDepth[len(c.jsBraceDepth)-1] >= 0 {
+                       return c, i + 1
+               }
+               c.jsBraceDepth = c.jsBraceDepth[:len(c.jsBraceDepth)-1]
+               c.state = stateJSTmplLit
        default:
                panic("unreachable")
        }
        return c, i + 1
 }
 
+func tJSTmpl(c context, s []byte) (context, int) {
+       var k int
+       for {
+               i := k + bytes.IndexAny(s[k:], "`\\$")
+               if i < k {
+                       break
+               }
+               switch s[i] {
+               case '\\':
+                       i++
+                       if i == len(s) {
+                               return context{
+                                       state: stateError,
+                                       err:   errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in JS string: %q", s),
+                               }, len(s)
+                       }
+               case '$':
+                       if len(s) >= i+2 && s[i+1] == '{' {
+                               c.jsBraceDepth = append(c.jsBraceDepth, 0)
+                               c.state = stateJS
+                               return c, i + 2
+                       }
+               case '`':
+                       // end
+                       c.state = stateJS
+                       return c, i + 1
+               }
+               k = i + 1
+       }
+
+       return c, len(s)
+}
+
 // tJSDelimited is the context transition function for the JS string and regexp
 // states.
 func tJSDelimited(c context, s []byte) (context, int) {
@@ -333,8 +387,6 @@ func tJSDelimited(c context, s []byte) (context, int) {
        switch c.state {
        case stateJSSqStr:
                specials = `\'`
-       case stateJSBqStr:
-               specials = "`\\"
        case stateJSRegexp:
                specials = `\/[]`
        }
index 88791b3b460866cc47f06f104f6127b64fedf281..14460224800bdaa1ab988c2a3cbd80f820e97104 100644 (file)
@@ -60,13 +60,6 @@ func tryExec() error {
        // may as well use the same path so that this branch can be tested without
        // an ios environment.
 
-       /*if !testing.Testing() {
-               // This isn't a standard 'go test' binary, so we don't know how to
-               // self-exec in a way that should succeed without side effects.
-               // Just forget it.
-               return errors.New("can't probe for exec support with a non-test executable")
-       }*/
-
        // We know that this is a test executable. We should be able to run it with a
        // no-op flag to check for overall exec support.
        exe, err := os.Executable()
@@ -99,11 +92,14 @@ func MustHaveExecPath(t testing.TB, path string) {
 // CleanCmdEnv will fill cmd.Env with the environment, excluding certain
 // variables that could modify the behavior of the Go tools such as
 // GODEBUG and GOTRACEBACK.
+//
+// If the caller wants to set cmd.Dir, set it before calling this function,
+// so PWD will be set correctly in the environment.
 func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd {
        if cmd.Env != nil {
                panic("environment already set")
        }
-       for _, env := range os.Environ() {
+       for _, env := range cmd.Environ() {
                // Exclude GODEBUG from the environment to prevent its output
                // from breaking tests that are trying to parse other command output.
                if strings.HasPrefix(env, "GODEBUG=") {
index 2430ae6cf3b1dff471f1c4754dc1e8d4f0e7d662..97d897a1f3c3bc18e36b9c302162aa4102b8ac2e 100644 (file)
@@ -15,10 +15,6 @@ import (
        "errors"
        "flag"
        "fmt"
-
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
-
-       //"internal/platform"
        "os"
        "os/exec"
        "path/filepath"
@@ -27,6 +23,8 @@ import (
        "strings"
        "sync"
        "testing"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
 )
 
 // Save the original environment during init for use in checks. A test
@@ -45,8 +43,8 @@ func Builder() string {
 
 // HasGoBuild reports whether the current system can build programs with “go build”
 // and then run them with os.StartProcess or exec.Command.
+// Modified by Hugo (not needed)
 func HasGoBuild() bool {
-       // Modified by Hugo (not needed)
        return false
 }
 
@@ -69,13 +67,13 @@ func MustHaveGoBuild(t testing.TB) {
        }
 }
 
-// HasGoRun reports whether the current system can run programs with “go run.”
+// HasGoRun reports whether the current system can run programs with “go run”.
 func HasGoRun() bool {
        // For now, having go run and having go build are the same.
        return HasGoBuild()
 }
 
-// MustHaveGoRun checks that the current system can run programs with “go run.”
+// MustHaveGoRun checks that the current system can run programs with “go run”.
 // If not, MustHaveGoRun calls t.Skip with an explanation.
 func MustHaveGoRun(t testing.TB) {
        if !HasGoRun() {
@@ -300,8 +298,8 @@ func MustHaveCGO(t testing.TB) {
 
 // CanInternalLink reports whether the current system can link programs with
 // internal linking.
+// Modified by Hugo (not needed)
 func CanInternalLink(withCgo bool) bool {
-       // Modified by Hugo (not needed)
        return false
 }
 
@@ -320,8 +318,8 @@ func MustInternalLink(t testing.TB, withCgo bool) {
 // MustHaveBuildMode reports whether the current system can build programs in
 // the given build mode.
 // If not, MustHaveBuildMode calls t.Skip with an explanation.
+// Modified by Hugo (not needed)
 func MustHaveBuildMode(t testing.TB, buildmode string) {
-       // Modified by Hugo (not needed)
 }
 
 // HasSymlink reports whether the current system can use os.Symlink.
@@ -438,7 +436,7 @@ func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string
                }
        }
 
-       if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil {
+       if err := os.WriteFile(dstPath, icfg.Bytes(), 0o666); err != nil {
                t.Fatal(err)
        }
 }
index 9d55f6258a9fd0f876a8fda77c9b047f13450b6b..916f1815379cb2d46471a4ea2ad9bdfc0767b4a6 100644 (file)
@@ -7,6 +7,8 @@
 package testenv
 
 import (
+       "errors"
+       "io/fs"
        "os"
 )
 
@@ -15,6 +17,5 @@ import (
 var Sigquit = os.Kill
 
 func syscallIsNotSupported(err error) bool {
-       // Removed by Hugo (not supported in Go 1.20).
-       return false
+       return errors.Is(err, fs.ErrPermission)
 }
index 564bff1abdd30fc68006aacbf8216acf089f9d2e..13e11abfb534b0d82d3e094e10424cec29d51451 100644 (file)
@@ -5,13 +5,13 @@
 package testenv_test
 
 import (
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
-       //"internal/platform"
        "os"
        "path/filepath"
        "runtime"
        "strings"
        "testing"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
 )
 
 func TestGoToolLocation(t *testing.T) {
@@ -83,3 +83,26 @@ func TestMustHaveExec(t *testing.T) {
                }
        }
 }
+
+func TestCleanCmdEnvPWD(t *testing.T) {
+       // Test that CleanCmdEnv sets PWD if cmd.Dir is set.
+       switch runtime.GOOS {
+       case "plan9", "windows":
+               t.Skipf("PWD is not used on %s", runtime.GOOS)
+       }
+       dir := t.TempDir()
+       cmd := testenv.Command(t, testenv.GoToolPath(t), "help")
+       cmd.Dir = dir
+       cmd = testenv.CleanCmdEnv(cmd)
+
+       for _, env := range cmd.Env {
+               if strings.HasPrefix(env, "PWD=") {
+                       pwd := strings.TrimPrefix(env, "PWD=")
+                       if pwd != dir {
+                               t.Errorf("unexpected PWD: want %s, got %s", dir, pwd)
+                       }
+                       return
+               }
+       }
+       t.Error("PWD not set in cmd.Env")
+}
index abb89eeca06925d4915ad4852538c6944d39e243..296eefc7a711c68f855ee3e3a9598df4614a1baf 100644 (file)
@@ -7,6 +7,8 @@
 package testenv
 
 import (
+       "errors"
+       "io/fs"
        "syscall"
 )
 
@@ -15,6 +17,27 @@ import (
 var Sigquit = syscall.SIGQUIT
 
 func syscallIsNotSupported(err error) bool {
-       // Removed by Hugo (not supported in Go 1.20)
+       if err == nil {
+               return false
+       }
+
+       var errno syscall.Errno
+       if errors.As(err, &errno) {
+               switch errno {
+               case syscall.EPERM, syscall.EROFS:
+                       // User lacks permission: either the call requires root permission and the
+                       // user is not root, or the call is denied by a container security policy.
+                       return true
+               case syscall.EINVAL:
+                       // Some containers return EINVAL instead of EPERM if a system call is
+                       // denied by security policy.
+                       return true
+               }
+       }
+
+       if errors.Is(err, fs.ErrPermission) {
+               return true
+       }
+
        return false
 }
index 4c01b05ebfe5423e128d539c90886b75df9df598..032784bc3f0c08fc8c69363198cbf71381556edc 100644 (file)
@@ -438,13 +438,13 @@ produce the text
 By construction, a template may reside in only one association. If it's
 necessary to have a template addressable from multiple associations, the
 template definition must be parsed multiple times to create distinct *Template
-values, or must be copied with the Clone or AddParseTree method.
+values, or must be copied with [Template.Clone] or [Template.AddParseTree].
 
 Parse may be called multiple times to assemble the various associated templates;
-see the ParseFiles and ParseGlob functions and methods for simple ways to parse
-related templates stored in files.
+see [ParseFiles], [ParseGlob], [Template.ParseFiles] and [Template.ParseGlob]
+for simple ways to parse related templates stored in files.
 
-A template may be executed directly or through ExecuteTemplate, which executes
+A template may be executed directly or through [Template.ExecuteTemplate], which executes
 an associated template identified by name. To invoke our example above, we
 might write,
 
index 597866c6866ee18a621fc2dfcc6a04d345a8d6bf..73153c7648d060c91e4ba2c1e98c0277e57ba7ea 100644 (file)
@@ -7,12 +7,13 @@ package template
 import (
        "errors"
        "fmt"
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
-       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
        "io"
        "reflect"
        "runtime"
        "strings"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 )
 
 // maxExecDepth specifies the maximum stack depth of templates within
index b8b3f3bbe19e0448843f69aeb7ec225841456afa..030e20ca0a4bedc0e85a2709c7082366bd946cac 100644 (file)
@@ -320,12 +320,16 @@ var execTests = []execTest{
        {"$.U.V", "{{$.U.V}}", "v", tVal, true},
        {"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
        {"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
-       {"nested assignment",
+       {
+               "nested assignment",
                "{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
-               "3", tVal, true},
-       {"nested assignment changes the last declaration",
+               "3", tVal, true,
+       },
+       {
+               "nested assignment changes the last declaration",
                "{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
-               "1", tVal, true},
+               "1", tVal, true,
+       },
 
        // Type with String method.
        {"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true},
@@ -372,15 +376,21 @@ var execTests = []execTest{
        {".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
        {".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true},
        {"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
-       {"method on chained var",
+       {
+               "method on chained var",
                "{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
-               "true", tVal, true},
-       {"chained method",
+               "true", tVal, true,
+       },
+       {
+               "chained method",
                "{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
-               "true", tVal, true},
-       {"chained method on variable",
+               "true", tVal, true,
+       },
+       {
+               "chained method on variable",
                "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
-               "true", tVal, true},
+               "true", tVal, true,
+       },
        {".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
        {".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
        {"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
@@ -466,10 +476,14 @@ var execTests = []execTest{
        {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
 
        // HTML.
-       {"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
-               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
-       {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
-               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
+       {
+               "html", `{{html "<script>alert(\"XSS\");</script>"}}`,
+               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true,
+       },
+       {
+               "html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
+               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true,
+       },
        {"html", `{{html .PS}}`, "a string", tVal, true},
        {"html typed nil", `{{html .NIL}}`, "&lt;nil&gt;", tVal, true},
        {"html untyped nil", `{{html .Empty0}}`, "&lt;no value&gt;", tVal, true},
@@ -844,7 +858,7 @@ var delimPairs = []string{
 
 func TestDelims(t *testing.T) {
        const hello = "Hello, world"
-       var value = struct{ Str string }{hello}
+       value := struct{ Str string }{hello}
        for i := 0; i < len(delimPairs); i += 2 {
                text := ".Str"
                left := delimPairs[i+0]
@@ -867,7 +881,7 @@ func TestDelims(t *testing.T) {
                if err != nil {
                        t.Fatalf("delim %q text %q parse err %s", left, text, err)
                }
-               var b = new(strings.Builder)
+               b := new(strings.Builder)
                err = tmpl.Execute(b, value)
                if err != nil {
                        t.Fatalf("delim %q exec err %s", left, err)
@@ -990,7 +1004,7 @@ const treeTemplate = `
 `
 
 func TestTree(t *testing.T) {
-       var tree = &Tree{
+       tree := &Tree{
                1,
                &Tree{
                        2, &Tree{
@@ -1243,7 +1257,7 @@ var cmpTests = []cmpTest{
 
 func TestComparison(t *testing.T) {
        b := new(strings.Builder)
-       var cmpStruct = struct {
+       cmpStruct := struct {
                Uthree, Ufour    uint
                NegOne, Three    int
                Ptr, NilPtr      *int
index b5a8c9ec50aa8daa19431a5a998872b4474962dc..a949f896fa3d5a5396f8a9750e7eee597ca76831 100644 (file)
@@ -478,7 +478,7 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
                        case k1 == uintKind && k2 == intKind:
                                truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int())
                        default:
-                               if arg1 != zero && arg != zero {
+                               if arg1.IsValid() && arg.IsValid() {
                                        return false, errBadComparison
                                }
                        }
index 70fdf2d3d21daeb5ca75a64fc8750aeb3a9ca292..61aaa993f056c0e1d67c7708ad6991a6dc8f3099 100644 (file)
@@ -169,13 +169,6 @@ func SetPageInContext(ctx context.Context, p page) context.Context {
        return context.WithValue(ctx, texttemplate.PageContextKey, p)
 }
 
-// SetSecurityAllowActionJSTmpl sets the global setting for allowing tempalte actions in JS template literals.
-// This was added in Hugo 0.114.0.
-// See https://github.com/golang/go/issues/59234
-func SetSecurityAllowActionJSTmpl(b bool) {
-       htmltemplate.SecurityAllowActionJSTmpl.Store(b)
-}
-
 type page interface {
        IsNode() bool
 }
index fa511fbab3542dd2ec276d06638dac15f1a9f4d3..1b2cffce6792cadea839d3580d8ead211edd8dca 100644 (file)
@@ -114,7 +114,6 @@ and2: true
 or2: true
 counter2: 3
 `)
-
 }
 
 // Issue 10495
@@ -163,7 +162,6 @@ title: "S3P1"
 }
 
 func TestGoTemplateBugs(t *testing.T) {
-
        t.Run("Issue 11112", func(t *testing.T) {
                t.Parallel()
 
@@ -188,11 +186,9 @@ func TestGoTemplateBugs(t *testing.T) {
 
                b.AssertFileContent("public/index.html", `key = value`)
        })
-
 }
 
 func TestSecurityAllowActionJSTmpl(t *testing.T) {
-
        filesTemplate := `
 -- config.toml --
 SECURITYCONFIG
@@ -211,20 +207,6 @@ var a = §§{{.Title }}§§;
                },
        ).BuildE()
 
-       b.Assert(err, qt.Not(qt.IsNil))
-       b.Assert(err.Error(), qt.Contains, "{{.Title}} appears in a JS template literal")
-
-       files = strings.ReplaceAll(filesTemplate, "SECURITYCONFIG", `
-[security]
-[security.gotemplates]
-allowActionJSTmpl = true               
-`)
-
-       b = hugolib.NewIntegrationTestBuilder(
-               hugolib.IntegrationTestConfig{
-                       T:           t,
-                       TxtarString: files,
-               },
-       ).Build()
-
+       // This used to fail, but not in >= Hugo 0.121.0.
+       b.Assert(err, qt.IsNil)
 }