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