diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go index 449536ac..65359d9e 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 fb00f4fe..5dc4ae55 100644 --- a/modules/caddyhttp/templates/tplcontext.go +++ b/modules/caddyhttp/templates/tplcontext.go @@ -30,7 +30,7 @@ import ( "time" "github.com/Masterminds/sprig/v3" - "github.com/alecthomas/chroma/v2/formatters/html" + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/dustin/go-humanize" @@ -74,6 +74,7 @@ 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, @@ -123,6 +124,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 { @@ -315,7 +333,7 @@ func (TemplateContext) funcMarkdown(input any) (string, error) { extension.Footnote, highlighting.NewHighlighting( highlighting.WithFormatOptions( - html.WithClasses(true), + chromahtml.WithClasses(true), ), ), ),