summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/templates
diff options
context:
space:
mode:
authorTom Barrett <tom@tombarrett.xyz>2023-11-01 17:57:48 +0100
committerTom Barrett <tom@tombarrett.xyz>2023-11-01 18:11:33 +0100
commit240c3d1338415e5d82ef7ca0e52c4284be6441bd (patch)
tree4b0ee5d208c2cdffa78d65f1b0abe0ec85f15652 /modules/caddyhttp/templates
parent73e78ab226f21e6c6c68961af88c4ab9c746f4f4 (diff)
parent0e204b730aa2b1fa0835336b1117eff8c420f713 (diff)
vbump to v2.7.5HEADcaddy-cgi
Diffstat (limited to 'modules/caddyhttp/templates')
-rw-r--r--modules/caddyhttp/templates/templates.go35
-rw-r--r--modules/caddyhttp/templates/tplcontext.go48
-rw-r--r--modules/caddyhttp/templates/tplcontext_test.go7
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())
}