From 437d5095a6c9aabbabf900417724e655bd4de234 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 6 Apr 2020 12:50:54 -0600 Subject: [PATCH] templates: Use text/template; add experimental notice to docs Using html/template.HTML like we were doing before caused nested include to be HTML-escaped, which breaks sites. Now we do not escape any of the output; template input is usually trusted, and if it's not, users should employ escaping actions within their templates to keep it safe. The docs already said this. --- modules/caddyhttp/templates/templates.go | 2 ++ modules/caddyhttp/templates/tplcontext.go | 20 +++++++++---------- .../caddyhttp/templates/tplcontext_test.go | 3 +-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go index cf0908dc..a0b7ffee 100644 --- a/modules/caddyhttp/templates/templates.go +++ b/modules/caddyhttp/templates/templates.go @@ -33,6 +33,8 @@ func init() { // The syntax is documented in the Go standard library's // [text/template package](https://golang.org/pkg/text/template/). // +// ⚠️ Template functions/actions are still experimental, so they are subject to change. +// // [All Sprig functions](https://masterminds.github.io/sprig/) are supported. // // In addition to the standard functions and Sprig functions, Caddy adds diff --git a/modules/caddyhttp/templates/tplcontext.go b/modules/caddyhttp/templates/tplcontext.go index f49c3692..2d7b9575 100644 --- a/modules/caddyhttp/templates/tplcontext.go +++ b/modules/caddyhttp/templates/tplcontext.go @@ -17,7 +17,6 @@ package templates import ( "bytes" "fmt" - "html/template" "io" "net" "net/http" @@ -25,6 +24,7 @@ import ( "strconv" "strings" "sync" + "text/template" "github.com/Masterminds/sprig/v3" "github.com/alecthomas/chroma/formatters/html" @@ -57,7 +57,7 @@ func (c templateContext) OriginalReq() http.Request { // Note that included files are NOT escaped, so you should only include // trusted files. If it is not trusted, be sure to use escaping functions // in your template. -func (c templateContext) funcInclude(filename string, args ...interface{}) (template.HTML, error) { +func (c templateContext) funcInclude(filename string, args ...interface{}) (string, error) { if c.Root == nil { return "", fmt.Errorf("root file system not specified") } @@ -84,14 +84,14 @@ func (c templateContext) funcInclude(filename string, args ...interface{}) (temp return "", err } - return template.HTML(bodyBuf.String()), nil + return bodyBuf.String(), nil } // funcHTTPInclude returns the body of a virtual (lightweight) request // to the given URI on the same server. Note that included bodies // are NOT escaped, so you should only include trusted resources. // If it is not trusted, be sure to use escaping functions yourself. -func (c templateContext) funcHTTPInclude(uri string) (template.HTML, error) { +func (c templateContext) funcHTTPInclude(uri string) (string, error) { // prevent virtual request loops by counting how many levels // deep we are; and if we get too deep, return an error recursionCount := 1 @@ -132,7 +132,7 @@ func (c templateContext) funcHTTPInclude(uri string) (template.HTML, error) { return "", err } - return template.HTML(buf.String()), nil + return buf.String(), nil } func (c templateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buffer) error { @@ -231,7 +231,7 @@ func (c templateContext) funcStripHTML(s string) string { // funcMarkdown renders the markdown body as HTML. The resulting // HTML is NOT escaped so that it can be rendered as HTML. -func (c templateContext) funcMarkdown(input interface{}) (template.HTML, error) { +func (c templateContext) funcMarkdown(input interface{}) (string, error) { inputStr := toString(input) md := goldmark.New( @@ -259,7 +259,7 @@ func (c templateContext) funcMarkdown(input interface{}) (template.HTML, error) md.Convert([]byte(inputStr), buf) - return template.HTML(buf.String()), nil + return buf.String(), nil } // splitFrontMatter parses front matter out from the beginning of input, @@ -338,14 +338,12 @@ func toString(input interface{}) string { switch v := input.(type) { case string: return v - case template.HTML: - return string(v) case fmt.Stringer: return v.String() case error: return v.Error() default: - return fmt.Sprintf("%s", input) + return fmt.Sprintf("%v", input) } } @@ -357,6 +355,6 @@ var bufPool = sync.Pool{ // at time of writing, sprig.FuncMap() makes a copy, thus // involves iterating the whole map, so do it just once -var sprigFuncMap = sprig.FuncMap() +var sprigFuncMap = sprig.TxtFuncMap() const recursionPreventionHeader = "Caddy-Templates-Include" diff --git a/modules/caddyhttp/templates/tplcontext_test.go b/modules/caddyhttp/templates/tplcontext_test.go index 37b63822..dbf2172e 100644 --- a/modules/caddyhttp/templates/tplcontext_test.go +++ b/modules/caddyhttp/templates/tplcontext_test.go @@ -31,7 +31,6 @@ package templates import ( "bytes" "fmt" - "html/template" "io/ioutil" "net/http" "os" @@ -48,7 +47,7 @@ func TestMarkdown(t *testing.T) { for i, test := range []struct { body string - expect template.HTML + expect string }{ { body: "- str1\n- str2\n",