@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 501 lines 16.8 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 httpcaddyfile
16
17 import (
18 "fmt"
19 "net"
20 "net/netip"
21 "reflect"
22 "sort"
23 "strconv"
24 "strings"
25 "unicode"
26
27 "github.com/caddyserver/certmagic"
28
29 "github.com/caddyserver/caddy/v2"
30 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
31 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
32 )
33
34 // mapAddressToProtocolToServerBlocks returns a map of listener address to list of server
35 // blocks that will be served on that address. To do this, each server block is
36 // expanded so that each one is considered individually, although keys of a
37 // server block that share the same address stay grouped together so the config
38 // isn't repeated unnecessarily. For example, this Caddyfile:
39 //
40 // example.com {
41 // bind 127.0.0.1
42 // }
43 // www.example.com, example.net/path, localhost:9999 {
44 // bind 127.0.0.1 1.2.3.4
45 // }
46 //
47 // has two server blocks to start with. But expressed in this Caddyfile are
48 // actually 4 listener addresses: 127.0.0.1:443, 1.2.3.4:443, 127.0.0.1:9999,
49 // and 127.0.0.1:9999. This is because the bind directive is applied to each
50 // key of its server block (specifying the host part), and each key may have
51 // a different port. And we definitely need to be sure that a site which is
52 // bound to be served on a specific interface is not served on others just
53 // because that is more convenient: it would be a potential security risk
54 // if the difference between interfaces means private vs. public.
55 //
56 // So what this function does for the example above is iterate each server
57 // block, and for each server block, iterate its keys. For the first, it
58 // finds one key (example.com) and determines its listener address
59 // (127.0.0.1:443 - because of 'bind' and automatic HTTPS). It then adds
60 // the listener address to the map value returned by this function, with
61 // the first server block as one of its associations.
62 //
63 // It then iterates each key on the second server block and associates them
64 // with one or more listener addresses. Indeed, each key in this block has
65 // two listener addresses because of the 'bind' directive. Once we know
66 // which addresses serve which keys, we can create a new server block for
67 // each address containing the contents of the server block and only those
68 // specific keys of the server block which use that address.
69 //
70 // It is possible and even likely that some keys in the returned map have
71 // the exact same list of server blocks (i.e. they are identical). This
72 // happens when multiple hosts are declared with a 'bind' directive and
73 // the resulting listener addresses are not shared by any other server
74 // block (or the other server blocks are exactly identical in their token
75 // contents). This happens with our example above because 1.2.3.4:443
76 // and 1.2.3.4:9999 are used exclusively with the second server block. This
77 // repetition may be undesirable, so call consolidateAddrMappings() to map
78 // multiple addresses to the same lists of server blocks (a many:many mapping).
79 // (Doing this is essentially a map-reduce technique.)
80 func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock,
81 options map[string]any,
82 ) (map[string]map[string][]serverBlock, error) {
83 addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{}
84
85 type keyWithParsedKey struct {
86 key caddyfile.Token
87 parsedKey Address
88 }
89
90 for i, sblock := range originalServerBlocks {
91 // within a server block, we need to map all the listener addresses
92 // implied by the server block to the keys of the server block which
93 // will be served by them; this has the effect of treating each
94 // key of a server block as its own, but without having to repeat its
95 // contents in cases where multiple keys really can be served together
96 addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{}
97 for j, key := range sblock.block.Keys {
98 parsedKey, err := ParseAddress(key.Text)
99 if err != nil {
100 return nil, fmt.Errorf("parsing key: %v", err)
101 }
102 parsedKey = parsedKey.Normalize()
103
104 // a key can have multiple listener addresses if there are multiple
105 // arguments to the 'bind' directive (although they will all have
106 // the same port, since the port is defined by the key or is implicit
107 // through automatic HTTPS)
108 listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options)
109 if err != nil {
110 return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
111 }
112
113 // associate this key with its protocols and each listener address served with them
114 kwpk := keyWithParsedKey{key, parsedKey}
115 for addr, protocols := range listeners {
116 protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr]
117 if !ok {
118 protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{}
119 addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys
120 }
121
122 // an empty protocol indicates the default, a nil or empty value in the ListenProtocols array
123 if len(protocols) == 0 {
124 protocols[""] = struct{}{}
125 }
126 for prot := range protocols {
127 protocolToKeyWithParsedKeys[prot] = append(
128 protocolToKeyWithParsedKeys[prot],
129 kwpk)
130 }
131 }
132 }
133
134 // make a slice of the map keys so we can iterate in sorted order
135 addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys))
136 for addr := range addrToProtocolToKeyWithParsedKeys {
137 addrs = append(addrs, addr)
138 }
139 sort.Strings(addrs)
140
141 // now that we know which addresses serve which keys of this
142 // server block, we iterate that mapping and create a list of
143 // new server blocks for each address where the keys of the
144 // server block are only the ones which use the address; but
145 // the contents (tokens) are of course the same
146 for _, addr := range addrs {
147 protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr]
148
149 prots := make([]string, 0, len(protocolToKeyWithParsedKeys))
150 for prot := range protocolToKeyWithParsedKeys {
151 prots = append(prots, prot)
152 }
153 sort.Strings(prots)
154
155 protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr]
156 if !ok {
157 protocolToServerBlocks = map[string][]serverBlock{}
158 addrToProtocolToServerBlocks[addr] = protocolToServerBlocks
159 }
160
161 for _, prot := range prots {
162 keyWithParsedKeys := protocolToKeyWithParsedKeys[prot]
163
164 keys := make([]caddyfile.Token, len(keyWithParsedKeys))
165 parsedKeys := make([]Address, len(keyWithParsedKeys))
166
167 for k, keyWithParsedKey := range keyWithParsedKeys {
168 keys[k] = keyWithParsedKey.key
169 parsedKeys[k] = keyWithParsedKey.parsedKey
170 }
171
172 protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{
173 block: caddyfile.ServerBlock{
174 Keys: keys,
175 Segments: sblock.block.Segments,
176 },
177 pile: sblock.pile,
178 parsedKeys: parsedKeys,
179 })
180 }
181 }
182 }
183
184 return addrToProtocolToServerBlocks, nil
185 }
186
187 // consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
188 // single listener addresses to protocols to lists of server blocks. Since multiple addresses
189 // may serve multiple protocols to identical sites (server block contents), this function turns
190 // a 1:many mapping into a many:many mapping. Server block contents (tokens) must be
191 // exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined.
192 // Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
193 // association from multiple addresses to multiple server blocks; i.e. each element of
194 // the returned slice) becomes a server definition in the output JSON.
195 func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation {
196 sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks))
197
198 addrs := make([]string, 0, len(addrToProtocolToServerBlocks))
199 for addr := range addrToProtocolToServerBlocks {
200 addrs = append(addrs, addr)
201 }
202 sort.Strings(addrs)
203
204 for _, addr := range addrs {
205 protocolToServerBlocks := addrToProtocolToServerBlocks[addr]
206
207 prots := make([]string, 0, len(protocolToServerBlocks))
208 for prot := range protocolToServerBlocks {
209 prots = append(prots, prot)
210 }
211 sort.Strings(prots)
212
213 for _, prot := range prots {
214 serverBlocks := protocolToServerBlocks[prot]
215
216 // now find other addresses that map to identical
217 // server blocks and add them to our map of listener
218 // addresses and protocols, while removing them from
219 // the original map
220 listeners := map[string]map[string]struct{}{}
221
222 for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks {
223 for otherProt, otherServerBlocks := range otherProtocolToServerBlocks {
224 if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) {
225 listener, ok := listeners[otherAddr]
226 if !ok {
227 listener = map[string]struct{}{}
228 listeners[otherAddr] = listener
229 }
230 listener[otherProt] = struct{}{}
231 delete(otherProtocolToServerBlocks, otherProt)
232 }
233 }
234 }
235
236 addresses := make([]string, 0, len(listeners))
237 for lnAddr := range listeners {
238 addresses = append(addresses, lnAddr)
239 }
240 sort.Strings(addresses)
241
242 addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners))
243
244 for _, lnAddr := range addresses {
245 lnProts := listeners[lnAddr]
246 prots := make([]string, 0, len(lnProts))
247 for prot := range lnProts {
248 prots = append(prots, prot)
249 }
250 sort.Strings(prots)
251
252 addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{
253 address: lnAddr,
254 protocols: prots,
255 })
256 }
257
258 sbaddrs = append(sbaddrs, sbAddrAssociation{
259 addressesWithProtocols: addressesWithProtocols,
260 serverBlocks: serverBlocks,
261 })
262 }
263 }
264
265 return sbaddrs
266 }
267
268 // listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from
269 // Caddy listener addresses and the protocols to serve them with to the parsed address for each server block.
270 func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address,
271 options map[string]any,
272 ) (map[string]map[string]struct{}, error) {
273 switch addr.Scheme {
274 case "wss":
275 return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
276 case "ws":
277 return nil, fmt.Errorf("the scheme ws:// is only supported in browsers; use http:// instead")
278 case "https", "http", "":
279 // Do nothing or handle the valid schemes
280 default:
281 return nil, fmt.Errorf("unsupported URL scheme %s://", addr.Scheme)
282 }
283
284 // figure out the HTTP and HTTPS ports; either
285 // use defaults, or override with user config
286 httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
287 if hport, ok := options["http_port"]; ok {
288 httpPort = strconv.Itoa(hport.(int))
289 }
290 if hsport, ok := options["https_port"]; ok {
291 httpsPort = strconv.Itoa(hsport.(int))
292 }
293
294 // default port is the HTTPS port
295 lnPort := httpsPort
296 if addr.Port != "" {
297 // port explicitly defined
298 lnPort = addr.Port
299 } else if addr.Scheme == "http" {
300 // port inferred from scheme
301 lnPort = httpPort
302 }
303
304 // error if scheme and port combination violate convention
305 if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
306 return nil, fmt.Errorf("[%s] scheme and port violate convention", addr.String())
307 }
308
309 // the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional
310 lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"]))
311 for _, cfgVal := range sblock.pile["bind"] {
312 if val, ok := cfgVal.Value.(addressesWithProtocols); ok {
313 lnCfgVals = append(lnCfgVals, val)
314 }
315 }
316 if len(lnCfgVals) == 0 {
317 if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok {
318 for _, defaultBindValue := range defaultBindValues {
319 lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols))
320 }
321 } else {
322 lnCfgVals = []addressesWithProtocols{{
323 addresses: []string{""},
324 protocols: nil,
325 }}
326 }
327 }
328
329 // use a map to prevent duplication
330 listeners := map[string]map[string]struct{}{}
331 for _, lnCfgVal := range lnCfgVals {
332 for _, lnAddr := range lnCfgVal.addresses {
333 lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr)
334 if err != nil {
335 return nil, fmt.Errorf("splitting listener address: %v", err)
336 }
337 networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort))
338 if err != nil {
339 return nil, fmt.Errorf("parsing network address: %v", err)
340 }
341 if _, ok := listeners[addr.String()]; !ok {
342 listeners[networkAddr.String()] = map[string]struct{}{}
343 }
344 for _, protocol := range lnCfgVal.protocols {
345 listeners[networkAddr.String()][protocol] = struct{}{}
346 }
347 }
348 }
349
350 return listeners, nil
351 }
352
353 // addressesWithProtocols associates a list of listen addresses
354 // with a list of protocols to serve them with
355 type addressesWithProtocols struct {
356 addresses []string
357 protocols []string
358 }
359
360 // Address represents a site address. It contains
361 // the original input value, and the component
362 // parts of an address. The component parts may be
363 // updated to the correct values as setup proceeds,
364 // but the original value should never be changed.
365 //
366 // The Host field must be in a normalized form.
367 type Address struct {
368 Original, Scheme, Host, Port, Path string
369 }
370
371 // ParseAddress parses an address string into a structured format with separate
372 // scheme, host, port, and path portions, as well as the original input string.
373 func ParseAddress(str string) (Address, error) {
374 const maxLen = 4096
375 if len(str) > maxLen {
376 str = str[:maxLen]
377 }
378 remaining := strings.TrimSpace(str)
379 a := Address{Original: remaining}
380
381 // extract scheme
382 splitScheme := strings.SplitN(remaining, "://", 2)
383 switch len(splitScheme) {
384 case 0:
385 return a, nil
386 case 1:
387 remaining = splitScheme[0]
388 case 2:
389 a.Scheme = splitScheme[0]
390 remaining = splitScheme[1]
391 }
392
393 // extract host and port
394 hostSplit := strings.SplitN(remaining, "/", 2)
395 if len(hostSplit) > 0 {
396 host, port, err := net.SplitHostPort(hostSplit[0])
397 if err != nil {
398 host, port, err = net.SplitHostPort(hostSplit[0] + ":")
399 if err != nil {
400 host = hostSplit[0]
401 }
402 }
403 a.Host = host
404 a.Port = port
405 }
406 if len(hostSplit) == 2 {
407 // all that remains is the path
408 a.Path = "/" + hostSplit[1]
409 }
410
411 // make sure port is valid
412 if a.Port != "" {
413 if portNum, err := strconv.Atoi(a.Port); err != nil {
414 return Address{}, fmt.Errorf("invalid port '%s': %v", a.Port, err)
415 } else if portNum < 0 || portNum > 65535 {
416 return Address{}, fmt.Errorf("port %d is out of range", portNum)
417 }
418 }
419
420 return a, nil
421 }
422
423 // String returns a human-readable form of a. It will
424 // be a cleaned-up and filled-out URL string.
425 func (a Address) String() string {
426 if a.Host == "" && a.Port == "" {
427 return ""
428 }
429 scheme := a.Scheme
430 if scheme == "" {
431 if a.Port == strconv.Itoa(certmagic.HTTPSPort) {
432 scheme = "https"
433 } else {
434 scheme = "http"
435 }
436 }
437 s := scheme
438 if s != "" {
439 s += "://"
440 }
441 if a.Port != "" &&
442 ((scheme == "https" && a.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort)) ||
443 (scheme == "http" && a.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort))) {
444 s += net.JoinHostPort(a.Host, a.Port)
445 } else {
446 s += a.Host
447 }
448 if a.Path != "" {
449 s += a.Path
450 }
451 return s
452 }
453
454 // Normalize returns a normalized version of a.
455 func (a Address) Normalize() Address {
456 path := a.Path
457
458 // ensure host is normalized if it's an IP address
459 host := strings.TrimSpace(a.Host)
460 if ip, err := netip.ParseAddr(host); err == nil {
461 if ip.Is6() && !ip.Is4() && !ip.Is4In6() {
462 host = ip.String()
463 }
464 }
465
466 return Address{
467 Original: a.Original,
468 Scheme: lowerExceptPlaceholders(a.Scheme),
469 Host: lowerExceptPlaceholders(host),
470 Port: a.Port,
471 Path: path,
472 }
473 }
474
475 // lowerExceptPlaceholders lowercases s except within
476 // placeholders (substrings in non-escaped '{ }' spans).
477 // See https://github.com/caddyserver/caddy/issues/3264
478 func lowerExceptPlaceholders(s string) string {
479 var sb strings.Builder
480 var escaped, inPlaceholder bool
481 for _, ch := range s {
482 if ch == '\\' && !escaped {
483 escaped = true
484 sb.WriteRune(ch)
485 continue
486 }
487 if ch == '{' && !escaped {
488 inPlaceholder = true
489 }
490 if ch == '}' && inPlaceholder && !escaped {
491 inPlaceholder = false
492 }
493 if inPlaceholder {
494 sb.WriteRune(ch)
495 } else {
496 sb.WriteRune(unicode.ToLower(ch))
497 }
498 escaped = false
499 }
500 return sb.String()
501 }