diff options
Diffstat (limited to 'modules/caddyhttp/templates')
| -rw-r--r-- | modules/caddyhttp/templates/templates.go | 35 | ||||
| -rw-r--r-- | modules/caddyhttp/templates/tplcontext.go | 48 | ||||
| -rw-r--r-- | modules/caddyhttp/templates/tplcontext_test.go | 7 | 
3 files changed, 73 insertions, 17 deletions
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go index 449536a..65359d9 100644 --- a/modules/caddyhttp/templates/templates.go +++ b/modules/caddyhttp/templates/templates.go @@ -88,7 +88,11 @@ func init() {  //  // ##### `httpInclude`  // -// Includes the contents of another file by making a virtual HTTP request (also known as a sub-request). The URI path must exist on the same virtual server because the request does not use sockets; instead, the request is crafted in memory and the handler is invoked directly for increased efficiency. +// Includes the contents of another file, and renders it in-place, +// by making a virtual HTTP request (also known as a sub-request). +// The URI path must exist on the same virtual server because the +// request does not use sockets; instead, the request is crafted in +// memory and the handler is invoked directly for increased efficiency.  //  // ```  // {{httpInclude "/foo/bar?q=val"}} @@ -96,7 +100,13 @@ func init() {  //  // ##### `import`  // -// Imports the contents of another file and adds any template definitions to the template stack. If there are no defitions, the filepath will be the defition name. Any {{ define }} blocks will be accessible by {{ template }} or {{ block }}. Imports must happen before the template or block action is called +// Reads and returns the contents of another file, and parses it +// as a template, adding any template definitions to the template +// stack. If there are no definitions, the filepath will be the +// definition name. Any {{ define }} blocks will be accessible by +// {{ template }} or {{ block }}. Imports must happen before the +// template or block action is called. Note that the contents are +// NOT escaped, so you should only import trusted template files.  //  // **filename.html**  // ``` @@ -113,13 +123,26 @@ func init() {  //  // ##### `include`  // -// Includes the contents of another file and renders in-place. Optionally can pass key-value pairs as arguments to be accessed by the included file. +// Includes the contents of another file, rendering it in-place. +// Optionally can pass key-value pairs as arguments to be accessed +// by the included file. Note that the contents are NOT escaped, +// so you should only include trusted template files.  //  // ```  // {{include "path/to/file.html"}}  // no arguments  // {{include "path/to/file.html" "arg1" 2 "value 3"}}  // with arguments  // ```  // +// ##### `readFile` +// +// Reads and returns the contents of another file, as-is. +// Note that the contents are NOT escaped, so you should +// only read trusted files. +// +// ``` +// {{readFile "path/to/file.html"}} +// ``` +//  // ##### `listFiles`  //  // Returns a list of the files in the given directory, which is relative to the template context's file root. @@ -130,10 +153,10 @@ func init() {  //  // ##### `markdown`  // -// Renders the given Markdown text as HTML. This uses the +// Renders the given Markdown text as HTML and returns it. This uses the  // [Goldmark](https://github.com/yuin/goldmark) library, -// which is CommonMark compliant. It also has these plugins -// enabled: Github Flavored Markdown, Footnote and syntax +// which is CommonMark compliant. It also has these extensions +// enabled: Github Flavored Markdown, Footnote, and syntax  // highlighting provided by [Chroma](https://github.com/alecthomas/chroma).  //  // ``` diff --git a/modules/caddyhttp/templates/tplcontext.go b/modules/caddyhttp/templates/tplcontext.go index ddad24f..a7d5314 100644 --- a/modules/caddyhttp/templates/tplcontext.go +++ b/modules/caddyhttp/templates/tplcontext.go @@ -18,6 +18,7 @@ import (  	"bytes"  	"fmt"  	"io" +	"io/fs"  	"net"  	"net/http"  	"os" @@ -29,15 +30,16 @@ import (  	"time"  	"github.com/Masterminds/sprig/v3" -	"github.com/alecthomas/chroma/v2/formatters/html" -	"github.com/caddyserver/caddy/v2" -	"github.com/caddyserver/caddy/v2/modules/caddyhttp" +	chromahtml "github.com/alecthomas/chroma/v2/formatters/html"  	"github.com/dustin/go-humanize"  	"github.com/yuin/goldmark"  	highlighting "github.com/yuin/goldmark-highlighting/v2"  	"github.com/yuin/goldmark/extension"  	"github.com/yuin/goldmark/parser"  	gmhtml "github.com/yuin/goldmark/renderer/html" + +	"github.com/caddyserver/caddy/v2" +	"github.com/caddyserver/caddy/v2/modules/caddyhttp"  )  // TemplateContext is the TemplateContext with which HTTP templates are executed. @@ -73,12 +75,14 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {  	// add our own library  	c.tpl.Funcs(template.FuncMap{  		"include":          c.funcInclude, +		"readFile":         c.funcReadFile,  		"import":           c.funcImport,  		"httpInclude":      c.funcHTTPInclude,  		"stripHTML":        c.funcStripHTML,  		"markdown":         c.funcMarkdown,  		"splitFrontMatter": c.funcSplitFrontMatter,  		"listFiles":        c.funcListFiles, +		"fileStat":         c.funcFileStat,  		"env":              c.funcEnv,  		"placeholder":      c.funcPlaceholder,  		"fileExists":       c.funcFileExists, @@ -100,13 +104,11 @@ func (c TemplateContext) OriginalReq() http.Request {  // trusted files. If it is not trusted, be sure to use escaping functions  // in your template.  func (c TemplateContext) funcInclude(filename string, args ...any) (string, error) { -  	bodyBuf := bufPool.Get().(*bytes.Buffer)  	bodyBuf.Reset()  	defer bufPool.Put(bodyBuf)  	err := c.readFileToBuffer(filename, bodyBuf) -  	if err != nil {  		return "", err  	} @@ -121,6 +123,23 @@ func (c TemplateContext) funcInclude(filename string, args ...any) (string, erro  	return bodyBuf.String(), nil  } +// funcReadFile returns the contents of a filename relative to the site root. +// 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) funcReadFile(filename string) (string, error) { +	bodyBuf := bufPool.Get().(*bytes.Buffer) +	bodyBuf.Reset() +	defer bufPool.Put(bodyBuf) + +	err := c.readFileToBuffer(filename, bodyBuf) +	if err != nil { +		return "", err +	} + +	return bodyBuf.String(), nil +} +  // readFileToBuffer reads a file into a buffer  func (c TemplateContext) readFileToBuffer(filename string, bodyBuf *bytes.Buffer) error {  	if c.Root == nil { @@ -169,6 +188,7 @@ func (c TemplateContext) funcHTTPInclude(uri string) (string, error) {  		return "", err  	}  	virtReq.Host = c.Req.Host +	virtReq.RemoteAddr = "127.0.0.1:10000" // https://github.com/caddyserver/caddy/issues/5835  	virtReq.Header = c.Req.Header.Clone()  	virtReq.Header.Set("Accept-Encoding", "identity") // https://github.com/caddyserver/caddy/issues/4352  	virtReq.Trailer = c.Req.Trailer.Clone() @@ -195,7 +215,6 @@ func (c TemplateContext) funcHTTPInclude(uri string) (string, error) {  // {{ template }} from the standard template library. If the imported file has  // no {{ define }} blocks, the name of the import will be the path  func (c *TemplateContext) funcImport(filename string) (string, error) { -  	bodyBuf := bufPool.Get().(*bytes.Buffer)  	bodyBuf.Reset()  	defer bufPool.Put(bodyBuf) @@ -313,7 +332,7 @@ func (TemplateContext) funcMarkdown(input any) (string, error) {  			extension.Footnote,  			highlighting.NewHighlighting(  				highlighting.WithFormatOptions( -					html.WithClasses(true), +					chromahtml.WithClasses(true),  				),  			),  		), @@ -395,6 +414,21 @@ func (c TemplateContext) funcFileExists(filename string) (bool, error) {  	return false, nil  } +// funcFileStat returns Stat of a filename +func (c TemplateContext) funcFileStat(filename string) (fs.FileInfo, error) { +	if c.Root == nil { +		return nil, fmt.Errorf("root file system not specified") +	} + +	file, err := c.Root.Open(path.Clean(filename)) +	if err != nil { +		return nil, err +	} +	defer file.Close() + +	return file.Stat() +} +  // funcHTTPError returns a structured HTTP handler error. EXPERIMENTAL; SUBJECT TO CHANGE.  // Example usage: `{{if not (fileExists $includeFile)}}{{httpError 404}}{{end}}`  func (c TemplateContext) funcHTTPError(statusCode int) (bool, error) { diff --git a/modules/caddyhttp/templates/tplcontext_test.go b/modules/caddyhttp/templates/tplcontext_test.go index 15a369e..fdf2c10 100644 --- a/modules/caddyhttp/templates/tplcontext_test.go +++ b/modules/caddyhttp/templates/tplcontext_test.go @@ -18,7 +18,6 @@ import (  	"bytes"  	"context"  	"fmt" -	"io/ioutil"  	"net/http"  	"os"  	"path/filepath" @@ -221,21 +220,21 @@ func TestNestedInclude(t *testing.T) {  		// create files and for test case  		if test.parentFile != "" {  			absFilePath = filepath.Join(fmt.Sprintf("%s", context.Root), test.parentFile) -			if err := ioutil.WriteFile(absFilePath, []byte(test.parent), os.ModePerm); err != nil { +			if err := os.WriteFile(absFilePath, []byte(test.parent), os.ModePerm); err != nil {  				os.Remove(absFilePath)  				t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error())  			}  		}  		if test.childFile != "" {  			absFilePath0 = filepath.Join(fmt.Sprintf("%s", context.Root), test.childFile) -			if err := ioutil.WriteFile(absFilePath0, []byte(test.child), os.ModePerm); err != nil { +			if err := os.WriteFile(absFilePath0, []byte(test.child), os.ModePerm); err != nil {  				os.Remove(absFilePath0)  				t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error())  			}  		}  		if test.child2File != "" {  			absFilePath1 = filepath.Join(fmt.Sprintf("%s", context.Root), test.child2File) -			if err := ioutil.WriteFile(absFilePath1, []byte(test.child2), os.ModePerm); err != nil { +			if err := os.WriteFile(absFilePath1, []byte(test.child2), os.ModePerm); err != nil {  				os.Remove(absFilePath0)  				t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error())  			}  | 
