summaryrefslogtreecommitdiff
path: root/caddyconfig/httpcaddyfile
diff options
context:
space:
mode:
Diffstat (limited to 'caddyconfig/httpcaddyfile')
-rw-r--r--caddyconfig/httpcaddyfile/addresses.go350
-rw-r--r--caddyconfig/httpcaddyfile/addresses_test.go129
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go257
-rw-r--r--caddyconfig/httpcaddyfile/handlers.go92
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go542
5 files changed, 1370 insertions, 0 deletions
diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go
new file mode 100644
index 0000000..6ecee26
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/addresses.go
@@ -0,0 +1,350 @@
+// 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 []caddyfile.ServerBlock) (map[string][]caddyfile.ServerBlock, error) {
+ sbmap := make(map[string][]caddyfile.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.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], caddyfile.ServerBlock{
+ Keys: keys,
+ Tokens: sblock.Tokens,
+ })
+ }
+ }
+
+ 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][]caddyfile.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 caddyfile.ServerBlock, key string) ([]string, error) {
+ addr, err := standardizeAddress(key)
+ if err != nil {
+ return nil, fmt.Errorf("parsing key: %v", err)
+ }
+
+ 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 i, token := range sblock.Tokens["bind"] {
+ if i == 0 {
+ continue
+ }
+ lnHosts = append(lnHosts, token.Text)
+ }
+ 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
+}
+
+// String returns a human-friendly print of the address.
+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
+}
+
+// VHost returns a sensible concatenation of Host:Port/Path from a.
+// It's basically the a.Original but without the scheme.
+func (a Address) VHost() string {
+ if idx := strings.Index(a.Original, "://"); idx > -1 {
+ return a.Original[idx+3:]
+ }
+ return a.Original
+}
+
+// Normalize normalizes URL: turn scheme and host names into lower case
+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 is similar to String, just replaces scheme and host values with modified values.
+// Unlike String it doesn't add anything default (scheme, port, etc)
+func (a Address) Key() string {
+ res := ""
+ if a.Scheme != "" {
+ res += a.Scheme + "://"
+ }
+ if a.Host != "" {
+ res += a.Host
+ }
+ if a.Port != "" {
+ if strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
+ // insert port only if the original has its own explicit port
+ res += ":" + a.Port
+ }
+ }
+ if a.Path != "" {
+ res += a.Path
+ }
+ return res
+}
+
+// standardizeAddress parses an address string into a structured format with separate
+// scheme, host, port, and path portions, as well as the original input string.
+func standardizeAddress(str string) (Address, error) {
+ httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
+
+ input := str
+
+ // Split input into components (prepend with // to assert host 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
+ }
+ }
+
+ // repeated or conflicting scheme is confusing, so error
+ if u.Scheme != "" && (port == "http" || port == "https") {
+ return Address{}, fmt.Errorf("[%s] scheme specified twice in address", input)
+ }
+
+ // 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)
+ }
+
+ // standardize http and https ports to their respective port numbers
+ if port == "http" {
+ u.Scheme = "http"
+ port = httpPort
+ } else if port == "https" {
+ u.Scheme = "https"
+ port = httpsPort
+ }
+
+ return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
+}
+
+const (
+ defaultPort = "2015"
+ caseSensitivePath = false
+)
diff --git a/caddyconfig/httpcaddyfile/addresses_test.go b/caddyconfig/httpcaddyfile/addresses_test.go
new file mode 100644
index 0000000..7e03d29
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/addresses_test.go
@@ -0,0 +1,129 @@
+// 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 "testing"
+
+func TestStandardizeAddress(t *testing.T) {
+ for i, test := range []struct {
+ input string
+ scheme, host, port, path string
+ shouldErr bool
+ }{
+ {`localhost`, "", "localhost", "", "", false},
+ {`localhost:1234`, "", "localhost", "1234", "", false},
+ {`localhost:`, "", "localhost", "", "", false},
+ {`0.0.0.0`, "", "0.0.0.0", "", "", false},
+ {`127.0.0.1:1234`, "", "127.0.0.1", "1234", "", false},
+ {`:1234`, "", "", "1234", "", false},
+ {`[::1]`, "", "::1", "", "", false},
+ {`[::1]:1234`, "", "::1", "1234", "", false},
+ {`:`, "", "", "", "", false},
+ {`localhost:http`, "http", "localhost", "80", "", false},
+ {`localhost:https`, "https", "localhost", "443", "", false},
+ {`:http`, "http", "", "80", "", false},
+ {`:https`, "https", "", "443", "", false},
+ {`http://localhost:https`, "", "", "", "", true}, // conflict
+ {`http://localhost:http`, "", "", "", "", true}, // repeated scheme
+ {`http://localhost:443`, "", "", "", "", true}, // not conventional
+ {`https://localhost:80`, "", "", "", "", true}, // not conventional
+ {`http://localhost`, "http", "localhost", "80", "", false},
+ {`https://localhost`, "https", "localhost", "443", "", false},
+ {`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
+ {`https://127.0.0.1`, "https", "127.0.0.1", "443", "", false},
+ {`http://[::1]`, "http", "::1", "80", "", false},
+ {`http://localhost:1234`, "http", "localhost", "1234", "", false},
+ {`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", "", false},
+ {`http://[::1]:1234`, "http", "::1", "1234", "", false},
+ {``, "", "", "", "", false},
+ {`::1`, "", "::1", "", "", true},
+ {`localhost::`, "", "localhost::", "", "", true},
+ {`#$%@`, "", "", "", "", true},
+ {`host/path`, "", "host", "", "/path", false},
+ {`http://host/`, "http", "host", "80", "/", false},
+ {`//asdf`, "", "asdf", "", "", false},
+ {`:1234/asdf`, "", "", "1234", "/asdf", false},
+ {`http://host/path`, "http", "host", "80", "/path", false},
+ {`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
+ {`host:80/path`, "", "host", "80", "/path", false},
+ {`host:https/path`, "https", "host", "443", "/path", false},
+ {`/path`, "", "", "", "/path", false},
+ } {
+ actual, err := standardizeAddress(test.input)
+
+ if err != nil && !test.shouldErr {
+ t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
+ }
+ if err == nil && test.shouldErr {
+ t.Errorf("Test %d (%s): Expected error, but had none", i, test.input)
+ }
+
+ if !test.shouldErr && actual.Original != test.input {
+ t.Errorf("Test %d (%s): Expected original '%s', got '%s'", i, test.input, test.input, actual.Original)
+ }
+ if actual.Scheme != test.scheme {
+ t.Errorf("Test %d (%s): Expected scheme '%s', got '%s'", i, test.input, test.scheme, actual.Scheme)
+ }
+ if actual.Host != test.host {
+ t.Errorf("Test %d (%s): Expected host '%s', got '%s'", i, test.input, test.host, actual.Host)
+ }
+ if actual.Port != test.port {
+ t.Errorf("Test %d (%s): Expected port '%s', got '%s'", i, test.input, test.port, actual.Port)
+ }
+ if actual.Path != test.path {
+ t.Errorf("Test %d (%s): Expected path '%s', got '%s'", i, test.input, test.path, actual.Path)
+ }
+ }
+}
+
+func TestAddressVHost(t *testing.T) {
+ for i, test := range []struct {
+ addr Address
+ expected string
+ }{
+ {Address{Original: "host:1234"}, "host:1234"},
+ {Address{Original: "host:1234/foo"}, "host:1234/foo"},
+ {Address{Original: "host/foo"}, "host/foo"},
+ {Address{Original: "http://host/foo"}, "host/foo"},
+ {Address{Original: "https://host/foo"}, "host/foo"},
+ } {
+ actual := test.addr.VHost()
+ if actual != test.expected {
+ t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expected, actual)
+ }
+ }
+}
+
+func TestAddressString(t *testing.T) {
+ for i, test := range []struct {
+ addr Address
+ expected string
+ }{
+ {Address{Scheme: "http", Host: "host", Port: "1234", Path: "/path"}, "http://host:1234/path"},
+ {Address{Scheme: "", Host: "host", Port: "", Path: ""}, "http://host"},
+ {Address{Scheme: "", Host: "host", Port: "80", Path: ""}, "http://host"},
+ {Address{Scheme: "", Host: "host", Port: "443", Path: ""}, "https://host"},
+ {Address{Scheme: "https", Host: "host", Port: "443", Path: ""}, "https://host"},
+ {Address{Scheme: "https", Host: "host", Port: "", Path: ""}, "https://host"},
+ {Address{Scheme: "", Host: "host", Port: "80", Path: "/path"}, "http://host/path"},
+ {Address{Scheme: "http", Host: "", Port: "1234", Path: ""}, "http://:1234"},
+ {Address{Scheme: "", Host: "", Port: "", Path: ""}, ""},
+ } {
+ actual := test.addr.String()
+ if actual != test.expected {
+ t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expected, actual)
+ }
+ }
+}
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
new file mode 100644
index 0000000..7e51e46
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -0,0 +1,257 @@
+// 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"
+ "html"
+ "net/http"
+
+ "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"
+)
+
+func (st *ServerType) parseRoot(
+ tkns []caddyfile.Token,
+ matcherDefs map[string]map[string]json.RawMessage,
+ warnings *[]caddyconfig.Warning,
+) ([]caddyhttp.Route, error) {
+ var routes []caddyhttp.Route
+
+ matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, mst := range matchersAndTokens {
+ d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
+
+ var root string
+ for d.Next() {
+ if !d.NextArg() {
+ return nil, d.ArgErr()
+ }
+ root = d.Val()
+ if d.NextArg() {
+ return nil, d.ArgErr()
+ }
+ }
+
+ varsHandler := caddyhttp.VarsMiddleware{"root": root}
+ route := caddyhttp.Route{
+ Handle: []json.RawMessage{
+ caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", warnings),
+ },
+ }
+ if mst.matcherSet != nil {
+ route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
+ }
+
+ routes = append(routes, route)
+ }
+
+ return routes, nil
+}
+
+func (st *ServerType) parseRedir(
+ tkns []caddyfile.Token,
+ matcherDefs map[string]map[string]json.RawMessage,
+ warnings *[]caddyconfig.Warning,
+) ([]caddyhttp.Route, error) {
+ var routes []caddyhttp.Route
+
+ matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, mst := range matchersAndTokens {
+ var route caddyhttp.Route
+
+ d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
+
+ for d.Next() {
+ if !d.NextArg() {
+ return nil, d.ArgErr()
+ }
+ to := d.Val()
+
+ var code string
+ if d.NextArg() {
+ code = d.Val()
+ }
+ if code == "permanent" {
+ code = "301"
+ }
+ if code == "temporary" || code == "" {
+ code = "307"
+ }
+ var body string
+ if code == "meta" {
+ // Script tag comes first since that will better imitate a redirect in the browser's
+ // history, but the meta tag is a fallback for most non-JS clients.
+ const metaRedir = `<!DOCTYPE html>
+<html>
+ <head>
+ <title>Redirecting...</title>
+ <script>window.location.replace("%s");</script>
+ <meta http-equiv="refresh" content="0; URL='%s'">
+ </head>
+ <body>Redirecting to <a href="%s">%s</a>...</body>
+</html>
+`
+ safeTo := html.EscapeString(to)
+ body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
+ }
+
+ handler := caddyhttp.StaticResponse{
+ StatusCode: caddyhttp.WeakString(code),
+ Headers: http.Header{"Location": []string{to}},
+ Body: body,
+ }
+
+ route.Handle = append(route.Handle,
+ caddyconfig.JSONModuleObject(handler, "handler", "static_response", warnings))
+ }
+
+ if mst.matcherSet != nil {
+ route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
+ }
+
+ routes = append(routes, route)
+ }
+
+ return routes, nil
+}
+
+func (st *ServerType) parseTLSAutomationManager(d *caddyfile.Dispenser) (caddytls.ACMEManagerMaker, error) {
+ var m caddytls.ACMEManagerMaker
+
+ for d.Next() {
+ firstLine := d.RemainingArgs()
+ if len(firstLine) == 1 && firstLine[0] != "off" {
+ m.Email = firstLine[0]
+ }
+
+ var hasBlock bool
+ for d.NextBlock() {
+ hasBlock = true
+ switch d.Val() {
+ case "ca":
+ arg := d.RemainingArgs()
+ if len(arg) != 1 {
+ return m, d.ArgErr()
+ }
+ m.CA = arg[0]
+ // TODO: other properties
+ }
+ }
+
+ // a naked tls directive is not allowed
+ if len(firstLine) == 0 && !hasBlock {
+ return m, d.ArgErr()
+ }
+ }
+
+ return m, nil
+}
+
+func (st *ServerType) parseTLSCerts(d *caddyfile.Dispenser) (map[string]caddytls.CertificateLoader, error) {
+ var fileLoader caddytls.FileLoader
+ var folderLoader caddytls.FolderLoader
+
+ for d.Next() {
+ // file loader
+ firstLine := d.RemainingArgs()
+ if len(firstLine) == 2 {
+ fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
+ Certificate: firstLine[0],
+ Key: firstLine[1],
+ // TODO: tags, for enterprise module's certificate selection
+ })
+ }
+
+ // folder loader
+ for d.NextBlock() {
+ if d.Val() == "load" {
+ folderLoader = append(folderLoader, d.RemainingArgs()...)
+ }
+ }
+ }
+
+ // put configured loaders into the map
+ loaders := make(map[string]caddytls.CertificateLoader)
+ if len(fileLoader) > 0 {
+ loaders["load_files"] = fileLoader
+ }
+ if len(folderLoader) > 0 {
+ loaders["load_folders"] = folderLoader
+ }
+
+ return loaders, nil
+}
+
+func (st *ServerType) parseTLSConnPolicy(d *caddyfile.Dispenser) (*caddytls.ConnectionPolicy, error) {
+ cp := new(caddytls.ConnectionPolicy)
+
+ for d.Next() {
+ for d.NextBlock() {
+ switch d.Val() {
+ case "protocols":
+ args := d.RemainingArgs()
+ if len(args) == 0 {
+ return nil, d.SyntaxErr("one or two protocols")
+ }
+ if len(args) > 0 {
+ if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
+ return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
+ }
+ cp.ProtocolMin = args[0]
+ }
+ if len(args) > 1 {
+ if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
+ return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
+ }
+ cp.ProtocolMax = args[1]
+ }
+ case "ciphers":
+ for d.NextArg() {
+ if _, ok := caddytls.SupportedCipherSuites[d.Val()]; !ok {
+ return nil, d.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", d.Val())
+ }
+ cp.CipherSuites = append(cp.CipherSuites, d.Val())
+ }
+ case "curves":
+ for d.NextArg() {
+ if _, ok := caddytls.SupportedCurves[d.Val()]; !ok {
+ return nil, d.Errf("Wrong curve name or curve not supported: '%s'", d.Val())
+ }
+ cp.Curves = append(cp.Curves, d.Val())
+ }
+ case "alpn":
+ args := d.RemainingArgs()
+ if len(args) == 0 {
+ return nil, d.ArgErr()
+ }
+ cp.ALPN = args
+ }
+ }
+ }
+
+ return cp, nil
+}
diff --git a/caddyconfig/httpcaddyfile/handlers.go b/caddyconfig/httpcaddyfile/handlers.go
new file mode 100644
index 0000000..a90aa4a
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/handlers.go
@@ -0,0 +1,92 @@
+// 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"
+ "log"
+
+ "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"
+)
+
+func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json.RawMessage, error) {
+ matchers := make(map[string]map[string]json.RawMessage)
+ for d.Next() {
+ definitionName := d.Val()
+ for d.NextBlock() {
+ matcherName := d.Val()
+ mod, err := caddy.GetModule("http.matchers." + matcherName)
+ if err != nil {
+ return nil, fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
+ }
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
+ if !ok {
+ return nil, fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
+ }
+ err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
+ if err != nil {
+ return nil, err
+ }
+ rm, ok := unm.(caddyhttp.RequestMatcher)
+ if !ok {
+ return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
+ }
+ if _, ok := matchers[definitionName]; !ok {
+ matchers[definitionName] = make(map[string]json.RawMessage)
+ }
+ matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
+ }
+ }
+ return matchers, nil
+}
+
+// directiveBuckets returns a list of middleware/handler directives.
+// Buckets are ordered, and directives should be evaluated in their
+// bucket order. Within a bucket, directives are not ordered. Hence,
+// the return value has a slice of buckets, where each bucket is a
+// map, which is a strongly-typed reminder that directives within a
+// bucket are not ordered.
+func directiveBuckets() []map[string]struct{} {
+ directiveBuckets := []map[string]struct{}{
+ // prefer odd-numbered buckets; evens are there for contingencies
+ {}, // 0
+ {}, // 1 - keep empty unless necessary
+ {}, // 2
+ {}, // 3 - first handlers, last responders
+ {}, // 4
+ {}, // 5 - middle of chain
+ {}, // 6
+ {}, // 7 - last handlers, first responders
+ {}, // 8
+ {}, // 9 - keep empty unless necessary
+ {}, // 10
+ }
+ for _, mod := range caddy.GetModules("http.handlers") {
+ if hd, ok := mod.New().(HandlerDirective); ok {
+ bucket := hd.Bucket()
+ if bucket < 0 || bucket >= len(directiveBuckets) {
+ log.Printf("[ERROR] directive %s: bucket out of range [0-%d): %d; skipping",
+ mod.Name, len(directiveBuckets), bucket)
+ continue
+ }
+ directiveBuckets[bucket][mod.ID()] = struct{}{}
+ }
+ }
+ return directiveBuckets
+}
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
new file mode 100644
index 0000000..e5bf048
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -0,0 +1,542 @@
+// 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"
+ "strconv"
+ "strings"
+
+ "github.com/mholt/certmagic"
+
+ "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"
+)
+
+func init() {
+ caddyconfig.RegisterAdapter("caddyfile", caddyfile.Adapter{ServerType: ServerType{}})
+}
+
+// ServerType can set up a config from an HTTP Caddyfile.
+type ServerType struct {
+}
+
+// ValidDirectives returns the list of known directives.
+func (ServerType) ValidDirectives() []string {
+ dirs := []string{"matcher", "root", "tls", "redir"} // TODO: put special-case (hard-coded, or non-handler) directives here
+ for _, mod := range caddy.GetModules("http.handlers") {
+ if _, ok := mod.New().(HandlerDirective); ok {
+ dirs = append(dirs, mod.ID())
+ }
+ }
+ return dirs
+}
+
+// Setup makes a config from the tokens.
+func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
+ options map[string]string) (*caddy.Config, []caddyconfig.Warning, error) {
+ var warnings []caddyconfig.Warning
+
+ // map
+ sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks)
+ 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
+ servers, err := st.serversFromPairings(pairings, &warnings)
+ if err != nil {
+ return nil, warnings, err
+ }
+
+ // now that each server is configured, make the HTTP app
+ httpApp := caddyhttp.App{
+ HTTPPort: tryInt(options["http-port"], &warnings),
+ HTTPSPort: tryInt(options["https-port"], &warnings),
+ 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 {
+ if tkns, ok := sblock.Tokens["tls"]; ok {
+ // extract all unique hostnames from the server block
+ // keys, then convert to a slice for use in the TLS app
+ hostMap := make(map[string]struct{})
+ for _, sblockKey := range sblock.Keys {
+ addr, err := standardizeAddress(sblockKey)
+ if err != nil {
+ return nil, warnings, fmt.Errorf("parsing server block key: %v", err)
+ }
+ hostMap[addr.Host] = struct{}{}
+ }
+ sblockHosts := make([]string, 0, len(hostMap))
+ for host := range hostMap {
+ sblockHosts = append(sblockHosts, host)
+ }
+
+ // parse tokens to get ACME manager config
+ acmeMgr, err := st.parseTLSAutomationManager(caddyfile.NewDispenser("Caddyfile", tkns))
+ if err != nil {
+ return nil, warnings, err
+ }
+
+ tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
+ Hosts: sblockHosts,
+ ManagementRaw: caddyconfig.JSONModuleObject(acmeMgr, "module", "acme", &warnings),
+ })
+
+ // parse tokens to get certificates to be loaded manually
+ certLoaders, err := st.parseTLSCerts(caddyfile.NewDispenser("Caddyfile", tkns))
+ if err != nil {
+ return nil, nil, err
+ }
+ for loaderName, loader := range certLoaders {
+ tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings)
+ }
+
+ }
+ }
+ }
+
+ // 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 {
+ addr, err := standardizeAddress(sblockKey)
+ if err != nil {
+ return nil, fmt.Errorf("parsing server block key: %v", err)
+ }
+ 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.
+func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings *[]caddyconfig.Warning) (map[string]*caddyhttp.Server, error) {
+ servers := make(map[string]*caddyhttp.Server)
+
+ for i, p := range pairings {
+ srv := &caddyhttp.Server{
+ Listen: p.addresses,
+ }
+
+ for _, sblock := range p.serverBlocks {
+ matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
+ if err != nil {
+ return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.Keys, err)
+ }
+
+ // extract matcher definitions
+ d := caddyfile.NewDispenser("Caddyfile", sblock.Tokens["matcher"])
+ matcherDefs, err := st.parseMatcherDefinitions(d)
+ if err != nil {
+ return nil, err
+ }
+
+ siteVarSubroute, handlerSubroute := new(caddyhttp.Subroute), new(caddyhttp.Subroute)
+
+ // built-in directives
+
+ // root: path to root of site
+ if tkns, ok := sblock.Tokens["root"]; ok {
+ routes, err := st.parseRoot(tkns, matcherDefs, warnings)
+ if err != nil {
+ return nil, err
+ }
+ siteVarSubroute.Routes = append(siteVarSubroute.Routes, routes...)
+ }
+
+ // tls: off and conn policies
+ if tkns, ok := sblock.Tokens["tls"]; ok {
+ // get the hosts for this server block...
+ hosts, err := st.hostsFromServerBlockKeys(sblock)
+ 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)
+ }
+ }
+
+ if len(tkns) == 2 && tkns[1].Text == "off" {
+ // tls off: disable TLS (and automatic HTTPS) for server block's names
+ if srv.AutoHTTPS == nil {
+ srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
+ }
+ srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
+ } else {
+ // tls connection policies
+ cp, err := st.parseTLSConnPolicy(caddyfile.NewDispenser("Caddyfile", tkns))
+ if err != nil {
+ return nil, err
+ }
+ // 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
+ }
+ srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
+ }
+ }
+
+ // set up each handler directive
+ for _, dirBucket := range directiveBuckets() {
+ for dir := range dirBucket {
+ // keep in mind that multiple occurrences of the directive may appear here
+ tkns, ok := sblock.Tokens[dir]
+ if !ok {
+ continue
+ }
+
+ // extract matcher sets from matcher tokens, if any
+ matcherSetsMap, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
+
+ mod, err := caddy.GetModule("http.handlers." + dir)
+ if err != nil {
+ return nil, fmt.Errorf("getting handler module '%s': %v", mod.Name, err)
+ }
+
+ // the tokens have been divided by matcher set for us,
+ // so iterate each one and set them up
+ for _, mst := range matcherSetsMap {
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
+ if !ok {
+ return nil, fmt.Errorf("handler module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ }
+ err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(d.File(), mst.tokens))
+ if err != nil {
+ return nil, err
+ }
+ handler, ok := unm.(caddyhttp.MiddlewareHandler)
+ if !ok {
+ return nil, fmt.Errorf("handler module '%s' does not implement caddyhttp.MiddlewareHandler interface", mod.Name)
+ }
+
+ route := caddyhttp.Route{
+ Handle: []json.RawMessage{
+ caddyconfig.JSONModuleObject(handler, "handler", dir, warnings),
+ },
+ }
+ if mst.matcherSet != nil {
+ route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
+ }
+ handlerSubroute.Routes = append(handlerSubroute.Routes, route)
+ }
+
+ }
+ }
+
+ // redir: static responses that redirect
+ if tkns, ok := sblock.Tokens["redir"]; ok {
+ routes, err := st.parseRedir(tkns, matcherDefs, warnings)
+ if err != nil {
+ return nil, err
+ }
+ handlerSubroute.Routes = append(handlerSubroute.Routes, routes...)
+ }
+
+ // 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{
+ Handle: []json.RawMessage{
+ caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings),
+ },
+ },
+ )
+ }
+
+ siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
+
+ srv.Routes = append(srv.Routes, caddyhttp.Route{
+ MatcherSets: matcherSetsEnc,
+ Handle: []json.RawMessage{
+ caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings),
+ },
+ })
+ }
+
+ srv.Routes = consolidateRoutes(srv.Routes)
+
+ servers[fmt.Sprintf("srv%d", i)] = srv
+ }
+
+ return servers, nil
+}
+
+// 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++ {
+ if reflect.DeepEqual(routes[i].MatcherSets, routes[i+1].MatcherSets) &&
+ 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
+ routes[i].Handle = append(routes[i].Handle, routes[i+1].Handle...)
+ routes = append(routes[:i+1], routes[i+2:]...)
+ i--
+ }
+ }
+ return routes
+}
+
+func (st *ServerType) tokensToMatcherSets(
+ tkns []caddyfile.Token,
+ matcherDefs map[string]map[string]json.RawMessage,
+ warnings *[]caddyconfig.Warning,
+) (map[string]matcherSetAndTokens, error) {
+ m := make(map[string]matcherSetAndTokens)
+
+ for len(tkns) > 0 {
+ d := caddyfile.NewDispenser("Caddyfile", tkns)
+ d.Next() // consume directive token
+
+ // look for matcher; it should be the next argument
+ var matcherToken caddyfile.Token
+ var matcherSet map[string]json.RawMessage
+ if d.NextArg() {
+ var ok bool
+ var err error
+ matcherSet, ok, err = st.matcherSetFromMatcherToken(d.Token(), matcherDefs, warnings)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ // found a matcher; save it, then splice it out
+ // since we don't want to parse it again
+ matcherToken = d.Token()
+ tkns = d.Delete()
+ }
+ d.RemainingArgs() // advance to end of line
+ }
+ for d.NextBlock() {
+ // skip entire block including any nested blocks; all
+ // we care about is accessing next directive occurrence
+ for d.Nested() {
+ d.NextBlock()
+ }
+ }
+ end := d.Cursor() + 1
+ m[matcherToken.Text] = matcherSetAndTokens{
+ matcherSet: matcherSet,
+ tokens: append(m[matcherToken.Text].tokens, tkns[:end]...),
+ }
+ tkns = tkns[end:]
+ }
+ return m, nil
+}
+
+func (st *ServerType) matcherSetFromMatcherToken(
+ 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 {
+ addr, err := standardizeAddress(key)
+ if err != nil {
+ return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
+ }
+
+ // 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
+}
+
+// HandlerDirective implements a directive for an HTTP handler,
+// in that it can unmarshal its own configuration from Caddyfile
+// tokens and also specify which directive bucket it belongs in.
+type HandlerDirective interface {
+ caddyfile.Unmarshaler
+ Bucket() int
+}
+
+// tryInt tries to convert str to an integer. If it fails, it downgrades
+// the error to a warning and returns 0.
+func tryInt(str string, warnings *[]caddyconfig.Warning) int {
+ if str == "" {
+ return 0
+ }
+ val, err := strconv.Atoi(str)
+ if err != nil && warnings != nil {
+ *warnings = append(*warnings, caddyconfig.Warning{Message: err.Error()})
+ }
+ return val
+}
+
+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
+ serverBlocks []caddyfile.ServerBlock
+}
+
+// Interface guard
+var _ caddyfile.ServerType = (*ServerType)(nil)