@marcel91200 / caddy Mirror · read-only / commits / 6727b2f3
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, 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 }