@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 183 lines 5.74 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 reverseproxy
16
17 import (
18 "context"
19 "encoding/json"
20 "io"
21 "net/http"
22 "net/http/httptest"
23 "testing"
24
25 "github.com/caddyserver/caddy/v2"
26 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
27 )
28
29 func TestCopyResponseHeadersStripsHopByHop(t *testing.T) {
30 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31 w.Header().Set("Alt-Svc", `h2="evil.example:443"; ma=86400`)
32 w.Header().Set("Keep-Alive", "timeout=999")
33 w.Header().Set("Proxy-Authenticate", `Basic realm="phish"`)
34 w.Header().Set("X-Upstream", "kept")
35 w.WriteHeader(http.StatusOK)
36 _, _ = io.WriteString(w, "from-upstream")
37 }))
38 t.Cleanup(backend.Close)
39
40 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
41 t.Cleanup(cancel)
42
43 newHandler := func(t *testing.T, handles ...json.RawMessage) *Handler {
44 t.Helper()
45 h := minimalHandler(0, &Upstream{Host: new(Host), Dial: backend.Listener.Addr().String()})
46 if len(handles) == 0 {
47 return h
48 }
49 route := caddyhttp.Route{HandlersRaw: handles}
50 if err := route.ProvisionHandlers(ctx, nil); err != nil {
51 t.Fatalf("provision handle_response: %v", err)
52 }
53 h.HandleResponse = []caddyhttp.ResponseHandler{{
54 Routes: caddyhttp.RouteList{route},
55 }}
56 return h
57 }
58
59 // The replacement body is the same length as the upstream body.
60 // copy_response_headers also copies Content-Length, and a shorter
61 // body makes the client report unexpected EOF. X-Replaced shows
62 // that static_response ran.
63 const replaced = `{"handler":"static_response","body":"from-upstream","headers":{"X-Replaced":["yes"]}}`
64 cases := []struct {
65 name string
66 handles []json.RawMessage
67 wantReplace bool
68 }{
69 {
70 name: "default finalize",
71 },
72 {
73 name: "copy_response",
74 handles: []json.RawMessage{
75 json.RawMessage(`{"handler":"copy_response"}`),
76 },
77 },
78 {
79 name: "copy_response_headers",
80 handles: []json.RawMessage{
81 json.RawMessage(`{"handler":"copy_response_headers"}`),
82 json.RawMessage(replaced),
83 },
84 wantReplace: true,
85 },
86 {
87 name: "include cannot copy hop-by-hop fields",
88 handles: []json.RawMessage{
89 json.RawMessage(`{"handler":"copy_response_headers","include":["Alt-Svc","X-Upstream","Keep-Alive"]}`),
90 json.RawMessage(replaced),
91 },
92 wantReplace: true,
93 },
94 }
95
96 for _, tc := range cases {
97 t.Run(tc.name, func(t *testing.T) {
98 h := newHandler(t, tc.handles...)
99 front := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
100 err := h.ServeHTTP(w, prepareTestRequest(r), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
101 return nil
102 }))
103 if err != nil {
104 http.Error(w, err.Error(), http.StatusBadGateway)
105 }
106 }))
107 t.Cleanup(front.Close)
108
109 resp, err := front.Client().Get(front.URL)
110 if err != nil {
111 t.Fatalf("GET: %v", err)
112 }
113 defer resp.Body.Close()
114 body, err := io.ReadAll(resp.Body)
115 if err != nil {
116 t.Fatalf("read body: %v", err)
117 }
118 if resp.StatusCode != http.StatusOK {
119 t.Fatalf("status = %d, body = %q", resp.StatusCode, body)
120 }
121 if string(body) != "from-upstream" {
122 t.Fatalf("body = %q", body)
123 }
124 if got := resp.Header.Get("X-Upstream"); got != "kept" {
125 t.Errorf("X-Upstream = %q, want kept", got)
126 }
127 if got, want := resp.Header.Get("X-Replaced") == "yes", tc.wantReplace; got != want {
128 t.Errorf("X-Replaced present = %v, want %v", got, want)
129 }
130 for _, field := range []string{"Alt-Svc", "Keep-Alive", "Proxy-Authenticate"} {
131 if got := resp.Header.Get(field); got != "" {
132 t.Errorf("%s = %q, want absent", field, got)
133 }
134 }
135 })
136 }
137 }
138
139 func TestCopyResponseHeadersStripLeavesUpstreamHeader(t *testing.T) {
140 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
141 t.Cleanup(cancel)
142
143 h := &CopyResponseHeadersHandler{Include: []string{"Alt-Svc", "X-Upstream", "X-Hop"}}
144 if err := h.Provision(ctx); err != nil {
145 t.Fatal(err)
146 }
147
148 res := &http.Response{Header: make(http.Header), StatusCode: http.StatusOK}
149 res.Header.Set("Alt-Svc", `h2="evil.example:443"`)
150 res.Header.Set("X-Upstream", "kept")
151 res.Header.Set("X-Hop", "secret")
152 res.Header.Set("Connection", "close, X-Hop")
153
154 req := httptest.NewRequest(http.MethodGet, "/", nil)
155 req = req.WithContext(context.WithValue(req.Context(), proxyHandleResponseContextCtxKey, &handleResponseContext{
156 response: res,
157 }))
158 rec := httptest.NewRecorder()
159 err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
160 return nil
161 }))
162 if err != nil {
163 t.Fatal(err)
164 }
165
166 if got := rec.Header().Get("X-Upstream"); got != "kept" {
167 t.Errorf("X-Upstream = %q, want kept", got)
168 }
169 for _, field := range []string{"Alt-Svc", "X-Hop", "Connection"} {
170 if got := rec.Header().Get(field); got != "" {
171 t.Errorf("copied %s = %q, want absent", field, got)
172 }
173 }
174 if got := res.Header.Get("Alt-Svc"); got != `h2="evil.example:443"` {
175 t.Errorf("upstream Alt-Svc = %q, want it unchanged", got)
176 }
177 if got := res.Header.Get("X-Hop"); got != "secret" {
178 t.Errorf("upstream X-Hop = %q, want it unchanged", got)
179 }
180 if got := res.Header.Get("Connection"); got != "close, X-Hop" {
181 t.Errorf("upstream Connection = %q, want it unchanged", got)
182 }
183 }