Skip to content

Commit

Permalink
Pull in the latest code from Go's template packages (#11771)
Browse files Browse the repository at this point in the history
Fixes #10707
Fixes #11507
  • Loading branch information
bep committed Dec 4, 2023
1 parent 14d85ec commit 9f978d3
Show file tree
Hide file tree
Showing 25 changed files with 417 additions and 190 deletions.
4 changes: 0 additions & 4 deletions config/allconfig/load.go
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
Expand Down
20 changes: 2 additions & 18 deletions config/security/securityConfig.go
Expand Up @@ -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.
Expand All @@ -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()
Expand All @@ -127,7 +115,6 @@ func (c Config) CheckAllowedExec(name string) error {
}
}
return nil

}

func (c Config) CheckAllowedGetEnv(name string) error {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -206,23 +192,21 @@ 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
}

wl := types.ToStringSlicePreserveString(data)

return NewWhitelist(wl...)

}
}

Expand Down
8 changes: 1 addition & 7 deletions config/security/securityConfig_test.go
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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 = ['.*']",
)
}

Expand Down Expand Up @@ -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)

}
2 changes: 1 addition & 1 deletion scripts/fork_go_templates/main.go
Expand Up @@ -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 ...")

Expand Down
10 changes: 7 additions & 3 deletions tpl/internal/go_templates/fmtsort/sort_test.go
Expand Up @@ -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{
Expand All @@ -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
Expand Down Expand Up @@ -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())
})
Expand Down
21 changes: 13 additions & 8 deletions tpl/internal/go_templates/htmltemplate/context.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 */.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tpl/internal/go_templates/htmltemplate/error.go
Expand Up @@ -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
)

Expand Down
51 changes: 22 additions & 29 deletions tpl/internal/go_templates/htmltemplate/escape.go
Expand Up @@ -8,8 +8,6 @@ import (
"bytes"
"fmt"
"html"

//"internal/godebug"
"io"
"regexp"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
},
Expand Down

0 comments on commit 9f978d3

Please sign in to comment.