@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 575 lines 16.3 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package templates
16
17 import (
18 "bytes"
19 "errors"
20 "fmt"
21 "net/http"
22 "strconv"
23 "strings"
24 "text/template"
25
26 "go.uber.org/zap"
27
28 "github.com/caddyserver/caddy/v2"
29 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
30 )
31
32 func init() {
33 caddy.RegisterModule(Templates{})
34 }
35
36 // Templates is a middleware which executes response bodies as Go templates.
37 // The syntax is documented in the Go standard library's
38 // [text/template package](https://golang.org/pkg/text/template/).
39 // Note that ANY response body that matches and qualifies may be evaluated,
40 // even if it comes from a proxied backend.
41 //
42 // ⚠️ Template functions/actions can access the environment, files on disk,
43 // and make HTTP requests. This is extremely useful, but you need to make
44 // sure templates are only evaluated on content that you trust, control, or
45 // at least sanitize properly.
46 //
47 // ⚠️ Templates are still experimental, so they are subject to change.
48 //
49 // [All Sprig functions](https://masterminds.github.io/sprig/) are supported.
50 //
51 // Custom template functions can be registered by creating a plugin module
52 // under the `http.handlers.templates.functions.*` namespace that implements
53 // the `CustomFunctions` interface.
54 //
55 // In addition to the standard functions and the Sprig library, Caddy adds
56 // extra functions and data that are available to a template:
57 //
58 // ##### `.Args`
59 //
60 // A slice of arguments passed to this page/context, for example
61 // as the result of a [`include`](#include).
62 //
63 // ```
64 // {{index .Args 0}} // first argument
65 // ```
66 //
67 // ##### `.Cookie`
68 //
69 // Gets the value of a cookie by name.
70 //
71 // ```
72 // {{.Cookie "cookiename"}}
73 // ```
74 //
75 // ##### `env`
76 //
77 // Gets an environment variable.
78 //
79 // ```
80 // {{env "VAR_NAME"}}
81 // ```
82 //
83 // ##### `placeholder`
84 //
85 // Gets an [placeholder variable](/docs/conventions#placeholders).
86 // The braces (`{}`) have to be omitted.
87 //
88 // ```
89 // {{placeholder "http.request.uri.path"}}
90 // {{placeholder "http.error.status_code"}}
91 // ```
92 //
93 // As a shortcut, `ph` is an alias for `placeholder`.
94 //
95 // ```
96 // {{ph "http.request.method"}}
97 // ```
98 //
99 // ##### `.Host`
100 //
101 // Returns the hostname portion (no port) of the Host header of the HTTP request.
102 //
103 // ```
104 // {{.Host}}
105 // ```
106 //
107 // ##### `httpInclude`
108 //
109 // Includes the contents of another file, and renders it in-place,
110 // by making a virtual HTTP request (also known as a sub-request).
111 // The URI path must exist on the same virtual server because the
112 // request does not use sockets; instead, the request is crafted in
113 // memory and the handler is invoked directly for increased efficiency.
114 //
115 // ```
116 // {{httpInclude "/foo/bar?q=val"}}
117 // ```
118 //
119 // ##### `import`
120 //
121 // Reads and returns the contents of another file, and parses it
122 // as a template, adding any template definitions to the template
123 // stack. If there are no definitions, the filepath will be the
124 // definition name. Any `{{ define }}` blocks will be accessible by
125 // `{{ template }}` or `{{ block }}`. Imports must happen before the
126 // template or block action is called. Note that the contents are
127 // NOT escaped, so you should only import trusted template files.
128 //
129 // **filename.html**
130 // ```
131 // {{ define "main" }}
132 // content
133 // {{ end }}
134 // ```
135 //
136 // **index.html**
137 // ```
138 // {{ import "/path/to/filename.html" }}
139 // {{ template "main" }}
140 // ```
141 //
142 // ##### `include`
143 //
144 // Includes the contents of another file, rendering it in-place.
145 // Optionally can pass key-value pairs as arguments to be accessed
146 // by the included file. Use [`.Args N`](#args) to access the N-th
147 // argument, 0-indexed. Note that the contents are NOT escaped, so
148 // you should only include trusted template files.
149 //
150 // ```
151 // {{include "path/to/file.html"}} // no arguments
152 // {{include "path/to/file.html" "arg0" 1 "value 2"}} // with arguments
153 // ```
154 //
155 // ##### `readFile`
156 //
157 // Reads and returns the contents of another file, as-is.
158 // Note that the contents are NOT escaped, so you should
159 // only read trusted files.
160 //
161 // ```
162 // {{readFile "path/to/file.html"}}
163 // ```
164 //
165 // ##### `listFiles`
166 //
167 // Returns a list of the files in the given directory, which is relative
168 // to the template context's file root.
169 //
170 // ```
171 // {{listFiles "/mydir"}}
172 // ```
173 //
174 // ##### `fileExists`
175 //
176 // Returns true if the given file name, relative to the template context's file root,
177 // can be opened successfully.
178 //
179 // ```
180 // {{fileExists "path/to/file.html"}}
181 // ```
182 //
183 // ##### `fileStat`
184 //
185 // Returns [FileInfo](https://pkg.go.dev/io/fs#FileInfo) using [Stat](https://pkg.go.dev/io/fs#Stat)
186 // on the given file name, relative to the template context's file root.
187 //
188 // ```
189 // {{$css := fileStat "css/style.css" -}}
190 // <link rel="stylesheet" href="/css/style.css?v={{ $css.ModTime.Unix }}">
191 // ```
192 //
193 // ##### `markdown`
194 //
195 // Renders the given Markdown text as HTML and returns it. This uses the
196 // [Goldmark](https://github.com/yuin/goldmark) library,
197 // which is CommonMark compliant. It also has these extensions
198 // enabled: GitHub Flavored Markdown, Footnote, and syntax
199 // highlighting provided by [Chroma](https://github.com/alecthomas/chroma).
200 //
201 // ```
202 // {{markdown "My _markdown_ text"}}
203 // ```
204 //
205 // ##### `.RemoteIP`
206 //
207 // Returns the connection's IP address.
208 //
209 // ```
210 // {{.RemoteIP}}
211 // ```
212 //
213 // ##### `.ClientIP`
214 //
215 // Returns the real client's IP address, if `trusted_proxies` was configured,
216 // otherwise returns the connection's IP address.
217 //
218 // ```
219 // {{.ClientIP}}
220 // ```
221 //
222 // ##### `.Req`
223 //
224 // Accesses the current HTTP request, which has various fields, including:
225 //
226 // - `.Method` - the method
227 // - `.URL` - the URL, which in turn has component fields (Scheme, Host, Path, etc.)
228 // - `.Header` - the header fields
229 // - `.Host` - the Host or :authority header of the request
230 //
231 // ```
232 // {{.Req.Header.Get "User-Agent"}}
233 // ```
234 //
235 // ##### `.OriginalReq`
236 //
237 // Like [`.Req`](#req), except it accesses the original HTTP
238 // request before rewrites or other internal modifications.
239 //
240 // ##### `.RespHeader.Add`
241 //
242 // Adds a header field to the HTTP response.
243 //
244 // ```
245 // {{.RespHeader.Add "Field-Name" "val"}}
246 // ```
247 //
248 // ##### `.RespHeader.Del`
249 //
250 // Deletes a header field on the HTTP response.
251 //
252 // ```
253 // {{.RespHeader.Del "Field-Name"}}
254 // ```
255 //
256 // ##### `.RespHeader.Set`
257 //
258 // Sets a header field on the HTTP response, replacing any existing value.
259 //
260 // ```
261 // {{.RespHeader.Set "Field-Name" "val"}}
262 // ```
263 //
264 // ##### `httpError`
265 //
266 // Returns an error with the given status code to the HTTP handler chain.
267 //
268 // ```
269 // {{if not (fileExists $includedFile)}}{{httpError 404}}{{end}}
270 // ```
271 //
272 // ##### `splitFrontMatter`
273 //
274 // Splits front matter out from the body. Front matter is metadata that
275 // appears at the very beginning of a file or string. Front matter can
276 // be in YAML, TOML, or JSON formats:
277 //
278 // **TOML** front matter starts and ends with `+++`:
279 //
280 // ```toml
281 // +++
282 // template = "blog"
283 // title = "Blog Homepage"
284 // sitename = "A Caddy site"
285 // +++
286 // ```
287 //
288 // **YAML** is surrounded by `---`:
289 //
290 // ```yaml
291 // ---
292 // template: blog
293 // title: Blog Homepage
294 // sitename: A Caddy site
295 // ---
296 // ```
297 //
298 // **JSON** is simply `{` and `}`:
299 //
300 // ```json
301 // {
302 // "template": "blog",
303 // "title": "Blog Homepage",
304 // "sitename": "A Caddy site"
305 // }
306 // ```
307 //
308 // The resulting front matter will be made available like so:
309 //
310 // - `.Meta` to access the metadata fields, for example: `{{$parsed.Meta.title}}`
311 // - `.Body` to access the body after the front matter, for example: `{{markdown $parsed.Body}}`
312 //
313 // ##### `stripHTML`
314 //
315 // Removes HTML from a string.
316 //
317 // ```
318 // {{stripHTML "Shows <b>only</b> text content"}}
319 // ```
320 //
321 // ##### `humanize`
322 //
323 // Transforms size and time inputs to a human readable format.
324 // This uses the [go-humanize](https://github.com/dustin/go-humanize) library.
325 //
326 // The first argument must be a format type, and the last argument
327 // is the input, or the input can be piped in. The supported format
328 // types are:
329 // - **size** which turns an integer amount of bytes into a string like `2.3 MB`
330 // - **time** which turns a time string into a relative time string like `2 weeks ago`
331 //
332 // For the `time` format, the layout for parsing the input can be configured
333 // by appending a colon `:` followed by the desired time layout. You can
334 // find the documentation on time layouts [in Go's docs](https://pkg.go.dev/time#pkg-constants).
335 // The default time layout is `RFC1123Z`, i.e. `Mon, 02 Jan 2006 15:04:05 -0700`.
336 //
337 // ```
338 // {{humanize "size" "2048000"}}
339 // {{placeholder "http.response.header.Content-Length" | humanize "size"}}
340 // {{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}}
341 // {{humanize "time:2006-Jan-02" "2022-May-05"}}
342 // ```
343 //
344 // ##### `pathEscape`
345 //
346 // Passes a string through `url.PathEscape`, replacing characters that have
347 // special meaning in URL path parameters (`?`, `&`, `%`).
348 //
349 // Useful e.g. to include filenames containing these characters in URL path
350 // parameters, or use them as an `img` element's `src` attribute.
351 //
352 // ```
353 // {{pathEscape "50%_valid_filename?.jpg"}}
354 // ```
355 //
356 // ##### `maybe`
357 //
358 // Invokes a custom template function only if it is registered (plugged-in)
359 // in the `http.handlers.templates.functions.*` namespace.
360 //
361 // The first argument is the function name, and any subsequent arguments
362 // are forwarded to that function. If the named function is not available,
363 // the invocation is ignored and a log message is emitted.
364 //
365 // This is useful for templates that optionally use components which may
366 // not be present in every build or environment.
367 //
368 // NOTE: This function is EXPERIMENTAL and subject to change or removal.
369 //
370 // ```
371 // {{ maybe "myOptionalFunc" "arg1" 2 }}
372 // ```
373 type Templates struct {
374 // The root path from which to load files. Required if template functions
375 // accessing the file system are used (such as include). Default is
376 // `{http.vars.root}` if set, or current working directory otherwise.
377 FileRoot string `json:"file_root,omitempty"`
378
379 // The MIME types for which to render templates. It is important to use
380 // this if the route matchers do not exclude images or other binary files.
381 // Default is text/plain, text/markdown, and text/html.
382 MIMETypes []string `json:"mime_types,omitempty"`
383
384 // The template action delimiters. If set, must be precisely two elements:
385 // the opening and closing delimiters. Default: `["{{", "}}"]`
386 Delimiters []string `json:"delimiters,omitempty"`
387
388 // Extensions adds functions to the template's func map. These often
389 // act as components on web pages, for example.
390 ExtensionsRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=http.handlers.templates.functions"`
391
392 customFuncs []template.FuncMap
393 logger *zap.Logger
394 }
395
396 // CustomFunctions is the interface for registering custom template functions.
397 type CustomFunctions interface {
398 // CustomTemplateFunctions should return the mapping from custom function names to implementations.
399 CustomTemplateFunctions() template.FuncMap
400 }
401
402 // CaddyModule returns the Caddy module information.
403 func (Templates) CaddyModule() caddy.ModuleInfo {
404 return caddy.ModuleInfo{
405 ID: "http.handlers.templates",
406 New: func() caddy.Module { return new(Templates) },
407 }
408 }
409
410 // Provision provisions t.
411 func (t *Templates) Provision(ctx caddy.Context) error {
412 t.logger = ctx.Logger()
413 mods, err := ctx.LoadModule(t, "ExtensionsRaw")
414 if err != nil {
415 return fmt.Errorf("loading template extensions: %v", err)
416 }
417 for _, modIface := range mods.(map[string]any) {
418 t.customFuncs = append(t.customFuncs, modIface.(CustomFunctions).CustomTemplateFunctions())
419 }
420
421 if t.MIMETypes == nil {
422 t.MIMETypes = defaultMIMETypes
423 }
424 if t.FileRoot == "" {
425 t.FileRoot = "{http.vars.root}"
426 }
427 return nil
428 }
429
430 // Validate ensures t has a valid configuration.
431 func (t *Templates) Validate() error {
432 if len(t.Delimiters) != 0 && len(t.Delimiters) != 2 {
433 return fmt.Errorf("delimiters must consist of exactly two elements: opening and closing")
434 }
435 return nil
436 }
437
438 func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
439 buf := bufPool.Get().(*bytes.Buffer)
440 buf.Reset()
441 defer bufPool.Put(buf)
442
443 // shouldBuf determines whether to execute templates on this response,
444 // since generally we will not want to execute for images or CSS, etc.
445 shouldBuf := func(status int, header http.Header) bool {
446 ct := header.Get("Content-Type")
447 for _, mt := range t.MIMETypes {
448 if strings.Contains(ct, mt) {
449 return true
450 }
451 }
452 return false
453 }
454
455 rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuf)
456
457 err := next.ServeHTTP(rec, r)
458 if err != nil {
459 return err
460 }
461 if !rec.Buffered() {
462 return nil
463 }
464
465 err = t.executeTemplate(rec, r)
466 if err != nil {
467 return err
468 }
469
470 // responses without a body (HEAD, 1xx, 204, 304) can't tell us the
471 // length of the rendered template, so drop the header rather than
472 // advertising the length of the empty buffer, and a 2xx response to
473 // CONNECT must not have one at all (RFC 9110 §8.6)
474 connectSuccess := r.Method == http.MethodConnect && rec.Status() >= 200 && rec.Status() <= 299
475 if r.Method == http.MethodHead || connectSuccess || !bodyAllowedForStatus(rec.Status()) {
476 rec.Header().Del("Content-Length")
477 } else {
478 rec.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
479 }
480 rec.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-created content
481 rec.Header().Del("Last-Modified") // useless for dynamic content since it's always changing
482
483 // we don't know a way to quickly generate etag for dynamic content,
484 // and weak etags still cause browsers to rely on it even after a
485 // refresh, so disable them until we find a better way to do this
486 rec.Header().Del("Etag")
487
488 return rec.WriteResponse()
489 }
490
491 // bodyAllowedForStatus reports whether a response with the given
492 // status may include a body, per RFC 9110 §6.4.1.
493 func bodyAllowedForStatus(status int) bool {
494 switch {
495 case status >= 100 && status <= 199:
496 return false
497 case status == http.StatusNoContent:
498 return false
499 case status == http.StatusNotModified:
500 return false
501 }
502 return true
503 }
504
505 // executeTemplate executes the template contained in wb.buf and replaces it with the results.
506 func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Request) error {
507 var fs http.FileSystem
508 if t.FileRoot != "" {
509 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
510 fs = http.Dir(repl.ReplaceAll(t.FileRoot, "."))
511 }
512
513 ctx := &TemplateContext{
514 Root: fs,
515 Req: r,
516 RespHeader: WrappedHeader{rr.Header()},
517 config: t,
518 CustomFuncs: t.customFuncs,
519 }
520
521 err := ctx.executeTemplateInBuffer(r.URL.Path, rr.Buffer())
522 if err != nil {
523 // Delete some headers that may have been set by the underlying
524 // handler (such as file_server) which may break the error response.
525 rr.Header().Del("Content-Length")
526 rr.Header().Del("Content-Type")
527 rr.Header().Del("Etag")
528 rr.Header().Del("Last-Modified")
529 rr.Header().Del("Accept-Ranges")
530 rr.Header().Del("Content-Encoding")
531 rr.Header().Del("Content-Range")
532
533 // templates may return a custom HTTP error to be propagated to the client,
534 // otherwise for any other error we assume the template is broken
535 if handlerErr, ok := errors.AsType[caddyhttp.HandlerError](err); ok {
536 return handlerErr
537 }
538 return caddyhttp.Error(http.StatusInternalServerError, err)
539 }
540
541 return nil
542 }
543
544 // virtualResponseWriter is used in virtualized HTTP requests
545 // that templates may execute.
546 type virtualResponseWriter struct {
547 status int
548 header http.Header
549 body *bytes.Buffer
550 }
551
552 func (vrw *virtualResponseWriter) Header() http.Header {
553 return vrw.header
554 }
555
556 func (vrw *virtualResponseWriter) WriteHeader(statusCode int) {
557 vrw.status = statusCode
558 }
559
560 func (vrw *virtualResponseWriter) Write(data []byte) (int, error) {
561 return vrw.body.Write(data)
562 }
563
564 var defaultMIMETypes = []string{
565 "text/html",
566 "text/plain",
567 "text/markdown",
568 }
569
570 // Interface guards
571 var (
572 _ caddy.Provisioner = (*Templates)(nil)
573 _ caddy.Validator = (*Templates)(nil)
574 _ caddyhttp.MiddlewareHandler = (*Templates)(nil)
575 )