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
| 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 | "strconv" |
| 22 | "testing" |
| 23 | |
| 24 | "github.com/caddyserver/caddy/v2/modules/caddyhttp" |
| 25 | ) |
| 26 | |
| 27 | func TestServeHTTPErrorDeletesFileHeaders(t *testing.T) { |
| 28 | for i, tc := range []struct { |
| 29 | body string |
| 30 | wantStatus int |
| 31 | }{ |
| 32 | {body: `{{httpError 404}}`, wantStatus: http.StatusNotFound}, |
| 33 | {body: `{{nosuchfunc}}`, wantStatus: http.StatusInternalServerError}, |
| 34 | {body: `{{include "missing.html"}}`, wantStatus: http.StatusInternalServerError}, |
| 35 | } { |
| 36 | tmpl := &Templates{MIMETypes: defaultMIMETypes} |
| 37 | next := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { |
| 38 | w.Header().Set("Content-Type", "text/html; charset=utf-8") |
| 39 | w.Header().Set("Content-Length", "123") |
| 40 | w.Header().Set("Etag", `"abc"`) |
| 41 | w.Header().Set("Last-Modified", "Mon, 02 Jan 2006 15:04:05 GMT") |
| 42 | w.Header().Set("Accept-Ranges", "bytes") |
| 43 | w.Header().Set("Content-Encoding", "gzip") |
| 44 | w.Header().Set("Content-Range", "bytes 0-122/456") |
| 45 | _, err := w.Write([]byte(tc.body)) |
| 46 | return err |
| 47 | }) |
| 48 | |
| 49 | w := httptest.NewRecorder() |
| 50 | r := httptest.NewRequest(http.MethodGet, "/index.html", nil) |
| 51 | |
| 52 | err := tmpl.ServeHTTP(w, r, next) |
| 53 | handlerErr, ok := errors.AsType[caddyhttp.HandlerError](err) |
| 54 | if !ok { |
| 55 | t.Fatalf("Test %d: expected a handler error, got %v", i, err) |
| 56 | } |
| 57 | if handlerErr.StatusCode != tc.wantStatus { |
| 58 | t.Errorf("Test %d: expected status %d, got %d", i, tc.wantStatus, handlerErr.StatusCode) |
| 59 | } |
| 60 | for _, name := range []string{"Content-Length", "Content-Type", "Etag", "Last-Modified", "Accept-Ranges", "Content-Encoding", "Content-Range"} { |
| 61 | if got := w.Header().Get(name); got != "" { |
| 62 | t.Errorf("Test %d: expected %s to be deleted, got %q", i, name, got) |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | func TestServeHTTPContentLength(t *testing.T) { |
| 69 | const ( |
| 70 | source = `{{"hello"}} world` |
| 71 | rendered = "hello world" |
| 72 | ) |
| 73 | |
| 74 | for _, tc := range []struct { |
| 75 | name string |
| 76 | method string |
| 77 | status int |
| 78 | wantLength string // empty means the header must be absent |
| 79 | wantBody string |
| 80 | }{ |
| 81 | {name: "GET reports rendered length", method: http.MethodGet, status: http.StatusOK, wantLength: strconv.Itoa(len(rendered)), wantBody: rendered}, |
| 82 | {name: "HEAD drops stale length", method: http.MethodHead, status: http.StatusOK}, |
| 83 | {name: "204 drops stale length", method: http.MethodGet, status: http.StatusNoContent}, |
| 84 | {name: "304 drops stale length", method: http.MethodGet, status: http.StatusNotModified}, |
| 85 | {name: "CONNECT 200 drops stale length", method: http.MethodConnect, status: http.StatusOK}, |
| 86 | {name: "CONNECT 407 reports rendered length", method: http.MethodConnect, status: http.StatusProxyAuthRequired, wantLength: strconv.Itoa(len(rendered)), wantBody: rendered}, |
| 87 | } { |
| 88 | t.Run(tc.name, func(t *testing.T) { |
| 89 | // next behaves like file_server: it advertises the length of the |
| 90 | // unrendered template and writes a body only when one is allowed |
| 91 | next := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { |
| 92 | w.Header().Set("Content-Type", "text/html") |
| 93 | w.Header().Set("Content-Length", strconv.Itoa(len(source))) |
| 94 | w.WriteHeader(tc.status) |
| 95 | if tc.wantBody != "" { |
| 96 | _, err := w.Write([]byte(source)) |
| 97 | return err |
| 98 | } |
| 99 | return nil |
| 100 | }) |
| 101 | |
| 102 | tmpl := &Templates{MIMETypes: []string{"text/html"}} |
| 103 | rec := httptest.NewRecorder() |
| 104 | req := httptest.NewRequest(tc.method, "/", nil) |
| 105 | if err := tmpl.ServeHTTP(rec, req, next); err != nil { |
| 106 | t.Fatalf("ServeHTTP: %v", err) |
| 107 | } |
| 108 | |
| 109 | if rec.Code != tc.status { |
| 110 | t.Errorf("expected status %d, got %d", tc.status, rec.Code) |
| 111 | } |
| 112 | gotLength, ok := rec.Header()["Content-Length"] |
| 113 | switch { |
| 114 | case tc.wantLength == "" && ok: |
| 115 | t.Errorf("expected no Content-Length, got %q", gotLength) |
| 116 | case tc.wantLength != "" && rec.Header().Get("Content-Length") != tc.wantLength: |
| 117 | t.Errorf("expected Content-Length %q, got %q", tc.wantLength, rec.Header().Get("Content-Length")) |
| 118 | } |
| 119 | if body := rec.Body.String(); body != tc.wantBody { |
| 120 | t.Errorf("expected body %q, got %q", tc.wantBody, body) |
| 121 | } |
| 122 | }) |
| 123 | } |
| 124 | } |