@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 226 lines 7.36 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 caddyhttp
16
17 import (
18 "context"
19 "errors"
20 "net/http"
21 "net/http/httptest"
22 "testing"
23
24 "github.com/caddyserver/caddy/v2"
25 )
26
27 func newVarsTestRequest(t *testing.T, target string, headers http.Header, vars map[string]any) (*http.Request, *caddy.Replacer) {
28 t.Helper()
29
30 if target == "" {
31 target = "https://example.com/test"
32 }
33
34 req := httptest.NewRequest(http.MethodGet, target, nil)
35 req.Header = headers
36
37 repl := caddy.NewReplacer()
38 ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
39 if vars == nil {
40 vars = make(map[string]any)
41 }
42 // Inject vars directly so these tests exercise matcher-side handling of
43 // already-resolved values, not VarsMiddleware placeholder expansion.
44 ctx = context.WithValue(ctx, VarsCtxKey, vars)
45 req = req.WithContext(ctx)
46
47 addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
48
49 return req, repl
50 }
51
52 func TestVarsMatcherDoesNotExpandResolvedValues(t *testing.T) {
53 t.Setenv("CADDY_VARS_TEST_SECRET", "topsecret")
54
55 for _, tc := range []struct {
56 name string
57 target string
58 match VarsMatcher
59 headers http.Header
60 vars map[string]any
61 expect bool
62 }{
63 {
64 name: "literal variable value containing placeholder syntax is not re-expanded",
65 match: VarsMatcher{"secret": []string{"topsecret"}},
66 vars: map[string]any{"secret": "{env.CADDY_VARS_TEST_SECRET}"},
67 expect: false,
68 },
69 {
70 name: "placeholder key value containing placeholder syntax is not re-expanded",
71 match: VarsMatcher{"{http.request.header.X-Input}": []string{"topsecret"}},
72 headers: http.Header{"X-Input": []string{"{env.CADDY_VARS_TEST_SECRET}"}},
73 expect: false,
74 },
75 {
76 name: "query placeholder value containing placeholder syntax is not re-expanded",
77 target: "https://example.com/test?foo=%7Benv.CADDY_VARS_TEST_SECRET%7D",
78 match: VarsMatcher{"{http.request.uri.query.foo}": []string{"topsecret"}},
79 expect: false,
80 },
81 {
82 name: "matcher values still expand placeholders",
83 match: VarsMatcher{"secret": []string{"{env.CADDY_VARS_TEST_SECRET}"}},
84 vars: map[string]any{"secret": "topsecret"},
85 expect: true,
86 },
87 } {
88 t.Run(tc.name, func(t *testing.T) {
89 t.Parallel()
90
91 req, _ := newVarsTestRequest(t, tc.target, tc.headers, tc.vars)
92
93 actual, err := tc.match.MatchWithError(req)
94 if err != nil {
95 t.Fatalf("MatchWithError() error = %v", err)
96 }
97
98 if actual != tc.expect {
99 t.Fatalf("MatchWithError() = %t, want %t", actual, tc.expect)
100 }
101 })
102 }
103 }
104
105 func TestMatchVarsREDoesNotExpandResolvedValues(t *testing.T) {
106 t.Setenv("CADDY_VARS_TEST_SECRET", "topsecret")
107
108 for _, tc := range []struct {
109 name string
110 target string
111 match MatchVarsRE
112 headers http.Header
113 vars map[string]any
114 expect bool
115 }{
116 {
117 name: "literal variable value containing placeholder syntax is not re-expanded",
118 match: MatchVarsRE{"secret": &MatchRegexp{Pattern: "^topsecret$"}},
119 vars: map[string]any{"secret": "{env.CADDY_VARS_TEST_SECRET}"},
120 expect: false,
121 },
122 {
123 name: "placeholder key value containing placeholder syntax is not re-expanded",
124 match: MatchVarsRE{"{http.request.header.X-Input}": &MatchRegexp{Pattern: "^topsecret$"}},
125 headers: http.Header{"X-Input": []string{"{env.CADDY_VARS_TEST_SECRET}"}},
126 expect: false,
127 },
128 {
129 name: "query placeholder value containing placeholder syntax is not re-expanded",
130 target: "https://example.com/test?foo=%7Benv.CADDY_VARS_TEST_SECRET%7D",
131 match: MatchVarsRE{"{http.request.uri.query.foo}": &MatchRegexp{Pattern: "^topsecret$"}},
132 expect: false,
133 },
134 } {
135 t.Run(tc.name, func(t *testing.T) {
136 t.Parallel()
137
138 err := tc.match.Provision(caddy.Context{})
139 if err != nil {
140 t.Fatalf("Provision() error = %v", err)
141 }
142
143 err = tc.match.Validate()
144 if err != nil {
145 t.Fatalf("Validate() error = %v", err)
146 }
147
148 req, _ := newVarsTestRequest(t, tc.target, tc.headers, tc.vars)
149
150 actual, err := tc.match.MatchWithError(req)
151 if err != nil {
152 t.Fatalf("MatchWithError() error = %v", err)
153 }
154
155 if actual != tc.expect {
156 t.Fatalf("MatchWithError() = %t, want %t", actual, tc.expect)
157 }
158 })
159 }
160 }
161 // TestVarsMatchersTreatErrorsByType verifies the error handling split in the
162 // vars matchers: only the request-body limit marker aborts request handling,
163 // while unrelated errors are matched on their text exactly like before.
164 func TestVarsMatchersTreatErrorsByType(t *testing.T) {
165 unrelated := HandlerError{Err: errors.New("boom"), StatusCode: http.StatusInternalServerError}
166 bodyLimit := RequestBodyLimitError{Err: &http.MaxBytesError{Limit: 10}}
167
168 t.Run("vars matcher matches unrelated errors on text like before", func(t *testing.T) {
169 req, _ := newVarsTestRequest(t, "", nil, map[string]any{"k": unrelated})
170 matched, err := (VarsMatcher{"k": []string{unrelated.Error()}}).MatchWithError(req)
171 if err != nil {
172 t.Fatalf("unrelated handler errors must not control request handling but got %v", err)
173 }
174 if !matched {
175 t.Error("expected a text match like before")
176 }
177 })
178
179 t.Run("vars regexp matcher matches unrelated errors on text like before", func(t *testing.T) {
180 req, _ := newVarsTestRequest(t, "", nil, map[string]any{"k": unrelated})
181 re := MatchVarsRE{"k": &MatchRegexp{Pattern: "^.*HTTP 500.*$"}}
182 if err := re.Provision(caddy.Context{}); err != nil {
183 t.Fatalf("provisioning the regexp: %v", err)
184 }
185 matched, err := re.MatchWithError(req)
186 if err != nil {
187 t.Fatalf("unrelated handler errors must not control request handling but got %v", err)
188 }
189 if !matched {
190 t.Error("expected the error text to match the regexp like before")
191 }
192 })
193
194 t.Run("body limit marker is the only propagated error", func(t *testing.T) {
195 req, _ := newVarsTestRequest(t, "", nil, map[string]any{"k": bodyLimit})
196
197 matched, err := (VarsMatcher{"k": []string{"x"}}).MatchWithError(req)
198 var marker RequestBodyLimitError
199 if err == nil || !errors.As(err, &marker) {
200 t.Fatalf("expected the body limit marker from the vars matcher but got %v", err)
201 }
202 handlerErr, ok := err.(HandlerError)
203 if !ok || handlerErr.StatusCode != http.StatusRequestEntityTooLarge {
204 t.Errorf("expected a 413 handler error carrying the marker but got %v", err)
205 }
206 if matched {
207 t.Error("no vars match should happen when the marker aborts")
208 }
209
210 re := MatchVarsRE{"k": &MatchRegexp{Pattern: ".*"}}
211 if err := re.Provision(caddy.Context{}); err != nil {
212 t.Fatalf("provisioning the regexp: %v", err)
213 }
214 matched, err = re.MatchWithError(req)
215 if err == nil || !errors.As(err, &marker) {
216 t.Fatalf("expected the body limit marker from the regexp matcher too but got %v", err)
217 }
218 handlerErr, ok = err.(HandlerError)
219 if !ok || handlerErr.StatusCode != http.StatusRequestEntityTooLarge {
220 t.Errorf("expected a 413 handler error carrying the marker but got %v", err)
221 }
222 if matched {
223 t.Error("no regexp match should happen when the marker aborts")
224 }
225 })
226 }