diff options
author | Francis Lavoie <[email protected]> | 2024-01-23 19:36:59 -0500 |
---|---|---|
committer | GitHub <[email protected]> | 2024-01-23 19:36:59 -0500 |
commit | 750d0b83319ac0ea6b7f057b8270c19404c3d6fa (patch) | |
tree | d0c3fb610cde8ae9d73a0a2caba94542af099770 /caddyconfig | |
parent | 54823f52bc9aed66a1a37f820daf6e494181211a (diff) | |
download | caddy-750d0b83319ac0ea6b7f057b8270c19404c3d6fa.tar.gz caddy-750d0b83319ac0ea6b7f057b8270c19404c3d6fa.zip |
caddyfile: Normalize & flatten all unmarshalers (#6037)
Diffstat (limited to 'caddyconfig')
-rw-r--r-- | caddyconfig/caddyfile/adapter.go | 32 | ||||
-rw-r--r-- | caddyconfig/httpcaddyfile/builtins.go | 1086 | ||||
-rw-r--r-- | caddyconfig/httpcaddyfile/directives.go | 6 | ||||
-rw-r--r-- | caddyconfig/httpcaddyfile/httptype.go | 109 | ||||
-rw-r--r-- | caddyconfig/httpcaddyfile/options.go | 365 | ||||
-rw-r--r-- | caddyconfig/httpcaddyfile/pkiapp.go | 196 | ||||
-rw-r--r-- | caddyconfig/httpcaddyfile/serveroptions.go | 400 |
7 files changed, 1085 insertions, 1109 deletions
diff --git a/caddyconfig/caddyfile/adapter.go b/caddyconfig/caddyfile/adapter.go index d6ef602dc..edd825b88 100644 --- a/caddyconfig/caddyfile/adapter.go +++ b/caddyconfig/caddyfile/adapter.go @@ -92,30 +92,26 @@ func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bo }, true } -// Unmarshaler is a type that can unmarshal -// Caddyfile tokens to set itself up for a -// JSON encoding. The goal of an unmarshaler -// is not to set itself up for actual use, -// but to set itself up for being marshaled -// into JSON. Caddyfile-unmarshaled values -// will not be used directly; they will be -// encoded as JSON and then used from that. -// Implementations must be able to support -// multiple segments (instances of their -// directive or batch of tokens); typically -// this means wrapping all token logic in -// a loop: `for d.Next() { ... }`. +// Unmarshaler is a type that can unmarshal Caddyfile tokens to +// set itself up for a JSON encoding. The goal of an unmarshaler +// is not to set itself up for actual use, but to set itself up for +// being marshaled into JSON. Caddyfile-unmarshaled values will not +// be used directly; they will be encoded as JSON and then used from +// that. Implementations _may_ be able to support multiple segments +// (instances of their directive or batch of tokens); typically this +// means wrapping parsing logic in a loop: `for d.Next() { ... }`. +// More commonly, only a single segment is supported, so a simple +// `d.Next()` at the start should be used to consume the module +// identifier token (directive name, etc). type Unmarshaler interface { UnmarshalCaddyfile(d *Dispenser) error } // ServerType is a type that can evaluate a Caddyfile and set up a caddy config. type ServerType interface { - // Setup takes the server blocks which - // contain tokens, as well as options - // (e.g. CLI flags) and creates a Caddy - // config, along with any warnings or - // an error. + // Setup takes the server blocks which contain tokens, + // as well as options (e.g. CLI flags) and creates a + // Caddy config, along with any warnings or an error. Setup([]ServerBlock, map[string]any) (*caddy.Config, []caddyconfig.Warning, error) } diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index bf95a3616..11d18caea 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -59,11 +59,8 @@ func init() { // // bind <addresses...> func parseBind(h Helper) ([]ConfigValue, error) { - var lnHosts []string - for h.Next() { - lnHosts = append(lnHosts, h.RemainingArgs()...) - } - return h.NewBindAddresses(lnHosts), nil + h.Next() // consume directive name + return []ConfigValue{{Class: "bind", Value: h.RemainingArgs()}}, nil } // parseTLS parses the tls directive. Syntax: @@ -98,6 +95,8 @@ func parseBind(h Helper) ([]ConfigValue, error) { // insecure_secrets_log <log_file> // } func parseTLS(h Helper) ([]ConfigValue, error) { + h.Next() // consume directive name + cp := new(caddytls.ConnectionPolicy) var fileLoader caddytls.FileLoader var folderLoader caddytls.FolderLoader @@ -110,423 +109,421 @@ func parseTLS(h Helper) ([]ConfigValue, error) { var onDemand bool var reusePrivateKeys bool - for h.Next() { - // file certificate loader - firstLine := h.RemainingArgs() - switch len(firstLine) { - case 0: - case 1: - if firstLine[0] == "internal" { - internalIssuer = new(caddytls.InternalIssuer) - } else if !strings.Contains(firstLine[0], "@") { - return nil, h.Err("single argument must either be 'internal' or an email address") - } else { - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - acmeIssuer.Email = firstLine[0] - } - - case 2: - certFilename := firstLine[0] - keyFilename := firstLine[1] - - // tag this certificate so if multiple certs match, specifically - // this one that the user has provided will be used, see #2588: - // https://github.com/caddyserver/caddy/issues/2588 ... but we - // must be careful about how we do this; being careless will - // lead to failed handshakes - // - // we need to remember which cert files we've seen, since we - // must load each cert only once; otherwise, they each get a - // different tag... since a cert loaded twice has the same - // bytes, it will overwrite the first one in the cache, and - // only the last cert (and its tag) will survive, so any conn - // policy that is looking for any tag other than the last one - // to be loaded won't find it, and TLS handshakes will fail - // (see end of issue #3004) - // - // tlsCertTags maps certificate filenames to their tag. - // This is used to remember which tag is used for each - // certificate files, since we need to avoid loading - // the same certificate files more than once, overwriting - // previous tags - tlsCertTags, ok := h.State["tlsCertTags"].(map[string]string) - if !ok { - tlsCertTags = make(map[string]string) - h.State["tlsCertTags"] = tlsCertTags + // file certificate loader + firstLine := h.RemainingArgs() + switch len(firstLine) { + case 0: + case 1: + if firstLine[0] == "internal" { + internalIssuer = new(caddytls.InternalIssuer) + } else if !strings.Contains(firstLine[0], "@") { + return nil, h.Err("single argument must either be 'internal' or an email address") + } else { + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) } + acmeIssuer.Email = firstLine[0] + } - tag, ok := tlsCertTags[certFilename] - if !ok { - // haven't seen this cert file yet, let's give it a tag - // and add a loader for it - tag = fmt.Sprintf("cert%d", len(tlsCertTags)) - fileLoader = append(fileLoader, caddytls.CertKeyFilePair{ - Certificate: certFilename, - Key: keyFilename, - Tags: []string{tag}, - }) - // remember this for next time we see this cert file - tlsCertTags[certFilename] = tag - } - certSelector.AnyTag = append(certSelector.AnyTag, tag) + case 2: + certFilename := firstLine[0] + keyFilename := firstLine[1] + + // tag this certificate so if multiple certs match, specifically + // this one that the user has provided will be used, see #2588: + // https://github.com/caddyserver/caddy/issues/2588 ... but we + // must be careful about how we do this; being careless will + // lead to failed handshakes + // + // we need to remember which cert files we've seen, since we + // must load each cert only once; otherwise, they each get a + // different tag... since a cert loaded twice has the same + // bytes, it will overwrite the first one in the cache, and + // only the last cert (and its tag) will survive, so any conn + // policy that is looking for any tag other than the last one + // to be loaded won't find it, and TLS handshakes will fail + // (see end of issue #3004) + // + // tlsCertTags maps certificate filenames to their tag. + // This is used to remember which tag is used for each + // certificate files, since we need to avoid loading + // the same certificate files more than once, overwriting + // previous tags + tlsCertTags, ok := h.State["tlsCertTags"].(map[string]string) + if !ok { + tlsCertTags = make(map[string]string) + h.State["tlsCertTags"] = tlsCertTags + } - default: - return nil, h.ArgErr() + tag, ok := tlsCertTags[certFilename] + if !ok { + // haven't seen this cert file yet, let's give it a tag + // and add a loader for it + tag = fmt.Sprintf("cert%d", len(tlsCertTags)) + fileLoader = append(fileLoader, caddytls.CertKeyFilePair{ + Certificate: certFilename, + Key: keyFilename, + Tags: []string{tag}, + }) + // remember this for next time we see this cert file + tlsCertTags[certFilename] = tag } + certSelector.AnyTag = append(certSelector.AnyTag, tag) - var hasBlock bool - for nesting := h.Nesting(); h.NextBlock(nesting); { - hasBlock = true + default: + return nil, h.ArgErr() + } + + var hasBlock bool + for h.NextBlock(0) { + hasBlock = true - switch h.Val() { - case "protocols": - args := h.RemainingArgs() - if len(args) == 0 { - return nil, h.Errf("protocols requires one or two arguments") + switch h.Val() { + case "protocols": + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.Errf("protocols requires one or two arguments") + } + if len(args) > 0 { + if _, ok := caddytls.SupportedProtocols[args[0]]; !ok { + return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[0]) } - if len(args) > 0 { - if _, ok := caddytls.SupportedProtocols[args[0]]; !ok { - return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[0]) - } - cp.ProtocolMin = args[0] + cp.ProtocolMin = args[0] + } + if len(args) > 1 { + if _, ok := caddytls.SupportedProtocols[args[1]]; !ok { + return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[1]) } - if len(args) > 1 { - if _, ok := caddytls.SupportedProtocols[args[1]]; !ok { - return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[1]) - } - cp.ProtocolMax = args[1] + cp.ProtocolMax = args[1] + } + + case "ciphers": + for h.NextArg() { + if !caddytls.CipherSuiteNameSupported(h.Val()) { + return nil, h.Errf("wrong cipher suite name or cipher suite not supported: '%s'", h.Val()) } + cp.CipherSuites = append(cp.CipherSuites, h.Val()) + } - case "ciphers": - for h.NextArg() { - if !caddytls.CipherSuiteNameSupported(h.Val()) { - return nil, h.Errf("wrong cipher suite name or cipher suite not supported: '%s'", h.Val()) - } - cp.CipherSuites = append(cp.CipherSuites, h.Val()) + case "curves": + for h.NextArg() { + if _, ok := caddytls.SupportedCurves[h.Val()]; !ok { + return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val()) } + cp.Curves = append(cp.Curves, h.Val()) + } - case "curves": - for h.NextArg() { - if _, ok := caddytls.SupportedCurves[h.Val()]; !ok { - return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val()) + case "client_auth": + cp.ClientAuthentication = &caddytls.ClientAuthentication{} + for nesting := h.Nesting(); h.NextBlock(nesting); { + subdir := h.Val() + switch subdir { + case "verifier": + if !h.NextArg() { + return nil, h.ArgErr() } - cp.Curves = append(cp.Curves, h.Val()) - } - case "client_auth": - cp.ClientAuthentication = &caddytls.ClientAuthentication{} - for nesting := h.Nesting(); h.NextBlock(nesting); { - subdir := h.Val() - switch subdir { - case "verifier": - if !h.NextArg() { - return nil, h.ArgErr() - } + vType := h.Val() + modID := "tls.client_auth." + vType + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } - vType := h.Val() - modID := "tls.client_auth." + vType - unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) - if err != nil { - return nil, err - } + _, ok := unm.(caddytls.ClientCertificateVerifier) + if !ok { + return nil, h.Dispenser.Errf("module %s is not a caddytls.ClientCertificatVerifier", modID) + } - _, ok := unm.(caddytls.ClientCertificateVerifier) - if !ok { - return nil, h.Dispenser.Errf("module %s is not a caddytls.ClientCertificatVerifier", modID) - } + cp.ClientAuthentication.VerifiersRaw = append(cp.ClientAuthentication.VerifiersRaw, caddyconfig.JSONModuleObject(unm, "verifier", vType, h.warnings)) + case "mode": + if !h.Args(&cp.ClientAuthentication.Mode) { + return nil, h.ArgErr() + } + if h.NextArg() { + return nil, h.ArgErr() + } - cp.ClientAuthentication.VerifiersRaw = append(cp.ClientAuthentication.VerifiersRaw, caddyconfig.JSONModuleObject(unm, "verifier", vType, h.warnings)) - case "mode": - if !h.Args(&cp.ClientAuthentication.Mode) { - return nil, h.ArgErr() - } - if h.NextArg() { - return nil, h.ArgErr() - } + case "trusted_ca_cert", + "trusted_leaf_cert": + if !h.NextArg() { + return nil, h.ArgErr() + } + if subdir == "trusted_ca_cert" { + cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts, h.Val()) + } else { + cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts, h.Val()) + } - case "trusted_ca_cert", - "trusted_leaf_cert": - if !h.NextArg() { - return nil, h.ArgErr() + case "trusted_ca_cert_file", + "trusted_leaf_cert_file": + if !h.NextArg() { + return nil, h.ArgErr() + } + filename := h.Val() + certDataPEM, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + // while block is not nil, we have more certificates in the file + for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) { + if block.Type != "CERTIFICATE" { + return nil, h.Errf("no CERTIFICATE pem block found in %s", filename) } - if subdir == "trusted_ca_cert" { - cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts, h.Val()) + if subdir == "trusted_ca_cert_file" { + cp.ClientAuthentication.TrustedCACerts = append( + cp.ClientAuthentication.TrustedCACerts, + base64.StdEncoding.EncodeToString(block.Bytes), + ) } else { - cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts, h.Val()) - } - - case "trusted_ca_cert_file", - "trusted_leaf_cert_file": - if !h.NextArg() { - return nil, h.ArgErr() - } - filename := h.Val() - certDataPEM, err := os.ReadFile(filename) - if err != nil { - return nil, err + cp.ClientAuthentication.TrustedLeafCerts = append( + cp.ClientAuthentication.TrustedLeafCerts, + base64.StdEncoding.EncodeToString(block.Bytes), + ) } - // while block is not nil, we have more certificates in the file - for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) { - if block.Type != "CERTIFICATE" { - return nil, h.Errf("no CERTIFICATE pem block found in %s", filename) - } - if subdir == "trusted_ca_cert_file" { - cp.ClientAuthentication.TrustedCACerts = append( - cp.ClientAuthentication.TrustedCACerts, - base64.StdEncoding.EncodeToString(block.Bytes), - ) - } else { - cp.ClientAuthentication.TrustedLeafCerts = append( - cp.ClientAuthentication.TrustedLeafCerts, - base64.StdEncoding.EncodeToString(block.Bytes), - ) - } - } - // if we decoded nothing, return an error - if len(cp.ClientAuthentication.TrustedCACerts) == 0 && len(cp.ClientAuthentication.TrustedLeafCerts) == 0 { - return nil, h.Errf("no CERTIFICATE pem block found in %s", filename) - } - - default: - return nil, h.Errf("unknown subdirective for client_auth: %s", subdir) } - } + // if we decoded nothing, return an error + if len(cp.ClientAuthentication.TrustedCACerts) == 0 && len(cp.ClientAuthentication.TrustedLeafCerts) == 0 { + return nil, h.Errf("no CERTIFICATE pem block found in %s", filename) + } - case "alpn": - args := h.RemainingArgs() - if len(args) == 0 { - return nil, h.ArgErr() + default: + return nil, h.Errf("unknown subdirective for client_auth: %s", subdir) } - cp.ALPN = args + } - case "load": - folderLoader = append(folderLoader, h.RemainingArgs()...) + case "alpn": + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.ArgErr() + } + cp.ALPN = args - case "ca": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - acmeIssuer.CA = arg[0] + case "load": + folderLoader = append(folderLoader, h.RemainingArgs()...) - case "key_type": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - keyType = arg[0] + case "ca": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + acmeIssuer.CA = arg[0] - case "eab": - arg := h.RemainingArgs() - if len(arg) != 2 { - return nil, h.ArgErr() - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - acmeIssuer.ExternalAccount = &acme.EAB{ - KeyID: arg[0], - MACKey: arg[1], - } + case "key_type": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + keyType = arg[0] - case "issuer": - if !h.NextArg() { - return nil, h.ArgErr() - } - modName := h.Val() - modID := "tls.issuance." + modName - unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) - if err != nil { - return nil, err - } - issuer, ok := unm.(certmagic.Issuer) - if !ok { - return nil, h.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm) - } - issuers = append(issuers, issuer) + case "eab": + arg := h.RemainingArgs() + if len(arg) != 2 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + acmeIssuer.ExternalAccount = &acme.EAB{ + KeyID: arg[0], + MACKey: arg[1], + } - case "get_certificate": - if !h.NextArg() { - return nil, h.ArgErr() - } - modName := h.Val() - modID := "tls.get_certificate." + modName - unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) - if err != nil { - return nil, err - } - certManager, ok := unm.(certmagic.Manager) - if !ok { - return nil, h.Errf("module %s (%T) is not a certmagic.CertificateManager", modID, unm) - } - certManagers = append(certManagers, certManager) + case "issuer": + if !h.NextArg() { + return nil, h.ArgErr() + } + modName := h.Val() + modID := "tls.issuance." + modName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + issuer, ok := unm.(certmagic.Issuer) + if !ok { + return nil, h.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm) + } + issuers = append(issuers, issuer) - case "dns": - if !h.NextArg() { - return nil, h.ArgErr() - } - provName := h.Val() - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - if acmeIssuer.Challenges == nil { - acmeIssuer.Challenges = new(caddytls.ChallengesConfig) - } - if acmeIssuer.Challenges.DNS == nil { - acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) - } - modID := "dns.providers." + provName - unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) - if err != nil { - return nil, err - } - acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings) + case "get_certificate": + if !h.NextArg() { + return nil, h.ArgErr() + } + modName := h.Val() + modID := "tls.get_certificate." + modName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + certManager, ok := unm.(certmagic.Manager) + if !ok { + return nil, h.Errf("module %s (%T) is not a certmagic.CertificateManager", modID, unm) + } + certManagers = append(certManagers, certManager) - case "resolvers": - args := h.RemainingArgs() - if len(args) == 0 { - return nil, h.ArgErr() - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - if acmeIssuer.Challenges == nil { - acmeIssuer.Challenges = new(caddytls.ChallengesConfig) - } - if acmeIssuer.Challenges.DNS == nil { - acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) - } - acmeIssuer.Challenges.DNS.Resolvers = args + case "dns": + if !h.NextArg() { + return nil, h.ArgErr() + } + provName := h.Val() + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + modID := "dns.providers." + provName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings) - case "propagation_delay": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - delayStr := arg[0] - delay, err := caddy.ParseDuration(delayStr) - if err != nil { - return nil, h.Errf("invalid propagation_delay duration %s: %v", delayStr, err) - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - if acmeIssuer.Challenges == nil { - acmeIssuer.Challenges = new(caddytls.ChallengesConfig) - } - if acmeIssuer.Challenges.DNS == nil { - acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) - } - acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay) + case "resolvers": + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.Resolvers = args - case "propagation_timeout": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - timeoutStr := arg[0] - var timeout time.Duration - if timeoutStr == "-1" { - timeout = time.Duration(-1) - } else { - var err error - timeout, err = caddy.ParseDuration(timeoutStr) - if err != nil { - return nil, h.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err) - } - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - if acmeIssuer.Challenges == nil { - acmeIssuer.Challenges = new(caddytls.ChallengesConfig) - } - if acmeIssuer.Challenges.DNS == nil { - acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) - } - acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout) + case "propagation_delay": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + delayStr := arg[0] + delay, err := caddy.ParseDuration(delayStr) + if err != nil { + return nil, h.Errf("invalid propagation_delay duration %s: %v", delayStr, err) + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay) - case "dns_ttl": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - ttlStr := arg[0] - ttl, err := caddy.ParseDuration(ttlStr) + case "propagation_timeout": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + timeoutStr := arg[0] + var timeout time.Duration + if timeoutStr == "-1" { + timeout = time.Duration(-1) + } else { + var err error + timeout, err = caddy.ParseDuration(timeoutStr) if err != nil { - return nil, h.Errf("invalid dns_ttl duration %s: %v", ttlStr, err) - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - if acmeIssuer.Challenges == nil { - acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + return nil, h.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err) } - if acmeIssuer.Challenges.DNS == nil { - acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) - } - acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl) + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout) - case "dns_challenge_override_domain": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - if acmeIssuer.Challenges == nil { - acmeIssuer.Challenges = new(caddytls.ChallengesConfig) - } - if acmeIssuer.Challenges.DNS == nil { - acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) - } - acmeIssuer.Challenges.DNS.OverrideDomain = arg[0] + case "dns_ttl": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + ttlStr := arg[0] + ttl, err := caddy.ParseDuration(ttlStr) + if err != nil { + return nil, h.Errf("invalid dns_ttl duration %s: %v", ttlStr, err) + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl) - case "ca_root": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - if acmeIssuer == nil { - acmeIssuer = new(caddytls.ACMEIssuer) - } - acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0]) + case "dns_challenge_override_domain": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.OverrideDomain = arg[0] - case "on_demand": - if h.NextArg() { - return nil, h.ArgErr() - } - onDemand = true + case "ca_root": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0]) - case "reuse_private_keys": - if h.NextArg() { - return nil, h.ArgErr() - } - reusePrivateKeys = true + case "on_demand": + if h.NextArg() { + return nil, h.ArgErr() + } + onDemand = true - case "insecure_secrets_log": - if !h.NextArg() { - return nil, h.ArgErr() - } - cp.InsecureSecretsLog = h.Val() + case "reuse_private_keys": + if h.NextArg() { + return nil, h.ArgErr() + } + reusePrivateKeys = true - default: - return nil, h.Errf("unknown subdirective: %s", h.Val()) + case "insecure_secrets_log": + if !h.NextArg() { + return nil, h.ArgErr() } - } + cp.InsecureSecretsLog = h.Val() - // a naked tls directive is not allowed - if len(firstLine) == 0 && !hasBlock { - return nil, h.ArgErr() + default: + return nil, h.Errf("unknown subdirective: %s", h.Val()) } } + // a naked tls directive is not allowed + if len(firstLine) == 0 && !hasBlock { + return nil, h.ArgErr() + } + // begin building the final config values configVals := []ConfigValue{} @@ -646,10 +643,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) { // // root [<matcher>] <path> func parseRoot(h Helper) ([]ConfigValue, error) { - // consume directive name - if !h.NextArg() { - return nil, h.ArgErr() - } + h.Next() // consume directive name // count the tokens to determine what to do argsCount := h.CountRemainingArgs() @@ -673,11 +667,8 @@ func parseRoot(h Helper) ([]ConfigValue, error) { if err != nil { return nil, err } + h.Next() // consume directive name again, matcher parsing does a reset - // consume directive name, again, because extracting matcher does a reset - if !h.NextArg() { - return nil, h.ArgErr() - } // advance to the root path if !h.NextArg() { return nil, h.ArgErr() @@ -690,17 +681,14 @@ func parseRoot(h Helper) ([]ConfigValue, error) { // // fs <filesystem> func parseFilesystem(h Helper) (caddyhttp.MiddlewareHandler, error) { - var name string - for h.Next() { - if !h.NextArg() { - return nil, h.ArgErr() - } - name = h.Val() - if h.NextArg() { - return nil, h.ArgErr() - } + h.Next() // consume directive name + if !h.NextArg() { + return nil, h.ArgErr() } - return caddyhttp.VarsMiddleware{"fs": name}, nil + if h.NextArg() { + return nil, h.ArgErr() + } + return caddyhttp.VarsMiddleware{"fs": h.Val()}, nil } // parseVars parses the vars directive. See its UnmarshalCaddyfile method for syntax. @@ -720,10 +708,7 @@ func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) { // respond with HTTP 200 and no Location header; redirect is performed // with JS and a meta tag). func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) { - if !h.Next() { - return nil, h.ArgErr() - } - + h.Next() // consume directive name if !h.NextArg() { return nil, h.ArgErr() } @@ -739,8 +724,10 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) { switch code { case "permanent": code = "301" + case "temporary", "": code = "302" + case "html": // 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. @@ -758,6 +745,7 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) { body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo) hdr = http.Header{"Content-Type": []string{"text/html; charset=utf-8"}} code = "200" // don't redirect non-browser clients + default: // Allow placeholders for the code if strings.HasPrefix(code, "{") { @@ -796,10 +784,7 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) { func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) { sr := new(caddyhttp.StaticResponse) err := sr.UnmarshalCaddyfile(h.Dispenser) - if err != nil { - return nil, err - } - return sr, nil + return sr, err } // parseAbort parses the abort directive. @@ -815,10 +800,7 @@ func parseAbort(h Helper) (caddyhttp.MiddlewareHandler, error) { func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) { se := new(caddyhttp.StaticError) err := se.UnmarshalCaddyfile(h.Dispenser) - if err != nil { - return nil, err - } - return se, nil + return se, err } // parseRoute parses the route directive. @@ -844,11 +826,11 @@ func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) { } func parseHandleErrors(h Helper) ([]ConfigValue, error) { - h.Next() - args := h.RemainingArgs() + h.Next() // consume directive name + expression := "" + args := h.RemainingArgs() if len(args) > 0 { - expression = "" codes := []string{} for _, val := range args { if len(val) != 3 { @@ -951,184 +933,185 @@ func parseLog(h Helper) ([]ConfigValue, error) { // level. The parseAsGlobalOption parameter is used to distinguish any differing logic // between the two. func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue, error) { + h.Next() // consume option name + // When the globalLogNames parameter is passed in, we make // modifications to the parsing behavior. parseAsGlobalOption := globalLogNames != nil var configValues []ConfigValue - for h.Next() { - // Logic below expects that a name is always present when a - // global option is being parsed; or an optional override - // is supported for access logs. - var logName string - if parseAsGlobalOption { - if h.NextArg() { - logName = h.Val() + // Logic below expects that a name is always present when a + // global option is being parsed; or an optional override + // is supported for access logs. + var logName string - // Only a single argument is supported. - if h.NextArg() { - return nil, h.ArgErr() - } - } else { - // If there is no log name specified, we - // reference the default logger. See the - // setupNewDefault function in the logging - // package for where this is configured. - logName = caddy.DefaultLoggerName - } + if parseAsGlobalOption { + if h.NextArg() { + logName = h.Val() - // Verify this name is unused. - _, used := globalLogNames[logName] - if used { - return nil, h.Err("duplicate global log option for: " + logName) + // Only a single argument is supported. + if h.NextArg() { + return nil, h.ArgErr() } - globalLogNames[logName] = struct{}{} } else { - // An optional override of the logger name can be provided; - // otherwise a default will be used, like "log0", "log1", etc. - if h.NextArg() { - logName = h.Val() + // If there is no log name specified, we + // reference the default logger. See the + // setupNewDefault function in the logging + // package for where this is configured. + logName = caddy.DefaultLoggerName + } - // Only a single argument is supported. - if h.NextArg() { - return nil, h.ArgErr() - } - } + // Verify this name is unused. + _, used := globalLogNames[logName] + if used { + return nil, h.Err("duplicate global log option for: " + logName) } + globalLogNames[logName] = struct{}{} + } else { + // An optional override of the logger name can be provided; + // otherwise a default will be used, like "log0", "log1", etc. + if h.NextArg() { + logName = h.Val() - cl := new(caddy.CustomLog) + // Only a single argument is supported. + if h.NextArg() { + return nil, h.ArgErr() + } + } + } - // allow overriding the current site block's hostnames for this logger; - // this is useful for setting up loggers per subdomain in a site block - // with a wildcard domain - customHostnames := []string{} + cl := new(caddy.CustomLog) - for h.NextBlock(0) { - switch h.Val() { - case "hostnames": - if parseAsGlobalOption { - return nil, h.Err("hostnames is not allowed in the log global options") - } - args := h.RemainingArgs() - if len(args) == 0 { - return nil, h.ArgErr() - } - customHostnames = append(customHostnames, args...) + // allow overriding the current site block's hostnames for this logger; + // this is useful for setting up loggers per subdomain in a site block + // with a wildcard domain + customHostnames := []string{} - case "output": - if !h.NextArg() { - return nil, h.ArgErr() - } - moduleName := h.Val() - - // can't use the usual caddyfile.Unmarshaler flow with the - // standard writers because they are in the caddy package - // (because they are the default) and implementing that - // interface there would unfortunately create circular import - var wo caddy.WriterOpener - switch moduleName { - case "stdout": - wo = caddy.StdoutWriter{} - case "stderr": - wo = caddy.StderrWriter{} - case "discard": - wo = caddy.DiscardWriter{} - default: - modID := "caddy.logging.writers." + moduleName - unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) - if err != nil { - return nil, err - } - var ok bool - wo, ok = unm.(caddy.WriterOpener) - if !ok { - return nil, h.Errf("module %s (%T) is not a WriterOpener", modID, unm) - } - } - cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings) + for h.NextBlock(0) { + switch h.Val() { + case "hostnames": + if parseAsGlobalOption { + return nil, h.Err("hostnames is not allowed in the log global options") + } + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.ArgErr() + } + customHostnames = append(customHostnames, args...) - case "format": - if !h.NextArg() { - return nil, h.ArgErr() - } - moduleName := h.Val() - moduleID := "caddy.logging.encoders." + moduleName - unm, err := caddyfile.UnmarshalModule(h.Dispenser, moduleID) + case "output": + if !h.NextArg() { + return nil, h.ArgErr() + } + moduleName := h.Val() + + // can't use the usual caddyfile.Unmarshaler flow with the + // standard writers because they are in the caddy package + // (because they are the default) and implementing that + // interface there would unfortunately create circular import + var wo caddy.WriterOpener + switch moduleName { + case "stdout": + wo = caddy.StdoutWriter{} + case "stderr": + wo = caddy.StderrWriter{} + case "discard": + wo = caddy.DiscardWriter{} + default: + modID := "caddy.logging.writers." + moduleName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) if err != nil { return nil, err } - enc, ok := unm.(zapcore.Encoder) + var ok bool + wo, ok = unm.(caddy.WriterOpener) if !ok { - return nil, h.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm) + return nil, h.Errf("module %s (%T) is not a WriterOpener", modID, unm) } - cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings) + } + cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings) - case "level": - if !h.NextArg() { - return nil, h.ArgErr() - } - cl.Level = h.Val() - if h.NextArg() { - return nil, h.ArgErr() - } + case "format": + if !h.NextArg() { + return nil, h.ArgErr() + } + moduleName := h.Val() + moduleID := "caddy.logging.encoders." + moduleName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, moduleID) + if err != nil { + return nil, err + } + enc, ok := unm.(zapcore.Encoder) + if !ok { + return nil, h.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm) + } + cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings) - case "include": - if !parseAsGlobalOption { - return nil, h.Err("include is not allowed in the log directive") - } - for h.NextArg() { - cl.Include = append(cl.Include, h.Val()) - } + case "level": + if !h.NextArg() { + return nil, h.ArgErr() + } + cl.Level = h.Val() + if h.NextArg() { + return nil, h.ArgErr() + } - case "exclude": - if !parseAsGlobalOption { - return nil, h.Err("exclude is not allowed in the log directive") - } - for h.NextArg() { - cl.Exclude = append(cl.Exclude, h.Val()) - } + case "include": + if !parseAsGlobalOption { + return nil, h.Err("include is not allowed in the log directive") + } + for h.NextArg() { + cl.Include = append(cl.Include, h.Val()) + } - default: - return nil, h.Errf("unrecognized subdirective: %s", h.Val()) + case "exclude": + if !parseAsGlobalOption { + return nil, h.Err("exclude is not allowed in the log directive") + } + for h.NextArg() { + cl.Exclude = append(cl.Exclude, h.Val()) } + + default: + return nil, h.Errf("unrecognized subdirective: %s", h.Val()) } + } - var val namedCustomLog - val.hostnames = customHostnames + var val namedCustomLog + val.hostnames = customHostnames - isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog)) + isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog)) - // Skip handling of empty logging configs + // Skip handling of empty logging configs - if parseAsGlobalOption { - // Use indicated name for global log options + if parseAsGlobalOption { + // Use indicated name for global log options + val.name = logName + } else { + if logName != "" { val.name = logName - } else { - if logName != "" { - val.name = logName - } else if !isEmptyConfig { - // Construct a log name for server log streams - logCounter, ok := h.State["logCounter"].(int) - if !ok { - logCounter = 0 - } - val.name = fmt.Sprintf("log%d", logCounter) - logCounter++ - h.State["logCounter"] = logCounter - } - if val.name != "" { - cl.Include = []string{"http.log.access." + val.name} + } else if !isEmptyConfig { + // Construct a log name for server log streams + logCounter, ok := h.State["logCounter"].(int) + if !ok { + logCounter = 0 } + val.name = fmt.Sprintf("log%d", logCounter) + logCounter++ + h.State["logCounter"] = logCounter } - if !isEmptyConfig { - val.log = cl + if val.name != "" { + cl.Include = []string{"http.log.access." + val.name} } - configValues = append(configValues, ConfigValue{ - Class: "custom_log", - Value: val, - }) } + if !isEmptyConfig { + val.log = cl + } + configValues = append(configValues, ConfigValue{ + Class: "custom_log", + Value: val, + }) return configValues, nil } @@ -1136,10 +1119,9 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue // // skip_log [<matcher>] func parseSkipLog(h Helper) (caddyhttp.MiddlewareHandler, error) { - for h.Next() { - if h.NextArg() { - return nil, h.ArgErr() - } + h.Next() // consume directive name + if h.NextArg() { + return nil, h.ArgErr() } return caddyhttp.VarsMiddleware{"skip_log": true}, nil } diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 58da2bd79..6e5241c7f 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -271,12 +271,6 @@ func (h Helper) GroupRoutes(vals []ConfigValue) { } } -// NewBindAddresses returns config values relevant to adding -// listener bind addresses to the config. -func (h Helper) NewBindAddresses(addrs []string) []ConfigValue { - return []ConfigValue{{Class: "bind", Value: addrs}} -} - // WithDispenser returns a new instance based on d. All others Helper // fields are copied, so typically maps are shared with this new instance. func (h Helper) WithDispenser(d *caddyfile.Dispenser) Helper { diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 066df3014..54e781119 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -1381,68 +1381,73 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod } func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.ModuleMap) error { - for d.Next() { - // this is the "name" for "named matchers" - definitionName := d.Val() + d.Next() // advance to the first token - if _, ok := matchers[definitionName]; ok { - return fmt.Errorf("matcher is defined more than once: %s", definitionName) + // this is the "name" for "named matchers" + definitionName := d.Val() + + if _, ok := matchers[definitionName]; ok { + return fmt.Errorf("matcher is defined more than once: %s", definitionName) + } + matchers[definitionName] = make(caddy.ModuleMap) + + // given a matcher name and the tokens following it, parse + // the tokens as a matcher module and record it + makeMatcher := func(matcherName string, tokens []caddyfile.Token) error { + mod, err := caddy.GetModule("http.matchers." + matcherName) + if err != nil { + return fmt.Errorf("getting matcher module '%s': %v", matcherName, err) } - matchers[definitionName] = make(caddy.ModuleMap) + unm, ok := mod.New().(caddyfile.Unmarshaler) + if !ok { + return fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) + } + err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens)) + if err != nil { + return err + } + rm, ok := unm.(caddyhttp.RequestMatcher) + if !ok { + return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) + } + matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) + return nil + } - // given a matcher name and the tokens following it, parse - // the tokens as a matcher module and record it - makeMatcher := func(matcherName string, tokens []caddyfile.Token) error { - mod, err := caddy.GetModule("http.matchers." + matcherName) - if err != nil { - return fmt.Errorf("getting matcher module '%s': %v", matcherName, err) - } - unm, ok := mod.New().(caddyfile.Unmarshaler) - if !ok { - return fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) - } - err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens)) + // if the next token is quoted, we can assume it's not a matcher name + // and that it's probably an 'expression' matcher + if d.NextArg() { + if d.Token().Quoted() { + // since it was missing the matcher name, we insert a token + // in front of the expression token itself + err := makeMatcher("expression", []caddyfile.Token{ + {Text: "expression", File: d.File(), Line: d.Line()}, + d.Token(), + }) if err != nil { return err } - rm, ok := unm.(caddyhttp.RequestMatcher) - if !ok { - return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) - } - matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) return nil } - // if the next token is quoted, we can assume it's not a matcher name - // and that it's probably an 'expression' matcher - if d.NextArg() { - if d.Token().Quoted() { - err := makeMatcher("expression", []caddyfile.Token{d.Token()}) - if err != nil { - return err - } - continue - } - - // if it wasn't quoted, then we need to rewind after calling - // d.NextArg() so the below properly grabs the matcher name - d.Prev() - } + // if it wasn't quoted, then we need to rewind after calling + // d.NextArg() so the below properly grabs the matcher name + d.Prev() + } - // in case there are multiple instances of the same matcher, concatenate - // their tokens (we expect that UnmarshalCaddyfile should be able to - // handle more than one segment); otherwise, we'd overwrite other - // instances of the matcher in this set - tokensByMatcherName := make(map[string][]caddyfile.Token) - for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { - matcherName := d.Val() - tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) - } - for matcherName, tokens := range tokensByMatcherName { - err := makeMatcher(matcherName, tokens) - if err != nil { - return err - } + // in case there are multiple instances of the same matcher, concatenate + // their tokens (we expect that UnmarshalCaddyfile should be able to + // handle more than one segment); otherwise, we'd overwrite other + // instances of the matcher in this set + tokensByMatcherName := make(map[string][]caddyfile.Token) + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + matcherName := d.Val() + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) + } + for matcherName, tokens := range tokensByMatcherName { + err := makeMatcher(matcherName, tokens) + if err != nil { + return err } } return nil diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index ba1896b6b..fa447f8dc 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -62,105 +62,103 @@ func init() { func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil } func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name var httpPort int - for d.Next() { - var httpPortStr string - if !d.AllArgs(&httpPortStr) { - return 0, d.ArgErr() - } - var err error - httpPort, err = strconv.Atoi(httpPortStr) - if err != nil { - return 0, d.Errf("converting port '%s' to integer value: %v", httpPortStr, err) - } + var httpPortStr string + if !d.AllArgs(&httpPortStr) { + return 0, d.ArgErr() + } + var err error + httpPort, err = strconv.Atoi(httpPortStr) + if err != nil { + return 0, d.Errf("converting port '%s' to integer value: %v", httpPortStr, err) } return httpPort, nil } func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name var httpsPort int - for d.Next() { - var httpsPortStr string - if !d.AllArgs(&httpsPortStr) { - return 0, d.ArgErr() - } - var err error - httpsPort, err = strconv.Atoi(httpsPortStr) - if err != nil { - return 0, d.Errf("converting port '%s' to integer value: %v", httpsPortStr, err) - } + var httpsPortStr string + if !d.AllArgs(&httpsPortStr) { + return 0, d.ArgErr() + } + var err error + httpsPort, err = strconv.Atoi(httpsPortStr) + if err != nil { + return 0, d.Errf("converting port '%s' to integer value: %v", httpsPortStr, err) } return httpsPort, nil } func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) { - newOrder := directiveOrder + d.Next() // consume option name - for d.Next() { - // get directive name - if !d.Next() { - return nil, d.ArgErr() - } - dirName := d.Val() - if _, ok := registeredDirectives[dirName]; !ok { - return nil, d.Errf("%s is not a registered directive", dirName) - } + // get directive name + if !d.Next() { + return nil, d.ArgErr() + } + dirName := d.Val() + if _, ok := registeredDirectives[dirName]; !ok { + return nil, d.Errf("%s is not a registered directive", dirName) + } - // get positional token - if !d.Next() { - return nil, d.ArgErr() - } - pos := d.Val() + // get positional token + if !d.Next() { + return nil, d.ArgErr() + } + pos := d.Val() - // if directive exists, first remove it - for i, d := range newOrder { - if d == dirName { - newOrder = append(newOrder[:i], newOrder[i+1:]...) - break - } - } + newOrder := directiveOrder - // act on the positional - switch pos { - case "first": - newOrder = append([]string{dirName}, newOrder...) - if d.NextArg() { - return nil, d.ArgErr() - } - directiveOrder = newOrder - return newOrder, nil - case "last": - newOrder = append(newOrder, dirName) - if d.NextArg() { - return nil, d.ArgErr() - } - directiveOrder = newOrder - return newOrder, nil - case "before": - case "after": - default: - return nil, d.Errf("unknown positional '%s'", pos) + // if directive exists, first remove it + for i, d := range newOrder { + if d == dirName { + newOrder = append(newOrder[:i], newOrder[i+1:]...) + break } + } - // get name of other directive - if !d.NextArg() { + // act on the positional + switch pos { + case "first": + newOrder = append([]string{dirName}, newOrder...) + if d.NextArg() { return nil, d.ArgErr() } - otherDir := d.Val() + directiveOrder = newOrder + return newOrder, nil + case "last": + newOrder = append(newOrder, dirName) if d.NextArg() { return nil, d.ArgErr() } + directiveOrder = newOrder + return newOrder, nil + case "before": + case "after": + default: + return nil, d.Errf("unknown positional '%s'", pos) + } + + // get name of other directive + if !d.NextArg() { + return nil, d.ArgErr() + } + otherDir := d.Val() + if d.NextArg() { + return nil, d.ArgErr() + } - // insert directive into proper position - for i, d := range newOrder { - if d == otherDir { - if pos == "before" { - newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...) - } else if pos == "after" { - newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...) - } - break + // insert directive into proper position + for i, d := range newOrder { + if d == otherDir { + if pos == "before" { + newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...) + } else if pos == "after" { + newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...) } + break } } @@ -223,57 +221,58 @@ func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) { func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) { eab := new(acme.EAB) - for d.Next() { - if d.NextArg() { - return nil, d.ArgErr() - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "key_id": - if !d.NextArg() { - return nil, d.ArgErr() - } - eab.KeyID = d.Val() - - case "mac_key": - if !d.NextArg() { - return nil, d.ArgErr() - } - eab.MACKey = d.Val() - - default: - return nil, d.Errf("unrecognized parameter '%s'", d.Val()) + d.Next() // consume option name + if d.NextArg() { + return nil, d.ArgErr() + } + for d.NextBlock(0) { + switch d.Val() { + case "key_id": + if !d.NextArg() { + return nil, d.ArgErr() + } + eab.KeyID = d.Val() + + case "mac_key": + if !d.NextArg() { + return nil, d.ArgErr() } + eab.MACKey = d.Val() + + default: + return nil, d.Errf("unrecognized parameter '%s'", d.Val()) } } return eab, nil } func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) { + d.Next() // consume option name + var issuers []certmagic.Issuer if existing != nil { issuers = existing.([]certmagic.Issuer) } - for d.Next() { // consume option name - if !d.Next() { // get issuer module name - return nil, d.ArgErr() - } - modID := "tls.issuance." + d.Val() - unm, err := caddyfile.UnmarshalModule(d, modID) - if err != nil { - return nil, err - } - iss, ok := unm.(certmagic.Issuer) - if !ok { - return nil, d.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm) - } - issuers = append(issuers, iss) + + // get issuer module name + if !d.Next() { + return nil, d.ArgErr() + } + modID := "tls.issuance." + d.Val() + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return nil, err + } + iss, ok := unm.(certmagic.Issuer) + if !ok { + return nil, d.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm) } + issuers = append(issuers, iss) return issuers, nil } func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) { - d.Next() // consume parameter name + d.Next() // consume option name if !d.Next() { return "", d.ArgErr() } @@ -285,7 +284,7 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) { } func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) { - d.Next() // consume parameter name + d.Next() // consume option name val := d.RemainingArgs() if len(val) == 0 { return "", d.ArgErr() @@ -294,33 +293,33 @@ func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) { } func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name + adminCfg := new(caddy.AdminConfig) - for d.Next() { - if d.NextArg() { - listenAddress := d.Val() - if listenAddress == "off" { - adminCfg.Disabled = true - if d.Next() { // Do not accept any remaining options including block - return nil, d.Err("No more option is allowed after turning off admin config") - } - } else { - adminCfg.Listen = listenAddress - if d.NextArg() { // At most 1 arg is allowed - return nil, d.ArgErr() - } + if d.NextArg() { + listenAddress := d.Val() + if listenAddress == "off" { + adminCfg.Disabled = true + if d.Next() { // Do not accept any remaining options including block + return nil, d.Err("No more option is allowed after turning off admin config") + } + } else { + adminCfg.Listen = listenAddress + if d.NextArg() { // At most 1 arg is allowed + return nil, d.ArgErr() } } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "enforce_origin": - adminCfg.EnforceOrigin = true + } + for d.NextBlock(0) { + switch d.Val() { + case "enforce_origin": + adminCfg.EnforceOrigin = true - case "origins": - adminCfg.Origins = d.RemainingArgs() + case "origins": + adminCfg.Origins = d.RemainingArgs() - default: - return nil, d.Errf("unrecognized parameter '%s'", d.Val()) - } + default: + return nil, d.Errf("unrecognized parameter '%s'", d.Val()) } } if adminCfg.Listen == "" && !adminCfg.Disabled { @@ -330,57 +329,57 @@ func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) { } func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name + if d.NextArg() { + return nil, d.ArgErr() + } + var ond *caddytls.OnDemandConfig - for d.Next() { - if d.NextArg() { - return nil, d.ArgErr() - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "ask": - if !d.NextArg() { - return nil, d.ArgErr() - } - if ond == nil { - ond = new(caddytls.OnDemandConfig) - } - ond.Ask = d.Val() - - case "interval": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, err - } - if ond == nil { - ond = new(caddytls.OnDemandConfig) - } - if ond.RateLimit == nil { - ond.RateLimit = new(caddytls.RateLimit) - } - ond.RateLimit.Interval = caddy.Duration(dur) - - case "burst": - if !d.NextArg() { - return nil, d.ArgErr() - } - burst, err := strconv.Atoi(d.Val()) - if err != nil { - return nil, err - } - if ond == nil { - ond = new(caddytls.OnDemandConfig) - } - if ond.RateLimit == nil { - ond.RateLimit = new(caddytls.RateLimit) - } - ond.RateLimit.Burst = burst - - default: - return nil, d.Errf("unrecognized parameter '%s'", d.Val()) + for d.NextBlock(0) { + switch d.Val() { + case "ask": + if !d.NextArg() { + return nil, d.ArgErr() + } + if ond == nil { + ond = new(caddytls.OnDemandConfig) + } + ond.Ask = d.Val() + + case "interval": + if !d.NextArg() { + return nil, d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return nil, err } + if ond == nil { + ond = new(caddytls.OnDemandConfig) + } + if ond.RateLimit == nil { + ond.RateLimit = new(caddytls.RateLimit) + } + ond.RateLimit.Interval = caddy.Duration(dur) + + case "burst": + if !d.NextArg() { + return nil, d.ArgErr() + } + burst, err := strconv.Atoi(d.Val()) + if err != nil { + return nil, err + } + if ond == nil { + ond = new(caddytls.OnDemandConfig) + } + if ond.RateLimit == nil { + ond.RateLimit = new(caddytls.RateLimit) + } + ond.RateLimit.Burst = burst + + default: + return nil, d.Errf("unrecognized parameter '%s'", d.Val()) } } if ond == nil { @@ -390,7 +389,7 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) { } func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) { - d.Next() // consume parameter name + d.Next() // consume option name if !d.Next() { return "", d.ArgErr() } @@ -405,7 +404,7 @@ func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) { } func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) { - d.Next() // consume parameter name + d.Next() // consume option name if !d.Next() { return "", d.ArgErr() } diff --git a/caddyconfig/httpcaddyfile/pkiapp.go b/caddyconfig/httpcaddyfile/pkiapp.go index b5c682187..c57263baf 100644 --- a/caddyconfig/httpcaddyfile/pkiapp.go +++ b/caddyconfig/httpcaddyfile/pkiapp.go @@ -48,124 +48,124 @@ func init() { // // When the CA ID is unspecified, 'local' is assumed. func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) { - pki := &caddypki.PKI{CAs: make(map[string]*caddypki.CA)} + d.Next() // consume app name - for d.Next() { - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "ca": - pkiCa := new(caddypki.CA) + pki := &caddypki.PKI{ + CAs: make(map[string]*caddypki.CA), + } + for d.NextBlock(0) { + switch d.Val() { + case "ca": + pkiCa := new(caddypki.CA) + if d.NextArg() { + pkiCa.ID = d.Val() if d.NextArg() { - pkiCa.ID = d.Val() - if d.NextArg() { + return nil, d.ArgErr() + } + } + if pkiCa.ID == "" { + pkiCa.ID = caddypki.DefaultCAID + } + + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "name": + if !d.NextArg() { return nil, d.ArgErr() } - } - if pkiCa.ID == "" { - pkiCa.ID = caddypki.DefaultCAID - } + pkiCa.Name = d.Val() - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "name": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.Name = d.Val() + case "root_cn": + if !d.NextArg() { + return nil, d.ArgErr() + } + pkiCa.RootCommonName = d.Val() - case "root_cn": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.RootCommonName = d.Val() + case "intermediate_cn": + if !d.NextArg() { + return nil, d.ArgErr() + } + pkiCa.IntermediateCommonName = d.Val() - case "intermediate_cn": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.IntermediateCommonName = d.Val() + case "intermediate_lifetime": + if !d.NextArg() { + return nil, d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return nil, err + } + pkiCa.IntermediateLifetime = caddy.Duration(dur) - case "intermediate_lifetime": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, err - } - pkiCa.IntermediateLifetime = caddy.Duration(dur) + case "root": + if pkiCa.Root == nil { + pkiCa.Root = new(caddypki.KeyPair) + } + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "cert": + if !d.NextArg() { + return nil, d.ArgErr() + } + pkiCa.Root.Certificate = d.Val() - case "root": - if pkiCa.Root == nil { - pkiCa.Root = new(caddypki.KeyPair) - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "cert": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.Root.Certificate = d.Val() - - case "key": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.Root.PrivateKey = d.Val() - - case "format": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.Root.Format = d.Val() - - default: - return nil, d.Errf("unrecognized pki ca root option '%s'", d.Val()) + case "key": + if !d.NextArg() { + return nil, d.ArgErr() } - } + pkiCa.Root.PrivateKey = d.Val() - case "intermediate": - if pkiCa.Intermediate == nil { - pkiCa.Intermediate = new(caddypki.KeyPair) - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "cert": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.Intermediate.Certificate = d.Val() - - case "key": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.Intermediate.PrivateKey = d.Val() - - case "format": - if !d.NextArg() { - return nil, d.ArgErr() - } - pkiCa.Intermediate.Format = d.Val() - - default: - return nil, d.Errf("unrecognized pki ca intermediate option '%s'", d.Val()) + case "format": + if !d.NextArg() { + return nil, d.ArgErr() } + pkiCa.Root.Format = d.Val() + + default: + return nil, d.Errf("unrecognized pki ca root option '%s'", d.Val()) } + } - default: - return nil, d.Errf("unrecognized pki ca option '%s'", d.Val()) + case "intermediate": + if pkiCa.Intermediate == nil { + pkiCa.Intermediate = new(caddypki.KeyPair) } - } + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "cert": + if !d.NextArg() { + return nil, d.ArgErr() + } + pkiCa.Intermediate.Certificate = d.Val() - pki.CAs[pkiCa.ID] = pkiCa + case "key": + if !d.NextArg() { + return nil, d.ArgErr() + } + pkiCa.Intermediate.PrivateKey = d.Val() - default: - return nil, d.Errf("unrecognized pki option '%s'", d.Val()) + case "format": + if !d.NextArg() { + return nil, d.ArgErr() + } + pkiCa.Intermediate.Format = d.Val() + + default: + return nil, d.Errf("unrecognized pki ca intermediate option '%s'", d.Val()) + } + } + + default: + return nil, d.Errf("unrecognized pki ca option '%s'", d.Val()) + } } + + pki.CAs[pkiCa.ID] = pkiCa + + default: + return nil, d.Errf("unrecognized pki option '%s'", d.Val()) } } - return pki, nil } diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go index c131a6417..62902b964 100644 --- a/caddyconfig/httpcaddyfile/serveroptions.go +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -53,235 +53,235 @@ type serverOptions struct { } func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { + d.Next() // consume option name + serverOpts := serverOptions{} - for d.Next() { + if d.NextArg() { + serverOpts.ListenerAddress = d.Val() if d.NextArg() { - serverOpts.ListenerAddress = d.Val() - if d.NextArg() { + return nil, d.ArgErr() + } + } + for d.NextBlock(0) { + switch d.Val() { + case "name": + if serverOpts.ListenerAddress == "" { + return nil, d.Errf("cannot set a name for a server without a listener address") + } + if !d.NextArg() { return nil, d.ArgErr() } - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "name": - if serverOpts.ListenerAddress == "" { - return nil, d.Errf("cannot set a name for a server without a listener address") + serverOpts.Name = d.Val() + + case "listener_wrappers": + for nesting := d.Nesting(); d.NextBlock(nesting); { + modID := "caddy.listeners." + d.Val() + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return nil, err } - if !d.NextArg() { - return nil, d.ArgErr() + listenerWrapper, ok := unm.(caddy.ListenerWrapper) + if !ok { + return nil, fmt.Errorf("module %s (%T) is not a listener wrapper", modID, unm) } - serverOpts.Name = d.Val() + jsonListenerWrapper := caddyconfig.JSONModuleObject( + listenerWrapper, + "wrapper", + listenerWrapper.(caddy.Module).CaddyModule().ID.Name(), + nil, + ) + serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper) + } - case "listener_wrappers": - for nesting := d.Nesting(); d.NextBlock(nesting); { - modID := "caddy.listeners." + d.Val() - unm, err := caddyfile.UnmarshalModule(d, modID) + case "timeouts": + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "read_body": + if !d.NextArg() { + return nil, d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) if err != nil { - return nil, err + return nil, d.Errf("parsing read_body timeout duration: %v", err) } - listenerWrapper, ok := unm.(caddy.ListenerWrapper) - if !ok { - return nil, fmt.Errorf("module %s (%T) is not a listener wrapper", modID, unm) + serverOpts.ReadTimeout = caddy.Duration(dur) + + case "read_header": + if !d.NextArg() { + return nil, d.ArgErr() } - jsonListenerWrapper := caddyconfig.JSONModuleObject( - listenerWrapper, - "wrapper", - listenerWrapper.(caddy.Module).CaddyModule().ID.Name(), - nil, - ) - serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper) - } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return nil, d.Errf("parsing read_header timeout duration: %v", err) + } + serverOpts.ReadHeaderTimeout = caddy.Duration(dur) - case "timeouts": - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "read_body": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, d.Errf("parsing read_body timeout duration: %v", err) - } - serverOpts.ReadTimeout = caddy.Duration(dur) - - case "read_header": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, d.Errf("parsing read_header timeout duration: %v", err) - } - serverOpts.ReadHeaderTimeout = caddy.Duration(dur) - - case "write": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, d.Errf("parsing write timeout duration: %v", err) - } - serverOpts.WriteTimeout = caddy.Duration(dur) - - case "idle": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, d.Errf("parsing idle timeout duration: %v", err) - } - serverOpts.IdleTimeout = caddy.Duration(dur) - - default: - return nil, d.Errf("unrecognized timeouts option '%s'", d.Val()) + case "write": + if !d.NextArg() { + return nil, d.ArgErr() } - } - case "keepalive_interval": - if !d.NextArg() { - return nil, d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return nil, d.Errf("parsing keepalive interval duration: %v", err) - } - serverOpts.KeepAliveInterval = caddy.Duration(dur) + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return nil, d.Errf("parsing write timeout duration: %v", err) + } + serverOpts.WriteTimeout = caddy.Duration(dur) - case "max_header_size": - var sizeStr string - if !d.AllArgs(&sizeStr) { - return nil, d.ArgErr() - } - size, err := humanize.ParseBytes(sizeStr) - if err != nil { - return nil, d.Errf("parsing max_header_size: %v", err) - } - serverOpts.MaxHeaderBytes = int(size) + case "idle": + if !d.NextArg() { + return nil, d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return nil, d.Errf("parsing idle timeout duration: %v", err) + } + serverOpts.IdleTimeout = caddy.Duration(dur) - case "enable_full_duplex": - if d.NextArg() { - return nil, d.ArgErr() + default: + return nil, d.Errf("unrecognized timeouts option '%s'", d.Val()) } - serverOpts.EnableFullDuplex = true + } + case "keepalive_interval": + if !d.NextArg() { + return nil, d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return nil, d.Errf("parsing keepalive interval duration: %v", err) + } + serverOpts.KeepAliveInterval = caddy.Duration(dur) - case "log_credentials": - if d.NextArg() { - return nil, d.ArgErr() - } - serverOpts.ShouldLogCredentials = true + case "max_header_size": + var sizeStr string + if !d.AllArgs(&sizeStr) { + return nil, d.ArgErr() + } + size, err := humanize.ParseBytes(sizeStr) + if err != nil { + return nil, d.Errf("parsing max_header_size: %v", err) + } + serverOpts.MaxHeaderBytes = int(size) - case "protocols": - protos := d.RemainingArgs() - for _, proto := range protos { - if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" { - return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto) - } - if sliceContains(serverOpts.Protocols, proto) { - return nil, d.Errf("protocol %s specified more than once", proto) - } - serverOpts.Protocols = append(serverOpts.Protocols, proto) - } - if nesting := d.Nesting(); d.NextBlock(nesting) { - return nil, d.ArgErr() - } + case "enable_full_duplex": + if d.NextArg() { + return nil, d.ArgErr() + } + serverOpts.EnableFullDuplex = true - case "strict_sni_host": - if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" { - return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val()) - } - boolVal := true - if d.Val() == "insecure_off" { - boolVal = false - } - serverOpts.StrictSNIHost = &boolVal + case "log_credentials": + if d.NextArg() { + return nil, d.ArgErr() + } + serverOpts.ShouldLogCredentials = true - case "trusted_proxies": - if !d.NextArg() { - return nil, d.Err("trusted_proxies expects an IP range source module name as its first argument") - } - modID := "http.ip_sources." + d.Val() - unm, err := caddyfile.UnmarshalModule(d, modID) - if err != nil { - return nil, err + case "protocols": + protos := d.RemainingArgs() + for _, proto := range protos { + if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" { + return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto) } - source, ok := unm.(caddyhttp.IPRangeSource) - if !ok { - return nil, fmt.Errorf("module %s (%T) is not an IP range source", modID, unm) + if sliceContains(serverOpts.Protocols, proto) { + return nil, d.Errf("protocol %s specified more than once", proto) } - jsonSource := caddyconfig.JSONModuleObject( - source, - "source", - source.(caddy.Module).CaddyModule().ID.Name(), - nil, - ) - serverOpts.TrustedProxiesRaw = jsonSource + serverOpts.Protocols = append(serverOpts.Protocols, proto) + } + if nesting := d.Nesting(); d.NextBlock(nesting) { + return nil, d.ArgErr() + } + + case "strict_sni_host": + if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" { + return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val()) + } + boolVal := true + if d.Val() == "insecure_off" { + boolVal = false + } + serverOpts.StrictSNIHost = &boolVal + + case "trusted_proxies": + if !d.NextArg() { + return nil, d.Err("trusted_proxies expects an IP range source module name as its first argument") + } + modID := "http.ip_sources." + d.Val() + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return nil, err + } + source, ok := unm.(caddyhttp.IPRangeSource) + if !ok { + return nil, fmt.Errorf("module %s (%T) is not an IP range source", modID, unm) + } + jsonSource := caddyconfig.JSONModuleObject( + source, + "source", + source.(caddy.Module).CaddyModule().ID.Name(), + nil, + ) + serverOpts.TrustedProxiesRaw = jsonSource + + case "trusted_proxies_strict": + if d.NextArg() { + return nil, d.ArgErr() + } + serverOpts.TrustedProxiesStrict = 1 - case "trusted_proxies_strict": - if d.NextArg() { - return nil, d.ArgErr() + case "client_ip_headers": + headers := d.RemainingArgs() + for _, header := range headers { + if sliceContains(serverOpts.ClientIPHeaders, header) { + return nil, d.Errf("client IP header %s specified more than once", header) } - serverOpts.TrustedProxiesStrict = 1 + serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header) + } + if nesting := d.Nesting(); d.NextBlock(nesting) { + return nil, d.ArgErr() + } + + case "metrics": + if d.NextArg() { + return nil, d.ArgErr() + } + if nesting := d.Nesting(); d.NextBlock(nesting) { + return nil, d.ArgErr() + } + serverOpts.Metrics = new(caddyhttp.Metrics) + + // TODO: DEPRECATED. (August 2022) + case "protocol": + caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon") - case "client_ip_headers": - headers := d.RemainingArgs() - for _, header := range headers { - if sliceContains(serverOpts.ClientIPHeaders, header) { - return nil, d.Errf("client IP header %s specified more than once", header) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "allow_h2c": + caddy.Log().Named("caddyfile").Warn("DEPRECATED: allow_h2c will be removed soon; use protocols option instead") + + if d.NextArg() { + return nil, d.ArgErr() } - serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header) - } - if nesting := d.Nesting(); d.NextBlock(nesting) { - return nil, d.ArgErr() - } + if sliceContains(serverOpts.Protocols, "h2c") { + return nil, d.Errf("protocol h2c already specified") + } + serverOpts.Protocols = append(serverOpts.Protocols, "h2c") - case "metrics": - if d.NextArg() { - return nil, d.ArgErr() - } - if nesting := d.Nesting(); d.NextBlock(nesting) { - return nil, d.ArgErr() - } - serverOpts.Metrics = new(caddyhttp.Metrics) - - // TODO: DEPRECATED. (August 2022) - case "protocol": - caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon") - - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "allow_h2c": - caddy.Log().Named("caddyfile").Warn("DEPRECATED: allow_h2c will be removed soon; use protocols option instead") - - if d.NextArg() { - return nil, d.ArgErr() - } - if sliceContains(serverOpts.Protocols, "h2c") { - return nil, d.Errf("protocol h2c already specified") - } - serverOpts.Protocols = append(serverOpts.Protocols, "h2c") - - case "strict_sni_host": - caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol > strict_sni_host in this position will be removed soon; move up to the servers block instead") - - if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" { - return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val()) - } - boolVal := true - if d.Val() == "insecure_off" { - boolVal = false - } - serverOpts.StrictSNIHost = &boolVal - - default: - return nil, d.Errf("unrecognized protocol option '%s'", d.Val()) + case "strict_sni_host": + caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol > strict_sni_host in this position will be removed soon; move up to the servers block instead") + + if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" { + return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val()) } - } + boolVal := true + if d.Val() == "insecure_off" { + boolVal = false + } + serverOpts.StrictSNIHost = &boolVal - default: - return nil, d.Errf("unrecognized servers option '%s'", d.Val()) + default: + return nil, d.Errf("unrecognized protocol option '%s'", d.Val()) + } } + + default: + return nil, d.Errf("unrecognized servers option '%s'", d.Val()) } } return serverOpts, nil |