Browse Source

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.
master
Matthew Holt 5 years ago
parent
commit
437d5095a6
No known key found for this signature in database GPG Key ID: 2A349DD577D586A5
  1. 2
      modules/caddyhttp/templates/templates.go
  2. 20
      modules/caddyhttp/templates/tplcontext.go
  3. 3
      modules/caddyhttp/templates/tplcontext_test.go

2
modules/caddyhttp/templates/templates.go

@ -33,6 +33,8 @@ func init() {
// The syntax is documented in the Go standard library's // The syntax is documented in the Go standard library's
// [text/template package](https://golang.org/pkg/text/template/). // [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. // [All Sprig functions](https://masterminds.github.io/sprig/) are supported.
// //
// In addition to the standard functions and Sprig functions, Caddy adds // In addition to the standard functions and Sprig functions, Caddy adds

20
modules/caddyhttp/templates/tplcontext.go

@ -17,7 +17,6 @@ package templates
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"html/template"
"io" "io"
"net" "net"
"net/http" "net/http"
@ -25,6 +24,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"text/template"
"github.com/Masterminds/sprig/v3" "github.com/Masterminds/sprig/v3"
"github.com/alecthomas/chroma/formatters/html" "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 // 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 // trusted files. If it is not trusted, be sure to use escaping functions
// in your template. // 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 { if c.Root == nil {
return "", fmt.Errorf("root file system not specified") return "", fmt.Errorf("root file system not specified")
} }
@ -84,14 +84,14 @@ func (c templateContext) funcInclude(filename string, args ...interface{}) (temp
return "", err return "", err
} }
return template.HTML(bodyBuf.String()), nil return bodyBuf.String(), nil
} }
// funcHTTPInclude returns the body of a virtual (lightweight) request // funcHTTPInclude returns the body of a virtual (lightweight) request
// to the given URI on the same server. Note that included bodies // to the given URI on the same server. Note that included bodies
// are NOT escaped, so you should only include trusted resources. // are NOT escaped, so you should only include trusted resources.
// If it is not trusted, be sure to use escaping functions yourself. // 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 // prevent virtual request loops by counting how many levels
// deep we are; and if we get too deep, return an error // deep we are; and if we get too deep, return an error
recursionCount := 1 recursionCount := 1
@ -132,7 +132,7 @@ func (c templateContext) funcHTTPInclude(uri string) (template.HTML, error) {
return "", err return "", err
} }
return template.HTML(buf.String()), nil return buf.String(), nil
} }
func (c templateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buffer) error { 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 // funcMarkdown renders the markdown body as HTML. The resulting
// HTML is NOT escaped so that it can be rendered as HTML. // 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) inputStr := toString(input)
md := goldmark.New( md := goldmark.New(
@ -259,7 +259,7 @@ func (c templateContext) funcMarkdown(input interface{}) (template.HTML, error)
md.Convert([]byte(inputStr), buf) 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, // splitFrontMatter parses front matter out from the beginning of input,
@ -338,14 +338,12 @@ func toString(input interface{}) string {
switch v := input.(type) { switch v := input.(type) {
case string: case string:
return v return v
case template.HTML:
return string(v)
case fmt.Stringer: case fmt.Stringer:
return v.String() return v.String()
case error: case error:
return v.Error() return v.Error()
default: 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 // at time of writing, sprig.FuncMap() makes a copy, thus
// involves iterating the whole map, so do it just once // involves iterating the whole map, so do it just once
var sprigFuncMap = sprig.FuncMap() var sprigFuncMap = sprig.TxtFuncMap()
const recursionPreventionHeader = "Caddy-Templates-Include" const recursionPreventionHeader = "Caddy-Templates-Include"

3
modules/caddyhttp/templates/tplcontext_test.go

@ -31,7 +31,6 @@ package templates
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
@ -48,7 +47,7 @@ func TestMarkdown(t *testing.T) {
for i, test := range []struct { for i, test := range []struct {
body string body string
expect template.HTML expect string
}{ }{
{ {
body: "- str1\n- str2\n", body: "- str1\n- str2\n",

Loading…
Cancel
Save