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, 03:30 UTC
templates: Delete file headers on any template error (#8149)
* templates: Delete file headers on any template error A template that fails to parse or execute (not only {{httpError}}) left the Content-Length, Etag and related headers set by file_server on the error response, so clients received a 500 that declared the file's size but had no body. * templates: Also delete Content-Encoding and Content-Range on errors Precompressed and ranged file responses could leave these headers on the replacement error response.
Jay Hemnani committed
Oct 8, 2026 at 09:10 UTC
6727b2f3a92bfc20f897ea9a24e88a4013274d55
3 files changed
+77
modules/caddyhttp/templates/templates.go
+10
@@ -497,6 +497,16 @@ func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Reque
497
498
err := ctx.executeTemplateInBuffer(r.URL.Path, rr.Buffer())
499
if err != nil {
500
+ // Delete some headers that may have been set by the underlying
501
+ // handler (such as file_server) which may break the error response.
502
+ rr.Header().Del("Content-Length")
503
+ rr.Header().Del("Content-Type")
504
+ rr.Header().Del("Etag")
505
+ rr.Header().Del("Last-Modified")
506
+ rr.Header().Del("Accept-Ranges")
507
+ rr.Header().Del("Content-Encoding")
508
+ rr.Header().Del("Content-Range")
509
+
510
// templates may return a custom HTTP error to be propagated to the client,
511
// otherwise for any other error we assume the template is broken
512
if handlerErr, ok := errors.AsType[caddyhttp.HandlerError](err); ok {
modules/caddyhttp/templates/templates_test.go
new
+65
@@ -0,0 +1,65 @@
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
+ "errors"
19
+ "net/http"
20
+ "net/http/httptest"
21
+ "testing"
22
+
23
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
24
+)
25
+
26
+func TestServeHTTPErrorDeletesFileHeaders(t *testing.T) {
27
+ for i, tc := range []struct {
28
+ body string
29
+ wantStatus int
30
+ }{
31
+ {body: `{{httpError 404}}`, wantStatus: http.StatusNotFound},
32
+ {body: `{{nosuchfunc}}`, wantStatus: http.StatusInternalServerError},
33
+ {body: `{{include "missing.html"}}`, wantStatus: http.StatusInternalServerError},
34
+ } {
35
+ tmpl := &Templates{MIMETypes: defaultMIMETypes}
36
+ next := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
37
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
38
+ w.Header().Set("Content-Length", "123")
39
+ w.Header().Set("Etag", `"abc"`)
40
+ w.Header().Set("Last-Modified", "Mon, 02 Jan 2006 15:04:05 GMT")
41
+ w.Header().Set("Accept-Ranges", "bytes")
42
+ w.Header().Set("Content-Encoding", "gzip")
43
+ w.Header().Set("Content-Range", "bytes 0-122/456")
44
+ _, err := w.Write([]byte(tc.body))
45
+ return err
46
+ })
47
+
48
+ w := httptest.NewRecorder()
49
+ r := httptest.NewRequest(http.MethodGet, "/index.html", nil)
50
+
51
+ err := tmpl.ServeHTTP(w, r, next)
52
+ handlerErr, ok := errors.AsType[caddyhttp.HandlerError](err)
53
+ if !ok {
54
+ t.Fatalf("Test %d: expected a handler error, got %v", i, err)
55
+ }
56
+ if handlerErr.StatusCode != tc.wantStatus {
57
+ t.Errorf("Test %d: expected status %d, got %d", i, tc.wantStatus, handlerErr.StatusCode)
58
+ }
59
+ for _, name := range []string{"Content-Length", "Content-Type", "Etag", "Last-Modified", "Accept-Ranges", "Content-Encoding", "Content-Range"} {
60
+ if got := w.Header().Get(name); got != "" {
61
+ t.Errorf("Test %d: expected %s to be deleted, got %q", i, name, got)
62
+ }
63
+ }
64
+ }
65
+}
modules/caddyhttp/templates/tplcontext.go
+2
@@ -473,6 +473,8 @@ func (c TemplateContext) funcHTTPError(statusCode int) (bool, error) {
473
c.RespHeader.Header.Del("Etag")
474
c.RespHeader.Header.Del("Last-Modified")
475
c.RespHeader.Header.Del("Accept-Ranges")
476
+ c.RespHeader.Header.Del("Content-Encoding")
477
+ c.RespHeader.Header.Del("Content-Range")
478
479
return false, caddyhttp.Error(statusCode, nil)
480
}