mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Pull in the latest code from Go's template packages (#11771)
Fixes #10707 Fixes #11507
This commit is contained in:
parent
14d85ec136
commit
9f978d387f
25 changed files with 417 additions and 190 deletions
|
@ -34,7 +34,6 @@ import (
|
||||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||||
"github.com/gohugoio/hugo/modules"
|
"github.com/gohugoio/hugo/modules"
|
||||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||||
"github.com/gohugoio/hugo/tpl"
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -91,9 +90,6 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
|
||||||
return nil, fmt.Errorf("failed to init config: %w", err)
|
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)
|
loggers.InitGlobalLogger(d.Logger.Level(), configs.Base.PanicOnWarning)
|
||||||
|
|
||||||
return configs, nil
|
return configs, nil
|
||||||
|
|
|
@ -68,9 +68,6 @@ type Config struct {
|
||||||
|
|
||||||
// Allow inline shortcodes
|
// Allow inline shortcodes
|
||||||
EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
|
EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
|
||||||
|
|
||||||
// Go templates related security config.
|
|
||||||
GoTemplates GoTemplates `json:"goTemplates"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec holds os/exec policies.
|
// Exec holds os/exec policies.
|
||||||
|
@ -96,15 +93,6 @@ type HTTP struct {
|
||||||
MediaTypes Whitelist `json:"mediaTypes"`
|
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.
|
// ToTOML converts c to TOML with [security] as the root.
|
||||||
func (c Config) ToTOML() string {
|
func (c Config) ToTOML() string {
|
||||||
sec := c.ToSecurityMap()
|
sec := c.ToSecurityMap()
|
||||||
|
@ -127,7 +115,6 @@ func (c Config) CheckAllowedExec(name string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) CheckAllowedGetEnv(name string) error {
|
func (c Config) CheckAllowedGetEnv(name string) error {
|
||||||
|
@ -176,7 +163,6 @@ func (c Config) ToSecurityMap() map[string]any {
|
||||||
"security": m,
|
"security": m,
|
||||||
}
|
}
|
||||||
return sec
|
return sec
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeConfig creates a privacy Config from a given Hugo configuration.
|
// DecodeConfig creates a privacy Config from a given Hugo configuration.
|
||||||
|
@ -206,15 +192,14 @@ func DecodeConfig(cfg config.Provider) (Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return sc, nil
|
return sc, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
|
func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
|
||||||
return func(
|
return func(
|
||||||
f reflect.Type,
|
f reflect.Type,
|
||||||
t reflect.Type,
|
t reflect.Type,
|
||||||
data any) (any, error) {
|
data any,
|
||||||
|
) (any, error) {
|
||||||
if t != reflect.TypeOf(Whitelist{}) {
|
if t != reflect.TypeOf(Whitelist{}) {
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
@ -222,7 +207,6 @@ func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
|
||||||
wl := types.ToStringSlicePreserveString(data)
|
wl := types.ToStringSlicePreserveString(data)
|
||||||
|
|
||||||
return NewWhitelist(wl...)
|
return NewWhitelist(wl...)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,6 @@ getEnv=["a", "b"]
|
||||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||||
c.Assert(pc.Funcs.Getenv.Accept("a"), qt.IsTrue)
|
c.Assert(pc.Funcs.Getenv.Accept("a"), qt.IsTrue)
|
||||||
c.Assert(pc.Funcs.Getenv.Accept("c"), qt.IsFalse)
|
c.Assert(pc.Funcs.Getenv.Accept("c"), qt.IsFalse)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Run("String whitelist", func(c *qt.C) {
|
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.Allow.Accept("d"), qt.IsFalse)
|
||||||
c.Assert(pc.Exec.OsEnv.Accept("b"), qt.IsTrue)
|
c.Assert(pc.Exec.OsEnv.Accept("b"), qt.IsTrue)
|
||||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Run("Default exec.osEnv", func(c *qt.C) {
|
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.Allow.Accept("a"), qt.IsTrue)
|
||||||
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
||||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Run("Enable inline shortcodes, legacy", func(c *qt.C) {
|
c.Run("Enable inline shortcodes, legacy", func(c *qt.C) {
|
||||||
|
@ -129,9 +126,7 @@ osEnv="b"
|
||||||
pc, err := DecodeConfig(cfg)
|
pc, err := DecodeConfig(cfg)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
|
c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToTOML(t *testing.T) {
|
func TestToTOML(t *testing.T) {
|
||||||
|
@ -140,7 +135,7 @@ func TestToTOML(t *testing.T) {
|
||||||
got := DefaultConfig.ToTOML()
|
got := DefaultConfig.ToTOML()
|
||||||
|
|
||||||
c.Assert(got, qt.Equals,
|
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("a"), qt.IsFalse)
|
||||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||||
c.Assert(pc.Exec.OsEnv.Accept("MYSECRET"), qt.IsFalse)
|
c.Assert(pc.Exec.OsEnv.Accept("MYSECRET"), qt.IsFalse)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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 ...")
|
fmt.Println("Forking ...")
|
||||||
defer fmt.Println("Done ...")
|
defer fmt.Println("Done ...")
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,14 @@ package fmtsort_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
||||||
)
|
)
|
||||||
|
|
||||||
var compareTests = [][]reflect.Value{
|
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(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(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([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
|
var iFace any
|
||||||
|
@ -190,12 +191,15 @@ func sprintKey(key reflect.Value) string {
|
||||||
var (
|
var (
|
||||||
ints [3]int
|
ints [3]int
|
||||||
chans = makeChans()
|
chans = makeChans()
|
||||||
|
// pin runtime.Pinner
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeChans() []chan int {
|
func makeChans() []chan int {
|
||||||
cs := []chan int{make(chan int), make(chan int), make(chan int)}
|
cs := []chan int{make(chan int), make(chan int), make(chan int)}
|
||||||
// Order channels by address. See issue #49431.
|
// 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 {
|
sort.Slice(cs, func(i, j int) bool {
|
||||||
return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer())
|
return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer())
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,10 +22,15 @@ type context struct {
|
||||||
delim delim
|
delim delim
|
||||||
urlPart urlPart
|
urlPart urlPart
|
||||||
jsCtx jsCtx
|
jsCtx jsCtx
|
||||||
attr attr
|
// jsBraceDepth contains the current depth, for each JS template literal
|
||||||
element element
|
// string interpolation expression, of braces we've seen. This is used to
|
||||||
n parse.Node // for range break/continue
|
// determine if the next } will close a JS template literal string
|
||||||
err *Error
|
// interpolation expression or not.
|
||||||
|
jsBraceDepth []int
|
||||||
|
attr attr
|
||||||
|
element element
|
||||||
|
n parse.Node // for range break/continue
|
||||||
|
err *Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c context) String() string {
|
func (c context) String() string {
|
||||||
|
@ -121,8 +126,8 @@ const (
|
||||||
stateJSDqStr
|
stateJSDqStr
|
||||||
// stateJSSqStr occurs inside a JavaScript single quoted string.
|
// stateJSSqStr occurs inside a JavaScript single quoted string.
|
||||||
stateJSSqStr
|
stateJSSqStr
|
||||||
// stateJSBqStr occurs inside a JavaScript back quoted string.
|
// stateJSTmplLit occurs inside a JavaScript back quoted string.
|
||||||
stateJSBqStr
|
stateJSTmplLit
|
||||||
// stateJSRegexp occurs inside a JavaScript regexp literal.
|
// stateJSRegexp occurs inside a JavaScript regexp literal.
|
||||||
stateJSRegexp
|
stateJSRegexp
|
||||||
// stateJSBlockCmt occurs inside a JavaScript /* block comment */.
|
// 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
|
// 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.
|
// need to be treated specially.
|
||||||
func isInScriptLiteral(s state) bool {
|
func isInScriptLiteral(s state) bool {
|
||||||
// Ignore the comment states (stateJSBlockCmt, stateJSLineCmt,
|
// Ignore the comment states (stateJSBlockCmt, stateJSLineCmt,
|
||||||
// stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already
|
// stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already
|
||||||
// omitted from the output.
|
// omitted from the output.
|
||||||
switch s {
|
switch s {
|
||||||
case stateJSDqStr, stateJSSqStr, stateJSBqStr, stateJSRegexp:
|
case stateJSDqStr, stateJSSqStr, stateJSTmplLit, stateJSRegexp:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -222,6 +222,10 @@ const (
|
||||||
// Discussion:
|
// Discussion:
|
||||||
// Package html/template does not support actions inside of JS template
|
// Package html/template does not support actions inside of JS template
|
||||||
// literals.
|
// 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
|
ErrJSTemplate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
|
||||||
//"internal/godebug"
|
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
@ -64,22 +62,23 @@ func evalArgs(args ...any) string {
|
||||||
|
|
||||||
// funcMap maps command names to functions that render their inputs safe.
|
// funcMap maps command names to functions that render their inputs safe.
|
||||||
var funcMap = template.FuncMap{
|
var funcMap = template.FuncMap{
|
||||||
"_html_template_attrescaper": attrEscaper,
|
"_html_template_attrescaper": attrEscaper,
|
||||||
"_html_template_commentescaper": commentEscaper,
|
"_html_template_commentescaper": commentEscaper,
|
||||||
"_html_template_cssescaper": cssEscaper,
|
"_html_template_cssescaper": cssEscaper,
|
||||||
"_html_template_cssvaluefilter": cssValueFilter,
|
"_html_template_cssvaluefilter": cssValueFilter,
|
||||||
"_html_template_htmlnamefilter": htmlNameFilter,
|
"_html_template_htmlnamefilter": htmlNameFilter,
|
||||||
"_html_template_htmlescaper": htmlEscaper,
|
"_html_template_htmlescaper": htmlEscaper,
|
||||||
"_html_template_jsregexpescaper": jsRegexpEscaper,
|
"_html_template_jsregexpescaper": jsRegexpEscaper,
|
||||||
"_html_template_jsstrescaper": jsStrEscaper,
|
"_html_template_jsstrescaper": jsStrEscaper,
|
||||||
"_html_template_jsvalescaper": jsValEscaper,
|
"_html_template_jstmpllitescaper": jsTmplLitEscaper,
|
||||||
"_html_template_nospaceescaper": htmlNospaceEscaper,
|
"_html_template_jsvalescaper": jsValEscaper,
|
||||||
"_html_template_rcdataescaper": rcdataEscaper,
|
"_html_template_nospaceescaper": htmlNospaceEscaper,
|
||||||
"_html_template_srcsetescaper": srcsetFilterAndEscaper,
|
"_html_template_rcdataescaper": rcdataEscaper,
|
||||||
"_html_template_urlescaper": urlEscaper,
|
"_html_template_srcsetescaper": srcsetFilterAndEscaper,
|
||||||
"_html_template_urlfilter": urlFilter,
|
"_html_template_urlescaper": urlEscaper,
|
||||||
"_html_template_urlnormalizer": urlNormalizer,
|
"_html_template_urlfilter": urlFilter,
|
||||||
"_eval_args_": evalArgs,
|
"_html_template_urlnormalizer": urlNormalizer,
|
||||||
|
"_eval_args_": evalArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
// escaper collects type inferences about templates and changes needed to make
|
// 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")
|
panic("escaping " + n.String() + " is unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modified by Hugo.
|
|
||||||
// var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")
|
// var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")
|
||||||
|
|
||||||
// escapeAction escapes an action template node.
|
// escapeAction escapes an action template node.
|
||||||
|
@ -230,16 +228,8 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
|
||||||
c.jsCtx = jsCtxDivOp
|
c.jsCtx = jsCtxDivOp
|
||||||
case stateJSDqStr, stateJSSqStr:
|
case stateJSDqStr, stateJSSqStr:
|
||||||
s = append(s, "_html_template_jsstrescaper")
|
s = append(s, "_html_template_jsstrescaper")
|
||||||
case stateJSBqStr:
|
case stateJSTmplLit:
|
||||||
if SecurityAllowActionJSTmpl.Load() {
|
s = append(s, "_html_template_jstmpllitescaper")
|
||||||
// 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 stateJSRegexp:
|
case stateJSRegexp:
|
||||||
s = append(s, "_html_template_jsregexpescaper")
|
s = append(s, "_html_template_jsregexpescaper")
|
||||||
case stateCSS:
|
case stateCSS:
|
||||||
|
@ -398,6 +388,9 @@ var redundantFuncs = map[string]map[string]bool{
|
||||||
"_html_template_jsstrescaper": {
|
"_html_template_jsstrescaper": {
|
||||||
"_html_template_attrescaper": true,
|
"_html_template_attrescaper": true,
|
||||||
},
|
},
|
||||||
|
"_html_template_jstmpllitescaper": {
|
||||||
|
"_html_template_attrescaper": true,
|
||||||
|
},
|
||||||
"_html_template_urlescaper": {
|
"_html_template_urlescaper": {
|
||||||
"_html_template_urlnormalizer": true,
|
"_html_template_urlnormalizer": true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,14 +35,14 @@ func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
func TestEscape(t *testing.T) {
|
func TestEscape(t *testing.T) {
|
||||||
data := struct {
|
data := struct {
|
||||||
F, T bool
|
F, T bool
|
||||||
C, G, H string
|
C, G, H, I string
|
||||||
A, E []string
|
A, E []string
|
||||||
B, M json.Marshaler
|
B, M json.Marshaler
|
||||||
N int
|
N int
|
||||||
U any // untyped nil
|
U any // untyped nil
|
||||||
Z *int // typed nil
|
Z *int // typed nil
|
||||||
W htmltemplate.HTML
|
W htmltemplate.HTML
|
||||||
}{
|
}{
|
||||||
F: false,
|
F: false,
|
||||||
T: true,
|
T: true,
|
||||||
|
@ -57,6 +57,7 @@ func TestEscape(t *testing.T) {
|
||||||
U: nil,
|
U: nil,
|
||||||
Z: nil,
|
Z: nil,
|
||||||
W: htmltemplate.HTML(`¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
|
W: htmltemplate.HTML(`¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
|
||||||
|
I: "${ asd `` }",
|
||||||
}
|
}
|
||||||
pdata := &data
|
pdata := &data
|
||||||
|
|
||||||
|
@ -723,6 +724,21 @@ func TestEscape(t *testing.T) {
|
||||||
"<p name=\"{{.U}}\">",
|
"<p name=\"{{.U}}\">",
|
||||||
"<p name=\"\">",
|
"<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 {
|
for _, test := range tests {
|
||||||
|
@ -981,6 +997,31 @@ func TestErrors(t *testing.T) {
|
||||||
"<script>var a = `${a+b}`</script>`",
|
"<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.
|
// Error cases.
|
||||||
{
|
{
|
||||||
"{{if .Cond}}<a{{end}}",
|
"{{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.
|
// html is allowed since it is the last command in the pipeline, but urlquery is not.
|
||||||
`predefined escaper "urlquery" disallowed in template`,
|
`predefined escaper "urlquery" disallowed in template`,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"<script>var tmpl = `asd {{.}}`;</script>",
|
|
||||||
`{{.}} appears in a JS template literal`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
@ -1354,7 +1391,7 @@ func TestEscapeText(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"<a onclick=\"`foo",
|
"<a onclick=\"`foo",
|
||||||
context{state: stateJSBqStr, delim: delimDoubleQuote, attr: attrScript},
|
context{state: stateJSTmplLit, delim: delimDoubleQuote, attr: attrScript},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`<A ONCLICK="'`,
|
`<A ONCLICK="'`,
|
||||||
|
@ -1696,6 +1733,94 @@ func TestEscapeText(t *testing.T) {
|
||||||
`<svg:a svg:onclick="x()">`,
|
`<svg:a svg:onclick="x()">`,
|
||||||
context{},
|
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 {
|
for _, test := range tests {
|
||||||
|
|
|
@ -325,12 +325,16 @@ var execTests = []execTest{
|
||||||
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
||||||
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
||||||
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
||||||
{"nested assignment",
|
{
|
||||||
|
"nested assignment",
|
||||||
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
||||||
"3", tVal, true},
|
"3", tVal, true,
|
||||||
{"nested assignment changes the last declaration",
|
},
|
||||||
|
{
|
||||||
|
"nested assignment changes the last declaration",
|
||||||
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
||||||
"1", tVal, true},
|
"1", tVal, true,
|
||||||
|
},
|
||||||
|
|
||||||
// Type with String method.
|
// Type with String method.
|
||||||
{"V{6666}.String()", "-{{.V0}}-", "-{6666}-", tVal, true}, // NOTE: -<6666>- in text/template
|
{"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: <nil>-", tVal, true},
|
{".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
|
||||||
{".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-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 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}}",
|
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||||
"true", tVal, true},
|
"true", tVal, true,
|
||||||
{"chained method",
|
},
|
||||||
|
{
|
||||||
|
"chained method",
|
||||||
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||||
"true", tVal, true},
|
"true", tVal, true,
|
||||||
{"chained method on variable",
|
},
|
||||||
|
{
|
||||||
|
"chained method on variable",
|
||||||
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
|
"{{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 not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
|
||||||
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
||||||
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, 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},
|
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
|
||||||
|
|
||||||
// HTML.
|
// HTML.
|
||||||
{"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
{
|
||||||
"<script>alert("XSS");</script>", nil, true},
|
"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
||||||
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
"<script>alert("XSS");</script>", nil, true,
|
||||||
"<script>alert("XSS");</script>", nil, true},
|
},
|
||||||
|
{
|
||||||
|
"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||||
|
"<script>alert("XSS");</script>", nil, true,
|
||||||
|
},
|
||||||
{"html", `{{html .PS}}`, "a string", tVal, true},
|
{"html", `{{html .PS}}`, "a string", tVal, true},
|
||||||
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
||||||
{"html untyped nil", `{{html .Empty0}}`, "<nil>", tVal, true}, // NOTE: "<no value>" in text/template
|
{"html untyped nil", `{{html .Empty0}}`, "<nil>", tVal, true}, // NOTE: "<no value>" in text/template
|
||||||
|
@ -838,7 +852,7 @@ var delimPairs = []string{
|
||||||
|
|
||||||
func TestDelims(t *testing.T) {
|
func TestDelims(t *testing.T) {
|
||||||
const hello = "Hello, world"
|
const hello = "Hello, world"
|
||||||
var value = struct{ Str string }{hello}
|
value := struct{ Str string }{hello}
|
||||||
for i := 0; i < len(delimPairs); i += 2 {
|
for i := 0; i < len(delimPairs); i += 2 {
|
||||||
text := ".Str"
|
text := ".Str"
|
||||||
left := delimPairs[i+0]
|
left := delimPairs[i+0]
|
||||||
|
@ -861,7 +875,7 @@ func TestDelims(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("delim %q text %q parse err %s", left, text, err)
|
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)
|
err = tmpl.Execute(b, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("delim %q exec err %s", left, err)
|
t.Fatalf("delim %q exec err %s", left, err)
|
||||||
|
@ -962,7 +976,7 @@ const treeTemplate = `
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestTree(t *testing.T) {
|
func TestTree(t *testing.T) {
|
||||||
var tree = &Tree{
|
tree := &Tree{
|
||||||
1,
|
1,
|
||||||
&Tree{
|
&Tree{
|
||||||
2, &Tree{
|
2, &Tree{
|
||||||
|
@ -1213,7 +1227,7 @@ var cmpTests = []cmpTest{
|
||||||
|
|
||||||
func TestComparison(t *testing.T) {
|
func TestComparison(t *testing.T) {
|
||||||
b := new(strings.Builder)
|
b := new(strings.Builder)
|
||||||
var cmpStruct = struct {
|
cmpStruct := struct {
|
||||||
Uthree, Ufour uint
|
Uthree, Ufour uint
|
||||||
NegOne, Three int
|
NegOne, Three int
|
||||||
Ptr, NilPtr *int
|
Ptr, NilPtr *int
|
||||||
|
|
|
@ -14,15 +14,9 @@
|
||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
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
|
This files contains the Hugo related addons. All the other files in this
|
||||||
|
|
|
@ -239,6 +239,11 @@ func jsStrEscaper(args ...any) string {
|
||||||
return replace(s, jsStrReplacementTable)
|
return replace(s, jsStrReplacementTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jsTmplLitEscaper(args ...any) string {
|
||||||
|
s, _ := stringify(args...)
|
||||||
|
return replace(s, jsBqStrReplacementTable)
|
||||||
|
}
|
||||||
|
|
||||||
// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
|
// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
|
||||||
// specials so the result is treated literally when included in a regular
|
// specials so the result is treated literally when included in a regular
|
||||||
// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
|
// 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
|
// jsStrNormReplacementTable is like jsStrReplacementTable but does not
|
||||||
// overencode existing escapes since this table has no entry for `\`.
|
// overencode existing escapes since this table has no entry for `\`.
|
||||||
var jsStrNormReplacementTable = []string{
|
var jsStrNormReplacementTable = []string{
|
||||||
|
@ -345,6 +375,7 @@ var jsStrNormReplacementTable = []string{
|
||||||
'<': `\u003c`,
|
'<': `\u003c`,
|
||||||
'>': `\u003e`,
|
'>': `\u003e`,
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsRegexpReplacementTable = []string{
|
var jsRegexpReplacementTable = []string{
|
||||||
0: `\u0000`,
|
0: `\u0000`,
|
||||||
'\t': `\t`,
|
'\t': `\t`,
|
||||||
|
|
|
@ -21,7 +21,7 @@ func _() {
|
||||||
_ = x[stateJS-10]
|
_ = x[stateJS-10]
|
||||||
_ = x[stateJSDqStr-11]
|
_ = x[stateJSDqStr-11]
|
||||||
_ = x[stateJSSqStr-12]
|
_ = x[stateJSSqStr-12]
|
||||||
_ = x[stateJSBqStr-13]
|
_ = x[stateJSTmplLit-13]
|
||||||
_ = x[stateJSRegexp-14]
|
_ = x[stateJSRegexp-14]
|
||||||
_ = x[stateJSBlockCmt-15]
|
_ = x[stateJSBlockCmt-15]
|
||||||
_ = x[stateJSLineCmt-16]
|
_ = x[stateJSLineCmt-16]
|
||||||
|
@ -39,9 +39,9 @@ func _() {
|
||||||
_ = x[stateDead-28]
|
_ = 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 {
|
func (i state) String() string {
|
||||||
if i >= state(len(_state_index)-1) {
|
if i >= state(len(_state_index)-1) {
|
||||||
|
|
|
@ -27,8 +27,8 @@ var transitionFunc = [...]func(context, []byte) (context, int){
|
||||||
stateJS: tJS,
|
stateJS: tJS,
|
||||||
stateJSDqStr: tJSDelimited,
|
stateJSDqStr: tJSDelimited,
|
||||||
stateJSSqStr: tJSDelimited,
|
stateJSSqStr: tJSDelimited,
|
||||||
stateJSBqStr: tJSDelimited,
|
|
||||||
stateJSRegexp: tJSDelimited,
|
stateJSRegexp: tJSDelimited,
|
||||||
|
stateJSTmplLit: tJSTmpl,
|
||||||
stateJSBlockCmt: tBlockCmt,
|
stateJSBlockCmt: tBlockCmt,
|
||||||
stateJSLineCmt: tLineCmt,
|
stateJSLineCmt: tLineCmt,
|
||||||
stateJSHTMLOpenCmt: 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.
|
// tJS is the context transition function for the JS state.
|
||||||
func tJS(c context, s []byte) (context, int) {
|
func tJS(c context, s []byte) (context, int) {
|
||||||
i := bytes.IndexAny(s, "\"`'/<-#")
|
i := bytes.IndexAny(s, "\"`'/{}<-#")
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
// Entire input is non string, comment, regexp tokens.
|
// Entire input is non string, comment, regexp tokens.
|
||||||
c.jsCtx = nextJSCtx(s, c.jsCtx)
|
c.jsCtx = nextJSCtx(s, c.jsCtx)
|
||||||
|
@ -283,7 +283,7 @@ func tJS(c context, s []byte) (context, int) {
|
||||||
case '\'':
|
case '\'':
|
||||||
c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
|
c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
|
||||||
case '`':
|
case '`':
|
||||||
c.state, c.jsCtx = stateJSBqStr, jsCtxRegexp
|
c.state, c.jsCtx = stateJSTmplLit, jsCtxRegexp
|
||||||
case '/':
|
case '/':
|
||||||
switch {
|
switch {
|
||||||
case i+1 < len(s) && s[i+1] == '/':
|
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] == '!' {
|
if i+1 < len(s) && s[i+1] == '!' {
|
||||||
c.state, i = stateJSLineCmt, 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:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
return c, i + 1
|
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
|
// tJSDelimited is the context transition function for the JS string and regexp
|
||||||
// states.
|
// states.
|
||||||
func tJSDelimited(c context, s []byte) (context, int) {
|
func tJSDelimited(c context, s []byte) (context, int) {
|
||||||
|
@ -333,8 +387,6 @@ func tJSDelimited(c context, s []byte) (context, int) {
|
||||||
switch c.state {
|
switch c.state {
|
||||||
case stateJSSqStr:
|
case stateJSSqStr:
|
||||||
specials = `\'`
|
specials = `\'`
|
||||||
case stateJSBqStr:
|
|
||||||
specials = "`\\"
|
|
||||||
case stateJSRegexp:
|
case stateJSRegexp:
|
||||||
specials = `\/[]`
|
specials = `\/[]`
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,13 +60,6 @@ func tryExec() error {
|
||||||
// may as well use the same path so that this branch can be tested without
|
// may as well use the same path so that this branch can be tested without
|
||||||
// an ios environment.
|
// 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
|
// 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.
|
// no-op flag to check for overall exec support.
|
||||||
exe, err := os.Executable()
|
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
|
// CleanCmdEnv will fill cmd.Env with the environment, excluding certain
|
||||||
// variables that could modify the behavior of the Go tools such as
|
// variables that could modify the behavior of the Go tools such as
|
||||||
// GODEBUG and GOTRACEBACK.
|
// 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 {
|
func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd {
|
||||||
if cmd.Env != nil {
|
if cmd.Env != nil {
|
||||||
panic("environment already set")
|
panic("environment already set")
|
||||||
}
|
}
|
||||||
for _, env := range os.Environ() {
|
for _, env := range cmd.Environ() {
|
||||||
// Exclude GODEBUG from the environment to prevent its output
|
// Exclude GODEBUG from the environment to prevent its output
|
||||||
// from breaking tests that are trying to parse other command output.
|
// from breaking tests that are trying to parse other command output.
|
||||||
if strings.HasPrefix(env, "GODEBUG=") {
|
if strings.HasPrefix(env, "GODEBUG=") {
|
||||||
|
|
|
@ -15,10 +15,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
|
|
||||||
|
|
||||||
//"internal/platform"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -27,6 +23,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Save the original environment during init for use in checks. A test
|
// 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”
|
// HasGoBuild reports whether the current system can build programs with “go build”
|
||||||
// and then run them with os.StartProcess or exec.Command.
|
// and then run them with os.StartProcess or exec.Command.
|
||||||
|
// Modified by Hugo (not needed)
|
||||||
func HasGoBuild() bool {
|
func HasGoBuild() bool {
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
return false
|
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 {
|
func HasGoRun() bool {
|
||||||
// For now, having go run and having go build are the same.
|
// For now, having go run and having go build are the same.
|
||||||
return HasGoBuild()
|
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.
|
// If not, MustHaveGoRun calls t.Skip with an explanation.
|
||||||
func MustHaveGoRun(t testing.TB) {
|
func MustHaveGoRun(t testing.TB) {
|
||||||
if !HasGoRun() {
|
if !HasGoRun() {
|
||||||
|
@ -300,8 +298,8 @@ func MustHaveCGO(t testing.TB) {
|
||||||
|
|
||||||
// CanInternalLink reports whether the current system can link programs with
|
// CanInternalLink reports whether the current system can link programs with
|
||||||
// internal linking.
|
// internal linking.
|
||||||
|
// Modified by Hugo (not needed)
|
||||||
func CanInternalLink(withCgo bool) bool {
|
func CanInternalLink(withCgo bool) bool {
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,8 +318,8 @@ func MustInternalLink(t testing.TB, withCgo bool) {
|
||||||
// MustHaveBuildMode reports whether the current system can build programs in
|
// MustHaveBuildMode reports whether the current system can build programs in
|
||||||
// the given build mode.
|
// the given build mode.
|
||||||
// If not, MustHaveBuildMode calls t.Skip with an explanation.
|
// If not, MustHaveBuildMode calls t.Skip with an explanation.
|
||||||
|
// Modified by Hugo (not needed)
|
||||||
func MustHaveBuildMode(t testing.TB, buildmode string) {
|
func MustHaveBuildMode(t testing.TB, buildmode string) {
|
||||||
// Modified by Hugo (not needed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasSymlink reports whether the current system can use os.Symlink.
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
package testenv
|
package testenv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,6 +17,5 @@ import (
|
||||||
var Sigquit = os.Kill
|
var Sigquit = os.Kill
|
||||||
|
|
||||||
func syscallIsNotSupported(err error) bool {
|
func syscallIsNotSupported(err error) bool {
|
||||||
// Removed by Hugo (not supported in Go 1.20).
|
return errors.Is(err, fs.ErrPermission)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
package testenv_test
|
package testenv_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
|
|
||||||
//"internal/platform"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGoToolLocation(t *testing.T) {
|
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")
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
package testenv
|
package testenv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,6 +17,27 @@ import (
|
||||||
var Sigquit = syscall.SIGQUIT
|
var Sigquit = syscall.SIGQUIT
|
||||||
|
|
||||||
func syscallIsNotSupported(err error) bool {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -438,13 +438,13 @@ produce the text
|
||||||
By construction, a template may reside in only one association. If it's
|
By construction, a template may reside in only one association. If it's
|
||||||
necessary to have a template addressable from multiple associations, the
|
necessary to have a template addressable from multiple associations, the
|
||||||
template definition must be parsed multiple times to create distinct *Template
|
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;
|
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
|
see [ParseFiles], [ParseGlob], [Template.ParseFiles] and [Template.ParseGlob]
|
||||||
related templates stored in files.
|
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
|
an associated template identified by name. To invoke our example above, we
|
||||||
might write,
|
might write,
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,13 @@ package template
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"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
|
// maxExecDepth specifies the maximum stack depth of templates within
|
||||||
|
|
|
@ -320,12 +320,16 @@ var execTests = []execTest{
|
||||||
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
||||||
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
||||||
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
||||||
{"nested assignment",
|
{
|
||||||
|
"nested assignment",
|
||||||
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
||||||
"3", tVal, true},
|
"3", tVal, true,
|
||||||
{"nested assignment changes the last declaration",
|
},
|
||||||
|
{
|
||||||
|
"nested assignment changes the last declaration",
|
||||||
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
||||||
"1", tVal, true},
|
"1", tVal, true,
|
||||||
|
},
|
||||||
|
|
||||||
// Type with String method.
|
// Type with String method.
|
||||||
{"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true},
|
{"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 constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
|
||||||
{".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-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 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}}",
|
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||||
"true", tVal, true},
|
"true", tVal, true,
|
||||||
{"chained method",
|
},
|
||||||
|
{
|
||||||
|
"chained method",
|
||||||
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||||
"true", tVal, true},
|
"true", tVal, true,
|
||||||
{"chained method on variable",
|
},
|
||||||
|
{
|
||||||
|
"chained method on variable",
|
||||||
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
|
"{{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 not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
|
||||||
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
||||||
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, 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},
|
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
|
||||||
|
|
||||||
// HTML.
|
// HTML.
|
||||||
{"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
{
|
||||||
"<script>alert("XSS");</script>", nil, true},
|
"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
||||||
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
"<script>alert("XSS");</script>", nil, true,
|
||||||
"<script>alert("XSS");</script>", nil, true},
|
},
|
||||||
|
{
|
||||||
|
"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||||
|
"<script>alert("XSS");</script>", nil, true,
|
||||||
|
},
|
||||||
{"html", `{{html .PS}}`, "a string", tVal, true},
|
{"html", `{{html .PS}}`, "a string", tVal, true},
|
||||||
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
||||||
{"html untyped nil", `{{html .Empty0}}`, "<no value>", tVal, true},
|
{"html untyped nil", `{{html .Empty0}}`, "<no value>", tVal, true},
|
||||||
|
@ -844,7 +858,7 @@ var delimPairs = []string{
|
||||||
|
|
||||||
func TestDelims(t *testing.T) {
|
func TestDelims(t *testing.T) {
|
||||||
const hello = "Hello, world"
|
const hello = "Hello, world"
|
||||||
var value = struct{ Str string }{hello}
|
value := struct{ Str string }{hello}
|
||||||
for i := 0; i < len(delimPairs); i += 2 {
|
for i := 0; i < len(delimPairs); i += 2 {
|
||||||
text := ".Str"
|
text := ".Str"
|
||||||
left := delimPairs[i+0]
|
left := delimPairs[i+0]
|
||||||
|
@ -867,7 +881,7 @@ func TestDelims(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("delim %q text %q parse err %s", left, text, err)
|
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)
|
err = tmpl.Execute(b, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("delim %q exec err %s", left, err)
|
t.Fatalf("delim %q exec err %s", left, err)
|
||||||
|
@ -990,7 +1004,7 @@ const treeTemplate = `
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestTree(t *testing.T) {
|
func TestTree(t *testing.T) {
|
||||||
var tree = &Tree{
|
tree := &Tree{
|
||||||
1,
|
1,
|
||||||
&Tree{
|
&Tree{
|
||||||
2, &Tree{
|
2, &Tree{
|
||||||
|
@ -1243,7 +1257,7 @@ var cmpTests = []cmpTest{
|
||||||
|
|
||||||
func TestComparison(t *testing.T) {
|
func TestComparison(t *testing.T) {
|
||||||
b := new(strings.Builder)
|
b := new(strings.Builder)
|
||||||
var cmpStruct = struct {
|
cmpStruct := struct {
|
||||||
Uthree, Ufour uint
|
Uthree, Ufour uint
|
||||||
NegOne, Three int
|
NegOne, Three int
|
||||||
Ptr, NilPtr *int
|
Ptr, NilPtr *int
|
||||||
|
|
|
@ -478,7 +478,7 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
||||||
case k1 == uintKind && k2 == intKind:
|
case k1 == uintKind && k2 == intKind:
|
||||||
truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int())
|
truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int())
|
||||||
default:
|
default:
|
||||||
if arg1 != zero && arg != zero {
|
if arg1.IsValid() && arg.IsValid() {
|
||||||
return false, errBadComparison
|
return false, errBadComparison
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,13 +169,6 @@ func SetPageInContext(ctx context.Context, p page) context.Context {
|
||||||
return context.WithValue(ctx, texttemplate.PageContextKey, p)
|
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 {
|
type page interface {
|
||||||
IsNode() bool
|
IsNode() bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,6 @@ and2: true
|
||||||
or2: true
|
or2: true
|
||||||
counter2: 3
|
counter2: 3
|
||||||
`)
|
`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue 10495
|
// Issue 10495
|
||||||
|
@ -163,7 +162,6 @@ title: "S3P1"
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGoTemplateBugs(t *testing.T) {
|
func TestGoTemplateBugs(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Issue 11112", func(t *testing.T) {
|
t.Run("Issue 11112", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -188,11 +186,9 @@ func TestGoTemplateBugs(t *testing.T) {
|
||||||
|
|
||||||
b.AssertFileContent("public/index.html", `key = value`)
|
b.AssertFileContent("public/index.html", `key = value`)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecurityAllowActionJSTmpl(t *testing.T) {
|
func TestSecurityAllowActionJSTmpl(t *testing.T) {
|
||||||
|
|
||||||
filesTemplate := `
|
filesTemplate := `
|
||||||
-- config.toml --
|
-- config.toml --
|
||||||
SECURITYCONFIG
|
SECURITYCONFIG
|
||||||
|
@ -211,20 +207,6 @@ var a = §§{{.Title }}§§;
|
||||||
},
|
},
|
||||||
).BuildE()
|
).BuildE()
|
||||||
|
|
||||||
b.Assert(err, qt.Not(qt.IsNil))
|
// This used to fail, but not in >= Hugo 0.121.0.
|
||||||
b.Assert(err.Error(), qt.Contains, "{{.Title}} appears in a JS template literal")
|
b.Assert(err, qt.IsNil)
|
||||||
|
|
||||||
files = strings.ReplaceAll(filesTemplate, "SECURITYCONFIG", `
|
|
||||||
[security]
|
|
||||||
[security.gotemplates]
|
|
||||||
allowActionJSTmpl = true
|
|
||||||
`)
|
|
||||||
|
|
||||||
b = hugolib.NewIntegrationTestBuilder(
|
|
||||||
hugolib.IntegrationTestConfig{
|
|
||||||
T: t,
|
|
||||||
TxtarString: files,
|
|
||||||
},
|
|
||||||
).Build()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue