@marcel91200 / caddy Mirror · read-only / commits / 092913a7
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, 23:30 UTC

httpcaddyfile: Prevent error handler from overriding sub-handler matchers (#6999)

Fixes: #6957

Youness Farini committed Jun 6, 2025 at 18:46 UTC 092913a7a568a5eb4b28c06e12c1274bd5eb1140
9 files changed +440 -35
caddyconfig/httpcaddyfile/builtins.go
+9 -3
@@ -15,6 +15,7 @@
15 package httpcaddyfile
16
17 import (
18 + "encoding/json"
19 "fmt"
20 "html"
21 "net/http"
@@ -843,13 +844,18 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
844 return nil, h.Errf("segment was not parsed as a subroute")
845 }
846
847 + // wrap the subroutes
848 + wrappingRoute := caddyhttp.Route{
849 + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
850 + }
851 + subroute = &caddyhttp.Subroute{
852 + Routes: []caddyhttp.Route{wrappingRoute},
853 + }
854 if expression != "" {
855 statusMatcher := caddy.ModuleMap{
856 "expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}),
857 }
850 - for i := range subroute.Routes {
851 - subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
852 - }
858 + subroute.Routes[0].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
859 }
860 return []ConfigValue{
861 {
caddytest/integration/caddyfile_adapt/error_example.caddyfiletest
+21 -12
@@ -106,20 +106,29 @@ example.com {
106 "handler": "subroute",
107 "routes": [
108 {
109 - "group": "group0",
109 "handle": [
110 {
112 - "handler": "rewrite",
113 - "uri": "/{http.error.status_code}.html"
114 - }
115 - ]
116 - },
117 - {
118 - "handle": [
119 - {
120 - "handler": "file_server",
121 - "hide": [
122 - "./Caddyfile"
111 + "handler": "subroute",
112 + "routes": [
113 + {
114 + "group": "group0",
115 + "handle": [
116 + {
117 + "handler": "rewrite",
118 + "uri": "/{http.error.status_code}.html"
119 + }
120 + ]
121 + },
122 + {
123 + "handle": [
124 + {
125 + "handler": "file_server",
126 + "hide": [
127 + "./Caddyfile"
128 + ]
129 + }
130 + ]
131 + }
132 ]
133 }
134 ]
caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest
+44 -8
@@ -165,8 +165,17 @@ bar.localhost {
165 {
166 "handle": [
167 {
168 - "body": "404 or 410 error",
169 - "handler": "static_response"
168 + "handler": "subroute",
169 + "routes": [
170 + {
171 + "handle": [
172 + {
173 + "body": "404 or 410 error",
174 + "handler": "static_response"
175 + }
176 + ]
177 + }
178 + ]
179 }
180 ],
181 "match": [
@@ -178,8 +187,17 @@ bar.localhost {
187 {
188 "handle": [
189 {
181 - "body": "Error In range [500 .. 599]",
182 - "handler": "static_response"
190 + "handler": "subroute",
191 + "routes": [
192 + {
193 + "handle": [
194 + {
195 + "body": "Error In range [500 .. 599]",
196 + "handler": "static_response"
197 + }
198 + ]
199 + }
200 + ]
201 }
202 ],
203 "match": [
@@ -208,8 +226,17 @@ bar.localhost {
226 {
227 "handle": [
228 {
211 - "body": "404 or 410 error from second site",
212 - "handler": "static_response"
229 + "handler": "subroute",
230 + "routes": [
231 + {
232 + "handle": [
233 + {
234 + "body": "404 or 410 error from second site",
235 + "handler": "static_response"
236 + }
237 + ]
238 + }
239 + ]
240 }
241 ],
242 "match": [
@@ -221,8 +248,17 @@ bar.localhost {
248 {
249 "handle": [
250 {
224 - "body": "Error In range [500 .. 599] from second site",
225 - "handler": "static_response"
251 + "handler": "subroute",
252 + "routes": [
253 + {
254 + "handle": [
255 + {
256 + "body": "Error In range [500 .. 599] from second site",
257 + "handler": "static_response"
258 + }
259 + ]
260 + }
261 + ]
262 }
263 ],
264 "match": [
caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest
+11 -2
@@ -96,8 +96,17 @@ localhost:3010 {
96 {
97 "handle": [
98 {
99 - "body": "Error in the [400 .. 499] range",
100 - "handler": "static_response"
99 + "handler": "subroute",
100 + "routes": [
101 + {
102 + "handle": [
103 + {
104 + "body": "Error in the [400 .. 499] range",
105 + "handler": "static_response"
106 + }
107 + ]
108 + }
109 + ]
110 }
111 ],
112 "match": [
caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest
+22 -4
@@ -116,8 +116,17 @@ localhost:2099 {
116 {
117 "handle": [
118 {
119 - "body": "Error in the [400 .. 499] range",
120 - "handler": "static_response"
119 + "handler": "subroute",
120 + "routes": [
121 + {
122 + "handle": [
123 + {
124 + "body": "Error in the [400 .. 499] range",
125 + "handler": "static_response"
126 + }
127 + ]
128 + }
129 + ]
130 }
131 ],
132 "match": [
@@ -129,8 +138,17 @@ localhost:2099 {
138 {
139 "handle": [
140 {
132 - "body": "Error code is equal to 500 or in the [300..399] range",
133 - "handler": "static_response"
141 + "handler": "subroute",
142 + "routes": [
143 + {
144 + "handle": [
145 + {
146 + "body": "Error code is equal to 500 or in the [300..399] range",
147 + "handler": "static_response"
148 + }
149 + ]
150 + }
151 + ]
152 }
153 ],
154 "match": [
caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest
+11 -2
@@ -96,8 +96,17 @@ localhost:3010 {
96 {
97 "handle": [
98 {
99 - "body": "404 or 410 error",
100 - "handler": "static_response"
99 + "handler": "subroute",
100 + "routes": [
101 + {
102 + "handle": [
103 + {
104 + "body": "404 or 410 error",
105 + "handler": "static_response"
106 + }
107 + ]
108 + }
109 + ]
110 }
111 ],
112 "match": [
caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest
+22 -4
@@ -116,8 +116,17 @@ localhost:2099 {
116 {
117 "handle": [
118 {
119 - "body": "Error in the [400 .. 499] range",
120 - "handler": "static_response"
119 + "handler": "subroute",
120 + "routes": [
121 + {
122 + "handle": [
123 + {
124 + "body": "Error in the [400 .. 499] range",
125 + "handler": "static_response"
126 + }
127 + ]
128 + }
129 + ]
130 }
131 ],
132 "match": [
@@ -129,8 +138,17 @@ localhost:2099 {
138 {
139 "handle": [
140 {
132 - "body": "Fallback route: code outside the [400..499] range",
133 - "handler": "static_response"
141 + "handler": "subroute",
142 + "routes": [
143 + {
144 + "handle": [
145 + {
146 + "body": "Fallback route: code outside the [400..499] range",
147 + "handler": "static_response"
148 + }
149 + ]
150 + }
151 + ]
152 }
153 ]
154 }
caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest new
+260
@@ -0,0 +1,260 @@
1 +{
2 + http_port 2099
3 +}
4 +localhost:2099 {
5 + root * /var/www/
6 + file_server
7 +
8 + handle_errors 404 {
9 + handle /en/* {
10 + respond "not found" 404
11 + }
12 + handle /es/* {
13 + respond "no encontrado"
14 + }
15 + handle {
16 + respond "default not found"
17 + }
18 + }
19 + handle_errors {
20 + handle /en/* {
21 + respond "English error"
22 + }
23 + handle /es/* {
24 + respond "Spanish error"
25 + }
26 + handle {
27 + respond "Default error"
28 + }
29 + }
30 +}
31 +----------
32 +{
33 + "apps": {
34 + "http": {
35 + "http_port": 2099,
36 + "servers": {
37 + "srv0": {
38 + "listen": [
39 + ":2099"
40 + ],
41 + "routes": [
42 + {
43 + "match": [
44 + {
45 + "host": [
46 + "localhost"
47 + ]
48 + }
49 + ],
50 + "handle": [
51 + {
52 + "handler": "subroute",
53 + "routes": [
54 + {
55 + "handle": [
56 + {
57 + "handler": "vars",
58 + "root": "/var/www/"
59 + },
60 + {
61 + "handler": "file_server",
62 + "hide": [
63 + "./Caddyfile"
64 + ]
65 + }
66 + ]
67 + }
68 + ]
69 + }
70 + ],
71 + "terminal": true
72 + }
73 + ],
74 + "errors": {
75 + "routes": [
76 + {
77 + "match": [
78 + {
79 + "host": [
80 + "localhost"
81 + ]
82 + }
83 + ],
84 + "handle": [
85 + {
86 + "handler": "subroute",
87 + "routes": [
88 + {
89 + "handle": [
90 + {
91 + "handler": "subroute",
92 + "routes": [
93 + {
94 + "group": "group3",
95 + "handle": [
96 + {
97 + "handler": "subroute",
98 + "routes": [
99 + {
100 + "handle": [
101 + {
102 + "body": "not found",
103 + "handler": "static_response",
104 + "status_code": 404
105 + }
106 + ]
107 + }
108 + ]
109 + }
110 + ],
111 + "match": [
112 + {
113 + "path": [
114 + "/en/*"
115 + ]
116 + }
117 + ]
118 + },
119 + {
120 + "group": "group3",
121 + "handle": [
122 + {
123 + "handler": "subroute",
124 + "routes": [
125 + {
126 + "handle": [
127 + {
128 + "body": "no encontrado",
129 + "handler": "static_response"
130 + }
131 + ]
132 + }
133 + ]
134 + }
135 + ],
136 + "match": [
137 + {
138 + "path": [
139 + "/es/*"
140 + ]
141 + }
142 + ]
143 + },
144 + {
145 + "group": "group3",
146 + "handle": [
147 + {
148 + "handler": "subroute",
149 + "routes": [
150 + {
151 + "handle": [
152 + {
153 + "body": "default not found",
154 + "handler": "static_response"
155 + }
156 + ]
157 + }
158 + ]
159 + }
160 + ]
161 + }
162 + ]
163 + }
164 + ],
165 + "match": [
166 + {
167 + "expression": "{http.error.status_code} in [404]"
168 + }
169 + ]
170 + },
171 + {
172 + "handle": [
173 + {
174 + "handler": "subroute",
175 + "routes": [
176 + {
177 + "group": "group8",
178 + "handle": [
179 + {
180 + "handler": "subroute",
181 + "routes": [
182 + {
183 + "handle": [
184 + {
185 + "body": "English error",
186 + "handler": "static_response"
187 + }
188 + ]
189 + }
190 + ]
191 + }
192 + ],
193 + "match": [
194 + {
195 + "path": [
196 + "/en/*"
197 + ]
198 + }
199 + ]
200 + },
201 + {
202 + "group": "group8",
203 + "handle": [
204 + {
205 + "handler": "subroute",
206 + "routes": [
207 + {
208 + "handle": [
209 + {
210 + "body": "Spanish error",
211 + "handler": "static_response"
212 + }
213 + ]
214 + }
215 + ]
216 + }
217 + ],
218 + "match": [
219 + {
220 + "path": [
221 + "/es/*"
222 + ]
223 + }
224 + ]
225 + },
226 + {
227 + "group": "group8",
228 + "handle": [
229 + {
230 + "handler": "subroute",
231 + "routes": [
232 + {
233 + "handle": [
234 + {
235 + "body": "Default error",
236 + "handler": "static_response"
237 + }
238 + ]
239 + }
240 + ]
241 + }
242 + ]
243 + }
244 + ]
245 + }
246 + ]
247 + }
248 + ]
249 + }
250 + ],
251 + "terminal": true
252 + }
253 + ]
254 + }
255 + }
256 + }
257 + }
258 + }
259 +}
260 +
caddytest/integration/caddyfile_test.go
+40
@@ -782,6 +782,46 @@ func TestHandleErrorRangeAndCodes(t *testing.T) {
782 tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range")
783 }
784
785 +func TestHandleErrorSubHandlers(t *testing.T) {
786 + tester := caddytest.NewTester(t)
787 + tester.InitServer(`{
788 + admin localhost:2999
789 + http_port 9080
790 + }
791 + localhost:9080 {
792 + root * /srv
793 + file_server
794 + error /*/internalerr* "Internal Server Error" 500
795 +
796 + handle_errors 404 {
797 + handle /en/* {
798 + respond "not found" 404
799 + }
800 + handle /es/* {
801 + respond "no encontrado" 404
802 + }
803 + handle {
804 + respond "default not found"
805 + }
806 + }
807 + handle_errors {
808 + handle {
809 + respond "Default error"
810 + }
811 + handle /en/* {
812 + respond "English error"
813 + }
814 + }
815 + }
816 + `, "caddyfile")
817 + // act and assert
818 + tester.AssertGetResponse("http://localhost:9080/en/notfound", 404, "not found")
819 + tester.AssertGetResponse("http://localhost:9080/es/notfound", 404, "no encontrado")
820 + tester.AssertGetResponse("http://localhost:9080/notfound", 404, "default not found")
821 + tester.AssertGetResponse("http://localhost:9080/es/internalerr", 500, "Default error")
822 + tester.AssertGetResponse("http://localhost:9080/en/internalerr", 500, "English error")
823 +}
824 +
825 func TestInvalidSiteAddressesAsDirectives(t *testing.T) {
826 type testCase struct {
827 config, expectedError string