You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							332 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							332 lines
						
					
					
						
							11 KiB
						
					
					
				| // Copyright 2015 Matthew Holt and The Caddy Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| //     http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
|  | |
| package httpcaddyfile | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"net" | |
| 	"net/url" | |
| 	"reflect" | |
| 	"strconv" | |
| 	"strings" | |
| 
 | |
| 	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" | |
| 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | |
| 	"github.com/mholt/certmagic" | |
| ) | |
| 
 | |
| // mapAddressToServerBlocks returns a map of listener address to list of server | |
| // blocks that will be served on that address. To do this, each server block is | |
| // expanded so that each one is considered individually, although keys of a | |
| // server block that share the same address stay grouped together so the config | |
| // isn't repeated unnecessarily. For example, this Caddyfile: | |
| // | |
| // 	example.com { | |
| // 		bind 127.0.0.1 | |
| // 	} | |
| // 	www.example.com, example.net/path, localhost:9999 { | |
| // 		bind 127.0.0.1 1.2.3.4 | |
| // 	} | |
| // | |
| // has two server blocks to start with. But expressed in this Caddyfile are | |
| // actually 4 listener addresses: 127.0.0.1:443, 1.2.3.4:443, 127.0.0.1:9999, | |
| // and 127.0.0.1:9999. This is because the bind directive is applied to each | |
| // key of its server block (specifying the host part), and each key may have | |
| // a different port. And we definitely need to be sure that a site which is | |
| // bound to be served on a specific interface is not served on others just | |
| // beceause that is more convenient: it would be a potential security risk | |
| // if the difference between interfaces means private vs. public. | |
| // | |
| // So what this function does for the example above is iterate each server | |
| // block, and for each server block, iterate its keys. For the first, it | |
| // finds one key (example.com) and determines its listener address | |
| // (127.0.0.1:443 - because of 'bind' and automatic HTTPS). It then adds | |
| // the listener address to the map value returned by this function, with | |
| // the first server block as one of its associations. | |
| // | |
| // It then iterates each key on the second server block and associates them | |
| // with one or more listener addresses. Indeed, each key in this block has | |
| // two listener addresses because of the 'bind' directive. Once we know | |
| // which addresses serve which keys, we can create a new server block for | |
| // each address containing the contents of the server block and only those | |
| // specific keys of the server block which use that address. | |
| // | |
| // It is possible and even likely that some keys in the returned map have | |
| // the exact same list of server blocks (i.e. they are identical). This | |
| // happens when multiple hosts are declared with a 'bind' directive and | |
| // the resulting listener addresses are not shared by any other server | |
| // block (or the other server blocks are exactly identical in their token | |
| // contents). This happens with our example above because 1.2.3.4:443 | |
| // and 1.2.3.4:9999 are used exclusively with the second server block. This | |
| // repetition may be undesirable, so call consolidateAddrMappings() to map | |
| // multiple addresses to the same lists of server blocks (a many:many mapping). | |
| // (Doing this is essentially a map-reduce technique.) | |
| func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock) (map[string][]serverBlock, error) { | |
| 	sbmap := make(map[string][]serverBlock) | |
| 
 | |
| 	for i, sblock := range originalServerBlocks { | |
| 		// within a server block, we need to map all the listener addresses | |
| 		// implied by the server block to the keys of the server block which | |
| 		// will be served by them; this has the effect of treating each | |
| 		// key of a server block as its own, but without having to repeat its | |
| 		// contents in cases where multiple keys really can be served together | |
| 		addrToKeys := make(map[string][]string) | |
| 		for j, key := range sblock.block.Keys { | |
| 			// a key can have multiple listener addresses if there are multiple | |
| 			// arguments to the 'bind' directive (although they will all have | |
| 			// the same port, since the port is defined by the key or is implicit | |
| 			// through automatic HTTPS) | |
| 			addrs, err := st.listenerAddrsForServerBlockKey(sblock, key) | |
| 			if err != nil { | |
| 				return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key, err) | |
| 			} | |
| 
 | |
| 			// associate this key with each listener address it is served on | |
| 			for _, addr := range addrs { | |
| 				addrToKeys[addr] = append(addrToKeys[addr], key) | |
| 			} | |
| 		} | |
| 
 | |
| 		// now that we know which addresses serve which keys of this | |
| 		// server block, we iterate that mapping and create a list of | |
| 		// new server blocks for each address where the keys of the | |
| 		// server block are only the ones which use the address; but | |
| 		// the contents (tokens) are of course the same | |
| 		for addr, keys := range addrToKeys { | |
| 			sbmap[addr] = append(sbmap[addr], serverBlock{ | |
| 				block: caddyfile.ServerBlock{ | |
| 					Keys:     keys, | |
| 					Segments: sblock.block.Segments, | |
| 				}, | |
| 				pile: sblock.pile, | |
| 			}) | |
| 		} | |
| 	} | |
| 
 | |
| 	return sbmap, nil | |
| } | |
| 
 | |
| // consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of | |
| // single listener addresses to lists of server blocks. Since multiple addresses may serve | |
| // identical sites (server block contents), this function turns a 1:many mapping into a | |
| // many:many mapping. Server block contents (tokens) must be exactly identical so that | |
| // reflect.DeepEqual returns true in order for the addresses to be combined. Identical | |
| // entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each | |
| // association from multiple addresses to multiple server blocks; i.e. each element of | |
| // the returned slice) becomes a server definition in the output JSON. | |
| func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation { | |
| 	var sbaddrs []sbAddrAssociation | |
| 	for addr, sblocks := range addrToServerBlocks { | |
| 		// we start with knowing that at least this address | |
| 		// maps to these server blocks | |
| 		a := sbAddrAssociation{ | |
| 			addresses:    []string{addr}, | |
| 			serverBlocks: sblocks, | |
| 		} | |
| 
 | |
| 		// now find other addresses that map to identical | |
| 		// server blocks and add them to our list of | |
| 		// addresses, while removing them from the map | |
| 		for otherAddr, otherSblocks := range addrToServerBlocks { | |
| 			if addr == otherAddr { | |
| 				continue | |
| 			} | |
| 			if reflect.DeepEqual(sblocks, otherSblocks) { | |
| 				a.addresses = append(a.addresses, otherAddr) | |
| 				delete(addrToServerBlocks, otherAddr) | |
| 			} | |
| 		} | |
| 
 | |
| 		sbaddrs = append(sbaddrs, a) | |
| 	} | |
| 	return sbaddrs | |
| } | |
| 
 | |
| func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string) ([]string, error) { | |
| 	addr, err := ParseAddress(key) | |
| 	if err != nil { | |
| 		return nil, fmt.Errorf("parsing key: %v", err) | |
| 	} | |
| 	addr = addr.Normalize() | |
| 
 | |
| 	lnPort := defaultPort | |
| 	if addr.Port != "" { | |
| 		// port explicitly defined | |
| 		lnPort = addr.Port | |
| 	} else if certmagic.HostQualifies(addr.Host) { | |
| 		// automatic HTTPS | |
| 		lnPort = strconv.Itoa(certmagic.HTTPSPort) | |
| 	} | |
| 
 | |
| 	// the bind directive specifies hosts, but is optional | |
| 	var lnHosts []string | |
| 	for _, cfgVal := range sblock.pile["bind"] { | |
| 		lnHosts = append(lnHosts, cfgVal.Value.([]string)...) | |
| 	} | |
| 	if len(lnHosts) == 0 { | |
| 		lnHosts = []string{""} | |
| 	} | |
| 
 | |
| 	// use a map to prevent duplication | |
| 	listeners := make(map[string]struct{}) | |
| 	for _, host := range lnHosts { | |
| 		listeners[net.JoinHostPort(host, lnPort)] = struct{}{} | |
| 	} | |
| 
 | |
| 	// now turn map into list | |
| 	var listenersList []string | |
| 	for lnStr := range listeners { | |
| 		listenersList = append(listenersList, lnStr) | |
| 	} | |
| 	// sort.Strings(listenersList) // TODO: is sorting necessary? | |
|  | |
| 	return listenersList, nil | |
| } | |
| 
 | |
| // Address represents a site address. It contains | |
| // the original input value, and the component | |
| // parts of an address. The component parts may be | |
| // updated to the correct values as setup proceeds, | |
| // but the original value should never be changed. | |
| // | |
| // The Host field must be in a normalized form. | |
| type Address struct { | |
| 	Original, Scheme, Host, Port, Path string | |
| } | |
| 
 | |
| // ParseAddress parses an address string into a structured format with separate | |
| // scheme, host, port, and path portions, as well as the original input string. | |
| func ParseAddress(str string) (Address, error) { | |
| 	httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort) | |
| 
 | |
| 	input := str | |
| 
 | |
| 	// Split input into components (prepend with // to force host portion by default) | |
| 	if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") { | |
| 		str = "//" + str | |
| 	} | |
| 
 | |
| 	u, err := url.Parse(str) | |
| 	if err != nil { | |
| 		return Address{}, err | |
| 	} | |
| 
 | |
| 	// separate host and port | |
| 	host, port, err := net.SplitHostPort(u.Host) | |
| 	if err != nil { | |
| 		host, port, err = net.SplitHostPort(u.Host + ":") | |
| 		if err != nil { | |
| 			host = u.Host | |
| 		} | |
| 	} | |
| 
 | |
| 	// see if we can set port based off scheme | |
| 	if port == "" { | |
| 		if u.Scheme == "http" { | |
| 			port = httpPort | |
| 		} else if u.Scheme == "https" { | |
| 			port = httpsPort | |
| 		} | |
| 	} | |
| 
 | |
| 	// error if scheme and port combination violate convention | |
| 	if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) { | |
| 		return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input) | |
| 	} | |
| 
 | |
| 	return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err | |
| } | |
| 
 | |
| // TODO: which of the methods on Address are even used? | |
|  | |
| // String returns a human-readable form of a. It will | |
| // be a cleaned-up and filled-out URL string. | |
| func (a Address) String() string { | |
| 	if a.Host == "" && a.Port == "" { | |
| 		return "" | |
| 	} | |
| 	scheme := a.Scheme | |
| 	if scheme == "" { | |
| 		if a.Port == strconv.Itoa(certmagic.HTTPSPort) { | |
| 			scheme = "https" | |
| 		} else { | |
| 			scheme = "http" | |
| 		} | |
| 	} | |
| 	s := scheme | |
| 	if s != "" { | |
| 		s += "://" | |
| 	} | |
| 	if a.Port != "" && | |
| 		((scheme == "https" && a.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort)) || | |
| 			(scheme == "http" && a.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort))) { | |
| 		s += net.JoinHostPort(a.Host, a.Port) | |
| 	} else { | |
| 		s += a.Host | |
| 	} | |
| 	if a.Path != "" { | |
| 		s += a.Path | |
| 	} | |
| 	return s | |
| } | |
| 
 | |
| // Normalize returns a normalized version of a. | |
| func (a Address) Normalize() Address { | |
| 	path := a.Path | |
| 	if !caseSensitivePath { | |
| 		path = strings.ToLower(path) | |
| 	} | |
| 
 | |
| 	// ensure host is normalized if it's an IP address | |
| 	host := a.Host | |
| 	if ip := net.ParseIP(host); ip != nil { | |
| 		host = ip.String() | |
| 	} | |
| 
 | |
| 	return Address{ | |
| 		Original: a.Original, | |
| 		Scheme:   strings.ToLower(a.Scheme), | |
| 		Host:     strings.ToLower(host), | |
| 		Port:     a.Port, | |
| 		Path:     path, | |
| 	} | |
| } | |
| 
 | |
| // Key returns a string form of a, much like String() does, but this | |
| // method doesn't add anything default that wasn't in the original. | |
| func (a Address) Key() string { | |
| 	res := "" | |
| 	if a.Scheme != "" { | |
| 		res += a.Scheme + "://" | |
| 	} | |
| 	if a.Host != "" { | |
| 		res += a.Host | |
| 	} | |
| 	// insert port only if the original has its own explicit port | |
| 	if a.Port != "" && | |
| 		len(a.Original) >= len(res) && | |
| 		strings.HasPrefix(a.Original[len(res):], ":"+a.Port) { | |
| 		res += ":" + a.Port | |
| 	} | |
| 	if a.Path != "" { | |
| 		res += a.Path | |
| 	} | |
| 	return res | |
| } | |
| 
 | |
| const ( | |
| 	defaultPort       = "2015" | |
| 	caseSensitivePath = false // TODO: Used? | |
| )
 | |
| 
 |