2019-08-09 21:05:47 +03:00
|
|
|
// 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 (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
2019-08-21 19:46:35 +03:00
|
|
|
"sort"
|
2019-08-09 21:05:47 +03:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
|
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
|
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
|
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
2019-08-21 19:46:35 +03:00
|
|
|
"github.com/mholt/certmagic"
|
2019-08-09 21:05:47 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
caddyconfig.RegisterAdapter("caddyfile", caddyfile.Adapter{ServerType: ServerType{}})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServerType can set up a config from an HTTP Caddyfile.
|
|
|
|
type ServerType struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup makes a config from the tokens.
|
|
|
|
func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
2019-08-22 22:38:37 +03:00
|
|
|
options map[string]interface{}) (*caddy.Config, []caddyconfig.Warning, error) {
|
2019-08-09 21:05:47 +03:00
|
|
|
var warnings []caddyconfig.Warning
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
var serverBlocks []serverBlock
|
|
|
|
for _, sblock := range originalServerBlocks {
|
|
|
|
serverBlocks = append(serverBlocks, serverBlock{
|
|
|
|
block: sblock,
|
|
|
|
pile: make(map[string][]ConfigValue),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-08-22 22:38:37 +03:00
|
|
|
// global configuration
|
|
|
|
if len(serverBlocks) > 0 && len(serverBlocks[0].block.Keys) == 0 {
|
|
|
|
sb := serverBlocks[0]
|
|
|
|
for _, segment := range sb.block.Segments {
|
|
|
|
dir := segment.Directive()
|
|
|
|
var val interface{}
|
|
|
|
var err error
|
|
|
|
switch dir {
|
|
|
|
case "http_port":
|
|
|
|
val, err = parseHTTPPort(caddyfile.NewDispenser(segment))
|
|
|
|
case "https_port":
|
|
|
|
val, err = parseHTTPSPort(caddyfile.NewDispenser(segment))
|
2019-08-22 23:26:33 +03:00
|
|
|
case "handler_order":
|
|
|
|
val, err = parseHandlerOrder(caddyfile.NewDispenser(segment))
|
2019-09-12 02:16:21 +03:00
|
|
|
case "experimental_http3":
|
|
|
|
val, err = parseExperimentalHTTP3(caddyfile.NewDispenser(segment))
|
2019-08-22 22:38:37 +03:00
|
|
|
default:
|
|
|
|
return nil, warnings, fmt.Errorf("unrecognized parameter name: %s", dir)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, warnings, fmt.Errorf("%s: %v", dir, err)
|
|
|
|
}
|
|
|
|
options[dir] = val
|
|
|
|
}
|
|
|
|
serverBlocks = serverBlocks[1:]
|
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
for _, sb := range serverBlocks {
|
2019-08-21 20:03:50 +03:00
|
|
|
// replace shorthand placeholders (which are
|
|
|
|
// convenient when writing a Caddyfile) with
|
|
|
|
// their actual placeholder identifiers or
|
|
|
|
// variable names
|
|
|
|
replacer := strings.NewReplacer(
|
|
|
|
"{uri}", "{http.request.uri}",
|
|
|
|
"{path}", "{http.request.uri.path}",
|
|
|
|
"{host}", "{http.request.host}",
|
|
|
|
"{hostport}", "{http.request.hostport}",
|
|
|
|
"{method}", "{http.request.method}",
|
|
|
|
"{scheme}", "{http.request.scheme}",
|
|
|
|
"{file}", "{http.request.uri.path.file}",
|
|
|
|
"{dir}", "{http.request.uri.path.dir}",
|
2019-08-27 23:41:57 +03:00
|
|
|
"{query}", "{http.request.uri.query_string}",
|
2019-08-21 20:03:50 +03:00
|
|
|
)
|
|
|
|
for _, segment := range sb.block.Segments {
|
|
|
|
for i := 0; i < len(segment); i++ {
|
|
|
|
segment[i].Text = replacer.Replace(segment[i].Text)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-22 22:38:37 +03:00
|
|
|
if len(sb.block.Keys) == 0 {
|
|
|
|
return nil, warnings, fmt.Errorf("server block without any key is global configuration, and if used, it must be first")
|
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
// extract matcher definitions
|
|
|
|
d := sb.block.DispenseDirective("matcher")
|
|
|
|
matcherDefs, err := st.parseMatcherDefinitions(d)
|
|
|
|
if err != nil {
|
2019-08-22 22:38:37 +03:00
|
|
|
return nil, warnings, err
|
2019-08-21 19:46:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, segment := range sb.block.Segments {
|
|
|
|
dir := segment.Directive()
|
|
|
|
if dir == "matcher" {
|
|
|
|
// TODO: This is a special case because we pre-processed it; handle this better
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if dirFunc, ok := registeredDirectives[dir]; ok {
|
|
|
|
results, err := dirFunc(Helper{
|
2019-08-22 00:23:00 +03:00
|
|
|
Dispenser: caddyfile.NewDispenser(segment),
|
2019-08-21 19:46:35 +03:00
|
|
|
warnings: &warnings,
|
|
|
|
matcherDefs: matcherDefs,
|
2019-08-22 00:50:02 +03:00
|
|
|
parentBlock: sb.block,
|
2019-08-21 19:46:35 +03:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
|
|
|
|
}
|
|
|
|
for _, result := range results {
|
|
|
|
result.directive = dir
|
|
|
|
sb.pile[result.Class] = append(sb.pile[result.Class], result)
|
|
|
|
}
|
|
|
|
} else {
|
2019-08-21 20:03:50 +03:00
|
|
|
tkn := segment[0]
|
|
|
|
return nil, warnings, fmt.Errorf("%s:%d: unrecognized directive: %s", tkn.File, tkn.Line, dir)
|
2019-08-21 19:46:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-09 21:05:47 +03:00
|
|
|
// map
|
2019-08-21 19:46:35 +03:00
|
|
|
sbmap, err := st.mapAddressToServerBlocks(serverBlocks)
|
2019-08-09 21:05:47 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, warnings, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// reduce
|
|
|
|
pairings := st.consolidateAddrMappings(sbmap)
|
|
|
|
|
|
|
|
// each pairing of listener addresses to list of server
|
|
|
|
// blocks is basically a server definition
|
2019-08-22 23:26:33 +03:00
|
|
|
servers, err := st.serversFromPairings(pairings, options, &warnings)
|
2019-08-09 21:05:47 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, warnings, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// now that each server is configured, make the HTTP app
|
|
|
|
httpApp := caddyhttp.App{
|
2019-08-22 22:38:37 +03:00
|
|
|
HTTPPort: tryInt(options["http_port"], &warnings),
|
|
|
|
HTTPSPort: tryInt(options["https_port"], &warnings),
|
2019-08-09 21:05:47 +03:00
|
|
|
Servers: servers,
|
|
|
|
}
|
|
|
|
|
|
|
|
// now for the TLS app! (TODO: refactor into own func)
|
|
|
|
tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)}
|
|
|
|
for _, p := range pairings {
|
|
|
|
for _, sblock := range p.serverBlocks {
|
2019-08-21 19:46:35 +03:00
|
|
|
// tls automation policies
|
|
|
|
if mmVals, ok := sblock.pile["tls.automation_manager"]; ok {
|
|
|
|
for _, mmVal := range mmVals {
|
|
|
|
mm := mmVal.Value.(caddytls.ManagerMaker)
|
|
|
|
sblockHosts, err := st.autoHTTPSHosts(sblock)
|
2019-08-09 21:05:47 +03:00
|
|
|
if err != nil {
|
2019-08-21 19:46:35 +03:00
|
|
|
return nil, warnings, err
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
|
|
|
|
Hosts: sblockHosts,
|
|
|
|
ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings),
|
|
|
|
})
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
}
|
2019-08-09 21:05:47 +03:00
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
// tls certificate loaders
|
|
|
|
if clVals, ok := sblock.pile["tls.certificate_loader"]; ok {
|
|
|
|
for _, clVal := range clVals {
|
|
|
|
loader := clVal.Value.(caddytls.CertificateLoader)
|
2019-09-13 18:45:10 +03:00
|
|
|
loaderName := caddy.GetModuleID(loader)
|
2019-08-09 21:05:47 +03:00
|
|
|
tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
// consolidate automation policies that are the exact same
|
|
|
|
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
|
2019-08-09 21:05:47 +03:00
|
|
|
|
2019-09-12 02:16:21 +03:00
|
|
|
// if experimental HTTP/3 is enabled, enable it on each server
|
|
|
|
if enableH3, ok := options["experimental_http3"].(bool); ok && enableH3 {
|
|
|
|
for _, srv := range httpApp.Servers {
|
|
|
|
srv.ExperimentalHTTP3 = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-09 21:05:47 +03:00
|
|
|
// annnd the top-level config, then we're done!
|
|
|
|
cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)}
|
|
|
|
if !reflect.DeepEqual(httpApp, caddyhttp.App{}) {
|
|
|
|
cfg.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings)
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(tlsApp, caddytls.TLS{}) {
|
|
|
|
cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cfg, warnings, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// hostsFromServerBlockKeys returns a list of all the
|
|
|
|
// hostnames found in the keys of the server block sb.
|
|
|
|
// The list may not be in a consistent order.
|
|
|
|
func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock) ([]string, error) {
|
|
|
|
// first get each unique hostname
|
|
|
|
hostMap := make(map[string]struct{})
|
|
|
|
for _, sblockKey := range sb.Keys {
|
2019-08-21 19:46:35 +03:00
|
|
|
addr, err := ParseAddress(sblockKey)
|
2019-08-09 21:05:47 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("parsing server block key: %v", err)
|
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
addr = addr.Normalize()
|
2019-08-09 21:05:47 +03:00
|
|
|
hostMap[addr.Host] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert map to slice
|
|
|
|
sblockHosts := make([]string, 0, len(hostMap))
|
|
|
|
for host := range hostMap {
|
|
|
|
sblockHosts = append(sblockHosts, host)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sblockHosts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// serversFromPairings creates the servers for each pairing of addresses
|
|
|
|
// to server blocks. Each pairing is essentially a server definition.
|
2019-08-22 23:26:33 +03:00
|
|
|
func (st *ServerType) serversFromPairings(
|
|
|
|
pairings []sbAddrAssociation,
|
|
|
|
options map[string]interface{},
|
|
|
|
warnings *[]caddyconfig.Warning,
|
|
|
|
) (map[string]*caddyhttp.Server, error) {
|
2019-08-09 21:05:47 +03:00
|
|
|
servers := make(map[string]*caddyhttp.Server)
|
|
|
|
|
|
|
|
for i, p := range pairings {
|
|
|
|
srv := &caddyhttp.Server{
|
|
|
|
Listen: p.addresses,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, sblock := range p.serverBlocks {
|
2019-08-21 19:46:35 +03:00
|
|
|
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock.block)
|
2019-08-09 21:05:47 +03:00
|
|
|
if err != nil {
|
2019-08-21 19:46:35 +03:00
|
|
|
return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
// if there are user-defined variables, then siteVarSubroute will
|
|
|
|
// wrap the handlerSubroute; otherwise handlerSubroute will be the
|
|
|
|
// site's primary subroute.
|
2019-08-09 21:05:47 +03:00
|
|
|
siteVarSubroute, handlerSubroute := new(caddyhttp.Subroute), new(caddyhttp.Subroute)
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
// tls: connection policies and toggle auto HTTPS
|
2019-08-09 21:05:47 +03:00
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-09-18 19:51:49 +03:00
|
|
|
if _, ok := sblock.pile["tls.off"]; ok && len(autoHTTPSQualifiedHosts) > 0 {
|
2019-08-21 19:46:35 +03:00
|
|
|
// tls off: disable TLS (and automatic HTTPS) for server block's names
|
2019-09-18 19:51:49 +03:00
|
|
|
if srv.AutoHTTPS == nil {
|
2019-08-21 19:46:35 +03:00
|
|
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
|
|
|
|
} else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
|
|
|
|
// tls connection policies
|
|
|
|
for _, cpVal := range cpVals {
|
|
|
|
cp := cpVal.Value.(*caddytls.ConnectionPolicy)
|
|
|
|
// only create if there is a non-empty policy
|
|
|
|
if !reflect.DeepEqual(cp, new(caddytls.ConnectionPolicy)) {
|
|
|
|
// make sure the policy covers all hostnames from the block
|
|
|
|
hosts, err := st.hostsFromServerBlockKeys(sblock.block)
|
2019-08-09 21:05:47 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
// TODO: are matchers needed if every hostname of the config is matched?
|
|
|
|
cp.Matchers = map[string]json.RawMessage{
|
|
|
|
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
// TODO: consolidate equal conn policies
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
// vars: special routes that will have to wrap the normal handlers
|
|
|
|
// so that these variables can be used across their matchers too
|
|
|
|
for _, cfgVal := range sblock.pile["var"] {
|
|
|
|
siteVarSubroute.Routes = append(siteVarSubroute.Routes, cfgVal.Value.(caddyhttp.Route))
|
|
|
|
}
|
|
|
|
|
2019-08-22 23:26:33 +03:00
|
|
|
// set up each handler directive - the order of the handlers
|
|
|
|
// as they are added to the routes depends on user preference
|
2019-08-21 19:46:35 +03:00
|
|
|
dirRoutes := sblock.pile["route"]
|
2019-08-22 23:26:33 +03:00
|
|
|
handlerOrder, ok := options["handler_order"].([]string)
|
|
|
|
if !ok {
|
|
|
|
handlerOrder = defaultDirectiveOrder
|
|
|
|
}
|
|
|
|
if len(handlerOrder) == 1 && handlerOrder[0] == "appearance" {
|
|
|
|
handlerOrder = nil
|
|
|
|
}
|
|
|
|
if handlerOrder != nil {
|
2019-08-21 19:46:35 +03:00
|
|
|
dirPositions := make(map[string]int)
|
2019-08-22 23:26:33 +03:00
|
|
|
for i, dir := range handlerOrder {
|
2019-08-21 19:46:35 +03:00
|
|
|
dirPositions[dir] = i
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
sort.SliceStable(dirRoutes, func(i, j int) bool {
|
|
|
|
iDir, jDir := dirRoutes[i].directive, dirRoutes[j].directive
|
|
|
|
return dirPositions[iDir] < dirPositions[jDir]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
for _, r := range dirRoutes {
|
|
|
|
handlerSubroute.Routes = append(handlerSubroute.Routes, r.Value.(caddyhttp.Route))
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// the route that contains the site's handlers will
|
|
|
|
// be assumed to be the sub-route for this site...
|
|
|
|
siteSubroute := handlerSubroute
|
|
|
|
|
|
|
|
// ... unless, of course, there are variables that might
|
|
|
|
// be used by the site's matchers or handlers, in which
|
|
|
|
// case we need to nest the handlers in a sub-sub-route,
|
|
|
|
// and the variables go in the sub-route so the variables
|
|
|
|
// get evaluated first
|
|
|
|
if len(siteVarSubroute.Routes) > 0 {
|
|
|
|
subSubRoute := caddyhttp.Subroute{Routes: siteSubroute.Routes}
|
|
|
|
siteSubroute.Routes = append(
|
|
|
|
siteVarSubroute.Routes,
|
|
|
|
caddyhttp.Route{
|
2019-08-21 19:46:35 +03:00
|
|
|
HandlersRaw: []json.RawMessage{
|
2019-08-09 21:05:47 +03:00
|
|
|
caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
|
|
|
|
|
|
|
|
srv.Routes = append(srv.Routes, caddyhttp.Route{
|
2019-08-21 19:46:35 +03:00
|
|
|
MatcherSetsRaw: matcherSetsEnc,
|
|
|
|
HandlersRaw: []json.RawMessage{
|
2019-08-09 21:05:47 +03:00
|
|
|
caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
srv.Routes = consolidateRoutes(srv.Routes)
|
|
|
|
|
|
|
|
servers[fmt.Sprintf("srv%d", i)] = srv
|
|
|
|
}
|
|
|
|
|
|
|
|
return servers, nil
|
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
func (st ServerType) autoHTTPSHosts(sb serverBlock) ([]string, error) {
|
|
|
|
// get the hosts for this server block...
|
|
|
|
hosts, err := st.hostsFromServerBlockKeys(sb.block)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// ...and of those, which ones qualify for auto HTTPS
|
|
|
|
var autoHTTPSQualifiedHosts []string
|
|
|
|
for _, h := range hosts {
|
|
|
|
if certmagic.HostQualifies(h) {
|
|
|
|
autoHTTPSQualifiedHosts = append(autoHTTPSQualifiedHosts, h)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return autoHTTPSQualifiedHosts, nil
|
|
|
|
}
|
|
|
|
|
2019-08-09 21:05:47 +03:00
|
|
|
// consolidateRoutes combines routes with the same properties
|
|
|
|
// (same matchers, same Terminal and Group settings) for a
|
|
|
|
// cleaner overall output.
|
|
|
|
func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
|
|
|
|
for i := 0; i < len(routes)-1; i++ {
|
2019-08-21 19:46:35 +03:00
|
|
|
if reflect.DeepEqual(routes[i].MatcherSetsRaw, routes[i+1].MatcherSetsRaw) &&
|
2019-08-09 21:05:47 +03:00
|
|
|
routes[i].Terminal == routes[i+1].Terminal &&
|
|
|
|
routes[i].Group == routes[i+1].Group {
|
|
|
|
// keep the handlers in the same order, then splice out repetitive route
|
2019-08-21 19:46:35 +03:00
|
|
|
routes[i].HandlersRaw = append(routes[i].HandlersRaw, routes[i+1].HandlersRaw...)
|
2019-08-09 21:05:47 +03:00
|
|
|
routes = append(routes[:i+1], routes[i+2:]...)
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return routes
|
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
// consolidateAutomationPolicies combines automation policies that are the same,
|
|
|
|
// for a cleaner overall output.
|
|
|
|
func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.AutomationPolicy {
|
|
|
|
for i := 0; i < len(aps); i++ {
|
|
|
|
for j := 0; j < len(aps); j++ {
|
|
|
|
if j == i {
|
|
|
|
continue
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
if reflect.DeepEqual(aps[i].ManagementRaw, aps[j].ManagementRaw) {
|
|
|
|
aps[i].Hosts = append(aps[i].Hosts, aps[j].Hosts...)
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
aps = append(aps[:j], aps[j+1:]...)
|
|
|
|
i--
|
|
|
|
break
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
return aps
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
|
2019-08-21 19:46:35 +03:00
|
|
|
func matcherSetFromMatcherToken(
|
2019-08-09 21:05:47 +03:00
|
|
|
tkn caddyfile.Token,
|
|
|
|
matcherDefs map[string]map[string]json.RawMessage,
|
|
|
|
warnings *[]caddyconfig.Warning,
|
|
|
|
) (map[string]json.RawMessage, bool, error) {
|
|
|
|
// matcher tokens can be wildcards, simple path matchers,
|
|
|
|
// or refer to a pre-defined matcher by some name
|
|
|
|
if tkn.Text == "*" {
|
|
|
|
// match all requests == no matchers, so nothing to do
|
|
|
|
return nil, true, nil
|
|
|
|
} else if strings.HasPrefix(tkn.Text, "/") {
|
|
|
|
// convenient way to specify a single path match
|
|
|
|
return map[string]json.RawMessage{
|
|
|
|
"path": caddyconfig.JSON(caddyhttp.MatchPath{tkn.Text}, warnings),
|
|
|
|
}, true, nil
|
|
|
|
} else if strings.HasPrefix(tkn.Text, "match:") {
|
|
|
|
// pre-defined matcher
|
|
|
|
matcherName := strings.TrimPrefix(tkn.Text, "match:")
|
|
|
|
m, ok := matcherDefs[matcherName]
|
|
|
|
if !ok {
|
|
|
|
return nil, false, fmt.Errorf("unrecognized matcher name: %+v", matcherName)
|
|
|
|
}
|
|
|
|
return m, true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([]map[string]json.RawMessage, error) {
|
|
|
|
type hostPathPair struct {
|
|
|
|
hostm caddyhttp.MatchHost
|
|
|
|
pathm caddyhttp.MatchPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep routes with common host and path matchers together
|
|
|
|
var matcherPairs []*hostPathPair
|
|
|
|
|
|
|
|
for _, key := range sblock.Keys {
|
2019-08-21 19:46:35 +03:00
|
|
|
addr, err := ParseAddress(key)
|
2019-08-09 21:05:47 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
|
|
|
|
}
|
2019-08-21 19:46:35 +03:00
|
|
|
addr = addr.Normalize()
|
2019-08-09 21:05:47 +03:00
|
|
|
|
|
|
|
// choose a matcher pair that should be shared by this
|
|
|
|
// server block; if none exists yet, create one
|
|
|
|
var chosenMatcherPair *hostPathPair
|
|
|
|
for _, mp := range matcherPairs {
|
|
|
|
if (len(mp.pathm) == 0 && addr.Path == "") ||
|
|
|
|
(len(mp.pathm) == 1 && mp.pathm[0] == addr.Path) {
|
|
|
|
chosenMatcherPair = mp
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if chosenMatcherPair == nil {
|
|
|
|
chosenMatcherPair = new(hostPathPair)
|
|
|
|
if addr.Path != "" {
|
|
|
|
chosenMatcherPair.pathm = []string{addr.Path}
|
|
|
|
}
|
|
|
|
matcherPairs = append(matcherPairs, chosenMatcherPair)
|
|
|
|
}
|
|
|
|
|
|
|
|
// add this server block's keys to the matcher
|
|
|
|
// pair if it doesn't already exist
|
|
|
|
if addr.Host != "" {
|
|
|
|
var found bool
|
|
|
|
for _, h := range chosenMatcherPair.hostm {
|
|
|
|
if h == addr.Host {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate each pairing of host and path matchers and
|
|
|
|
// put them into a map for JSON encoding
|
|
|
|
var matcherSets []map[string]caddyhttp.RequestMatcher
|
|
|
|
for _, mp := range matcherPairs {
|
|
|
|
matcherSet := make(map[string]caddyhttp.RequestMatcher)
|
|
|
|
if len(mp.hostm) > 0 {
|
|
|
|
matcherSet["host"] = mp.hostm
|
|
|
|
}
|
|
|
|
if len(mp.pathm) > 0 {
|
|
|
|
matcherSet["path"] = mp.pathm
|
|
|
|
}
|
|
|
|
if len(matcherSet) > 0 {
|
|
|
|
matcherSets = append(matcherSets, matcherSet)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// finally, encode each of the matcher sets
|
|
|
|
var matcherSetsEnc []map[string]json.RawMessage
|
|
|
|
for _, ms := range matcherSets {
|
|
|
|
msEncoded, err := encodeMatcherSet(ms)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("server block %v: %v", sblock.Keys, err)
|
|
|
|
}
|
|
|
|
matcherSetsEnc = append(matcherSetsEnc, msEncoded)
|
|
|
|
}
|
|
|
|
|
|
|
|
return matcherSetsEnc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]json.RawMessage, error) {
|
|
|
|
msEncoded := make(map[string]json.RawMessage)
|
|
|
|
for matcherName, val := range matchers {
|
|
|
|
jsonBytes, err := json.Marshal(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("marshaling matcher set %#v: %v", matchers, err)
|
|
|
|
}
|
|
|
|
msEncoded[matcherName] = jsonBytes
|
|
|
|
}
|
|
|
|
return msEncoded, nil
|
|
|
|
}
|
|
|
|
|
2019-08-22 22:38:37 +03:00
|
|
|
// tryInt tries to convert val to an integer. If it fails,
|
|
|
|
// it downgrades the error to a warning and returns 0.
|
|
|
|
func tryInt(val interface{}, warnings *[]caddyconfig.Warning) int {
|
|
|
|
intVal, ok := val.(int)
|
|
|
|
if val != nil && !ok && warnings != nil {
|
|
|
|
*warnings = append(*warnings, caddyconfig.Warning{Message: "not an integer type"})
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
2019-08-22 22:38:37 +03:00
|
|
|
return intVal
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type matcherSetAndTokens struct {
|
|
|
|
matcherSet map[string]json.RawMessage
|
|
|
|
tokens []caddyfile.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
// sbAddrAssocation is a mapping from a list of
|
|
|
|
// addresses to a list of server blocks that are
|
|
|
|
// served on those addresses.
|
|
|
|
type sbAddrAssociation struct {
|
|
|
|
addresses []string
|
2019-08-21 19:46:35 +03:00
|
|
|
serverBlocks []serverBlock
|
2019-08-09 21:05:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Interface guard
|
|
|
|
var _ caddyfile.ServerType = (*ServerType)(nil)
|