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 httpcaddyfile
16
+
17
+import (
18
+ "encoding/json"
19
+ "fmt"
20
+
21
+ "github.com/caddyserver/caddy/v2"
22
+ "github.com/caddyserver/caddy/v2/caddyconfig"
23
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
24
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
25
+ "github.com/dustin/go-humanize"
26
+)
27
+
28
+// serverOptions collects server config overrides parsed from Caddyfile global options
29
+type serverOptions struct {
30
+ // If set, will only apply these options to servers that contain a
31
+ // listener address that matches exactly. If empty, will apply to all
32
+ // servers that were not already matched by another serverOptions.
33
+ ListenerAddress string
34
+
35
+ // These will all map 1:1 to the caddyhttp.Server struct
36
+ ListenerWrappersRaw []json.RawMessage
37
+ ReadTimeout caddy.Duration
38
+ ReadHeaderTimeout caddy.Duration
39
+ WriteTimeout caddy.Duration
40
+ IdleTimeout caddy.Duration
41
+ MaxHeaderBytes int
42
+ AllowH2C bool
43
+ ExperimentalHTTP3 bool
44
+ StrictSNIHost *bool
45
+}
46
+
47
+func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error) {
48
+ serverOpts := serverOptions{}
49
+ for d.Next() {
50
+ if d.NextArg() {
51
+ serverOpts.ListenerAddress = d.Val()
52
+ if d.NextArg() {
53
+ return nil, d.ArgErr()
54
+ }
55
+ }
56
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
57
+ switch d.Val() {
58
+ case "listener_wrappers":
59
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
60
+ mod, err := caddy.GetModule("caddy.listeners." + d.Val())
61
+ if err != nil {
62
+ return nil, fmt.Errorf("finding listener module '%s': %v", d.Val(), err)
63
+ }
64
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
65
+ if !ok {
66
+ return nil, fmt.Errorf("listener module '%s' is not a Caddyfile unmarshaler", mod)
67
+ }
68
+ err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
69
+ if err != nil {
70
+ return nil, err
71
+ }
72
+ listenerWrapper, ok := unm.(caddy.ListenerWrapper)
73
+ if !ok {
74
+ return nil, fmt.Errorf("module %s is not a listener wrapper", mod)
75
+ }
76
+ jsonListenerWrapper := caddyconfig.JSONModuleObject(
77
+ listenerWrapper,
78
+ "wrapper",
79
+ listenerWrapper.(caddy.Module).CaddyModule().ID.Name(),
80
+ nil,
81
+ )
82
+ serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
83
+ }
84
+
85
+ case "timeouts":
86
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
87
+ switch d.Val() {
88
+ case "read_body":
89
+ if !d.NextArg() {
90
+ return nil, d.ArgErr()
91
+ }
92
+ dur, err := caddy.ParseDuration(d.Val())
93
+ if err != nil {
94
+ return nil, d.Errf("parsing read_body timeout duration: %v", err)
95
+ }
96
+ serverOpts.ReadTimeout = caddy.Duration(dur)
97
+
98
+ case "read_header":
99
+ if !d.NextArg() {
100
+ return nil, d.ArgErr()
101
+ }
102
+ dur, err := caddy.ParseDuration(d.Val())
103
+ if err != nil {
104
+ return nil, d.Errf("parsing read_header timeout duration: %v", err)
105
+ }
106
+ serverOpts.ReadHeaderTimeout = caddy.Duration(dur)
107
+
108
+ case "write":
109
+ if !d.NextArg() {
110
+ return nil, d.ArgErr()
111
+ }
112
+ dur, err := caddy.ParseDuration(d.Val())
113
+ if err != nil {
114
+ return nil, d.Errf("parsing write timeout duration: %v", err)
115
+ }
116
+ serverOpts.WriteTimeout = caddy.Duration(dur)
117
+
118
+ case "idle":
119
+ if !d.NextArg() {
120
+ return nil, d.ArgErr()
121
+ }
122
+ dur, err := caddy.ParseDuration(d.Val())
123
+ if err != nil {
124
+ return nil, d.Errf("parsing idle timeout duration: %v", err)
125
+ }
126
+ serverOpts.IdleTimeout = caddy.Duration(dur)
127
+
128
+ default:
129
+ return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
130
+ }
131
+ }
132
+
133
+ case "max_header_size":
134
+ var sizeStr string
135
+ if !d.AllArgs(&sizeStr) {
136
+ return nil, d.ArgErr()
137
+ }
138
+ size, err := humanize.ParseBytes(sizeStr)
139
+ if err != nil {
140
+ return nil, d.Errf("parsing max_header_size: %v", err)
141
+ }
142
+ serverOpts.MaxHeaderBytes = int(size)
143
+
144
+ case "protocol":
145
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
146
+ switch d.Val() {
147
+ case "allow_h2c":
148
+ if d.NextArg() {
149
+ return nil, d.ArgErr()
150
+ }
151
+ serverOpts.AllowH2C = true
152
+
153
+ case "experimental_http3":
154
+ if d.NextArg() {
155
+ return nil, d.ArgErr()
156
+ }
157
+ serverOpts.ExperimentalHTTP3 = true
158
+
159
+ case "strict_sni_host":
160
+ if d.NextArg() {
161
+ return nil, d.ArgErr()
162
+ }
163
+ trueBool := true
164
+ serverOpts.StrictSNIHost = &trueBool
165
+
166
+ default:
167
+ return nil, d.Errf("unrecognized protocol option '%s'", d.Val())
168
+ }
169
+ }
170
+
171
+ default:
172
+ return nil, d.Errf("unrecognized servers option '%s'", d.Val())
173
+ }
174
+ }
175
+ }
176
+ return serverOpts, nil
177
+}
178
+
179
+// applyServerOptions sets the server options on the appropriate servers
180
+func applyServerOptions(
181
+ servers map[string]*caddyhttp.Server,
182
+ options map[string]interface{},
183
+ warnings *[]caddyconfig.Warning,
184
+) error {
185
+ // If experimental HTTP/3 is enabled, enable it on each server.
186
+ // We already know there won't be a conflict with serverOptions because
187
+ // we validated earlier that "experimental_http3" cannot be set at the same
188
+ // time as "servers"
189
+ if enableH3, ok := options["experimental_http3"].(bool); ok && enableH3 {
190
+ *warnings = append(*warnings, caddyconfig.Warning{Message: "the 'experimental_http3' global option is deprecated, please use the 'servers > protocol > experimental_http3' option instead"})
191
+ for _, srv := range servers {
192
+ srv.ExperimentalHTTP3 = true
193
+ }
194
+ }
195
+
196
+ serverOpts, ok := options["servers"].([]serverOptions)
197
+ if !ok {
198
+ return nil
199
+ }
200
+
201
+ for _, server := range servers {
202
+ // find the options that apply to this server
203
+ opts := func() *serverOptions {
204
+ for _, entry := range serverOpts {
205
+ if entry.ListenerAddress == "" {
206
+ return &entry
207
+ }
208
+ for _, listener := range server.Listen {
209
+ if entry.ListenerAddress == listener {
210
+ return &entry
211
+ }
212
+ }
213
+ }
214
+ return nil
215
+ }()
216
+
217
+ // if none apply, then move to the next server
218
+ if opts == nil {
219
+ continue
220
+ }
221
+
222
+ // set all the options
223
+ server.ListenerWrappersRaw = opts.ListenerWrappersRaw
224
+ server.ReadTimeout = opts.ReadTimeout
225
+ server.ReadHeaderTimeout = opts.ReadHeaderTimeout
226
+ server.WriteTimeout = opts.WriteTimeout
227
+ server.IdleTimeout = opts.IdleTimeout
228
+ server.MaxHeaderBytes = opts.MaxHeaderBytes
229
+ server.AllowH2C = opts.AllowH2C
230
+ server.ExperimentalHTTP3 = opts.ExperimentalHTTP3
231
+ server.StrictSNIHost = opts.StrictSNIHost
232
+ }
233
+
234
+ return nil
235
+}