diff --git a/caddyconfig/caddyfile/adapter.go b/caddyconfig/caddyfile/adapter.go index d6ef602d..edd825b8 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 bf95a361..11d18cae 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -59,11 +59,8 @@ func init() { // // bind 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 // } 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] + // 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) } - - 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 - } - - 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) - - default: - return nil, h.ArgErr() + acmeIssuer.Email = firstLine[0] } - var hasBlock bool - for nesting := h.Nesting(); h.NextBlock(nesting); { - hasBlock = true + case 2: + certFilename := firstLine[0] + keyFilename := firstLine[1] - switch h.Val() { - case "protocols": - args := h.RemainingArgs() - if len(args) == 0 { - return nil, h.Errf("protocols requires one or two arguments") + // 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 + } + + 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) + + 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") + } + 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] + } + 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] + } + + 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 "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.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]) + + vType := h.Val() + modID := "tls.client_auth." + vType + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err } - 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()) + _, ok := unm.(caddytls.ClientCertificateVerifier) + if !ok { + return nil, h.Dispenser.Errf("module %s is not a caddytls.ClientCertificatVerifier", modID) } - 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.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.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() - } + 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()) + } - 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) - } - - 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_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_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 { + 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) } - - default: - return nil, h.Errf("unknown subdirective for client_auth: %s", subdir) + 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), + ) + } } - } - - case "alpn": - args := h.RemainingArgs() - if len(args) == 0 { - return nil, h.ArgErr() - } - cp.ALPN = args - - case "load": - folderLoader = append(folderLoader, h.RemainingArgs()...) - - 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 "key_type": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() - } - keyType = 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 "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 "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 "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 "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_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 "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 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) } - } - 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_ttl": - arg := h.RemainingArgs() - if len(arg) != 1 { - return nil, h.ArgErr() + default: + return nil, h.Errf("unknown subdirective for client_auth: %s", subdir) } - 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 "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 "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 "on_demand": - if h.NextArg() { - return nil, h.ArgErr() - } - onDemand = true - - case "reuse_private_keys": - if h.NextArg() { - return nil, h.ArgErr() - } - reusePrivateKeys = true - - case "insecure_secrets_log": - if !h.NextArg() { - return nil, h.ArgErr() - } - cp.InsecureSecretsLog = h.Val() - - 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() + case "alpn": + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.ArgErr() + } + cp.ALPN = args + + case "load": + folderLoader = append(folderLoader, h.RemainingArgs()...) + + 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 "key_type": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + keyType = 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 "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 "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 "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 "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_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 "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 "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 "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 "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 "on_demand": + if h.NextArg() { + return nil, h.ArgErr() + } + onDemand = true + + case "reuse_private_keys": + if h.NextArg() { + return nil, h.ArgErr() + } + reusePrivateKeys = true + + case "insecure_secrets_log": + if !h.NextArg() { + return nil, h.ArgErr() + } + cp.InsecureSecretsLog = h.Val() + + 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 [] 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 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 { + // 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() + + // Only a single argument is supported. if h.NextArg() { - logName = h.Val() - - // 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 + 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() - - // Only a single argument is supported. - if h.NextArg() { - return nil, h.ArgErr() - } - } + // 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 } - cl := new(caddy.CustomLog) + // 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() - // 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{} + // Only a single argument is supported. + if h.NextArg() { + return nil, h.ArgErr() + } + } + } - 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...) + cl := new(caddy.CustomLog) - case "output": - if !h.NextArg() { - return nil, h.ArgErr() - } - moduleName := h.Val() + // 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{} - // 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) - - case "level": - if !h.NextArg() { - return nil, h.ArgErr() - } - cl.Level = h.Val() - if h.NextArg() { - return nil, h.ArgErr() - } - - 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 "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()) } - } + cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings) - var val namedCustomLog - val.hostnames = customHostnames - - isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog)) - - // Skip handling of empty logging configs - - if parseAsGlobalOption { - // Use indicated name for global log options - 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 + case "format": + if !h.NextArg() { + return nil, h.ArgErr() } - if val.name != "" { - cl.Include = []string{"http.log.access." + val.name} + 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 "level": + if !h.NextArg() { + return nil, h.ArgErr() + } + cl.Level = h.Val() + if h.NextArg() { + return nil, h.ArgErr() + } + + 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 "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()) } - if !isEmptyConfig { - val.log = cl - } - configValues = append(configValues, ConfigValue{ - Class: "custom_log", - Value: val, - }) } + + var val namedCustomLog + val.hostnames = customHostnames + + isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog)) + + // Skip handling of empty logging configs + + if parseAsGlobalOption { + // Use indicated name for global log options + 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} + } + } + 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 [] 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 58da2bd7..6e5241c7 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 066df301..54e78111 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 ba1896b6..fa447f8d 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) { + d.Next() // consume option name + + // 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() + newOrder := directiveOrder - 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) + // if directive exists, first remove it + for i, d := range newOrder { + if d == dirName { + newOrder = append(newOrder[:i], newOrder[i+1:]...) + break } + } - // 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 - } - } - - // 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) - } - - // get name of other directive - if !d.NextArg() { - return nil, d.ArgErr() - } - otherDir := d.Val() + // 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) + } - // 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 + // 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 } } @@ -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 b5c68218..c57263ba 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 "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 "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 "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()) - } - } - - default: - return nil, d.Errf("unrecognized pki ca option '%s'", 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_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 "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 "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()) + } + } + + 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()) } + + 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 c131a641..62902b96 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") - } - if !d.NextArg() { - return nil, d.ArgErr() - } - serverOpts.Name = d.Val() + 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 - } - listenerWrapper, ok := unm.(caddy.ListenerWrapper) - if !ok { - return nil, fmt.Errorf("module %s (%T) is not a listener wrapper", modID, unm) - } - jsonListenerWrapper := caddyconfig.JSONModuleObject( - listenerWrapper, - "wrapper", - listenerWrapper.(caddy.Module).CaddyModule().ID.Name(), - nil, - ) - serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper) - } - - 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 "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 "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 "enable_full_duplex": - if d.NextArg() { - return nil, d.ArgErr() - } - serverOpts.EnableFullDuplex = true - - case "log_credentials": - if d.NextArg() { - return nil, d.ArgErr() - } - serverOpts.ShouldLogCredentials = true - - 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 "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() + 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 } - source, ok := unm.(caddyhttp.IPRangeSource) + listenerWrapper, ok := unm.(caddy.ListenerWrapper) if !ok { - return nil, fmt.Errorf("module %s (%T) is not an IP range source", modID, unm) + return nil, fmt.Errorf("module %s (%T) is not a listener wrapper", modID, unm) } - jsonSource := caddyconfig.JSONModuleObject( - source, - "source", - source.(caddy.Module).CaddyModule().ID.Name(), + jsonListenerWrapper := caddyconfig.JSONModuleObject( + listenerWrapper, + "wrapper", + listenerWrapper.(caddy.Module).CaddyModule().ID.Name(), nil, ) - serverOpts.TrustedProxiesRaw = jsonSource - - case "trusted_proxies_strict": - if d.NextArg() { - return nil, d.ArgErr() - } - serverOpts.TrustedProxiesStrict = 1 - - 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.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") - - 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()) - } - } - - default: - return nil, d.Errf("unrecognized servers option '%s'", d.Val()) + serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper) } + + 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 "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 "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 "enable_full_duplex": + if d.NextArg() { + return nil, d.ArgErr() + } + serverOpts.EnableFullDuplex = true + + case "log_credentials": + if d.NextArg() { + return nil, d.ArgErr() + } + serverOpts.ShouldLogCredentials = true + + 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 "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 "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.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") + + 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()) + } + } + + default: + return nil, d.Errf("unrecognized servers option '%s'", d.Val()) } } return serverOpts, nil diff --git a/caddytest/integration/caddyfile_adapt/push.txt b/caddytest/integration/caddyfile_adapt/push.txt new file mode 100644 index 00000000..1fe344e0 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/push.txt @@ -0,0 +1,78 @@ +:80 + +push * /foo.txt + +push { + GET /foo.txt +} + +push { + GET /foo.txt + HEAD /foo.txt +} + +push { + headers { + Foo bar + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "handler": "push", + "resources": [ + { + "target": "/foo.txt" + } + ] + }, + { + "handler": "push", + "resources": [ + { + "method": "GET", + "target": "/foo.txt" + } + ] + }, + { + "handler": "push", + "resources": [ + { + "method": "GET", + "target": "/foo.txt" + }, + { + "method": "HEAD", + "target": "/foo.txt" + } + ] + }, + { + "handler": "push", + "headers": { + "set": { + "Foo": [ + "bar" + ] + } + } + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/modules/caddyevents/eventsconfig/caddyfile.go b/modules/caddyevents/eventsconfig/caddyfile.go index 9c3fae78..93a4c3d3 100644 --- a/modules/caddyevents/eventsconfig/caddyfile.go +++ b/modules/caddyevents/eventsconfig/caddyfile.go @@ -40,14 +40,8 @@ func init() { // // If is *, then it will bind to all events. func parseApp(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name app := new(caddyevents.App) - - // consume the option name - if !d.Next() { - return nil, d.ArgErr() - } - - // handle the block for d.NextBlock(0) { switch d.Val() { case "on": diff --git a/modules/caddyhttp/caddyauth/caddyfile.go b/modules/caddyhttp/caddyauth/caddyfile.go index 05c02320..66201dd9 100644 --- a/modules/caddyhttp/caddyauth/caddyfile.go +++ b/modules/caddyhttp/caddyauth/caddyfile.go @@ -34,56 +34,56 @@ func init() { // // If no hash algorithm is supplied, bcrypt will be assumed. func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + var ba HTTPBasicAuth ba.HashCache = new(Cache) - for h.Next() { - var cmp Comparer - args := h.RemainingArgs() + var cmp Comparer + args := h.RemainingArgs() - var hashName string - switch len(args) { - case 0: - hashName = "bcrypt" - case 1: - hashName = args[0] - case 2: - hashName = args[0] - ba.Realm = args[1] - default: + var hashName string + switch len(args) { + case 0: + hashName = "bcrypt" + case 1: + hashName = args[0] + case 2: + hashName = args[0] + ba.Realm = args[1] + default: + return nil, h.ArgErr() + } + + switch hashName { + case "bcrypt": + cmp = BcryptHash{} + case "scrypt": + cmp = ScryptHash{} + default: + return nil, h.Errf("unrecognized hash algorithm: %s", hashName) + } + + ba.HashRaw = caddyconfig.JSONModuleObject(cmp, "algorithm", hashName, nil) + + for h.NextBlock(0) { + username := h.Val() + + var b64Pwd, b64Salt string + h.Args(&b64Pwd, &b64Salt) + if h.NextArg() { return nil, h.ArgErr() } - switch hashName { - case "bcrypt": - cmp = BcryptHash{} - case "scrypt": - cmp = ScryptHash{} - default: - return nil, h.Errf("unrecognized hash algorithm: %s", hashName) + if username == "" || b64Pwd == "" { + return nil, h.Err("username and password cannot be empty or missing") } - ba.HashRaw = caddyconfig.JSONModuleObject(cmp, "algorithm", hashName, nil) - - for h.NextBlock(0) { - username := h.Val() - - var b64Pwd, b64Salt string - h.Args(&b64Pwd, &b64Salt) - if h.NextArg() { - return nil, h.ArgErr() - } - - if username == "" || b64Pwd == "" { - return nil, h.Err("username and password cannot be empty or missing") - } - - ba.AccountList = append(ba.AccountList, Account{ - Username: username, - Password: b64Pwd, - Salt: b64Salt, - }) - } + ba.AccountList = append(ba.AccountList, Account{ + Username: username, + Password: b64Pwd, + Salt: b64Salt, + }) } return Authentication{ diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go index e997336f..65a0e7af 100644 --- a/modules/caddyhttp/celmatcher.go +++ b/modules/caddyhttp/celmatcher.go @@ -176,13 +176,27 @@ func (m MatchExpression) Match(r *http.Request) bool { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.CountRemainingArgs() > 1 { - m.Expr = strings.Join(d.RemainingArgsRaw(), " ") - } else { - m.Expr = d.Val() - } + d.Next() // consume matcher name + + // if there's multiple args, then we need to keep the raw + // tokens because the user may have used quotes within their + // CEL expression (e.g. strings) and we should retain that + if d.CountRemainingArgs() > 1 { + m.Expr = strings.Join(d.RemainingArgsRaw(), " ") + return nil } + + // there should at least be one arg + if !d.NextArg() { + return d.ArgErr() + } + + // if there's only one token, then we can safely grab the + // cleaned token (no quotes) and use that as the expression + // because there's no valid CEL expression that is only a + // quoted string; commonly quotes are used in Caddyfile to + // define the expression + m.Expr = d.Val() return nil } diff --git a/modules/caddyhttp/encode/caddyfile.go b/modules/caddyhttp/encode/caddyfile.go index 25d13f7c..e8ea4b80 100644 --- a/modules/caddyhttp/encode/caddyfile.go +++ b/modules/caddyhttp/encode/caddyfile.go @@ -54,62 +54,60 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) // // Specifying the formats on the first line will use those formats' defaults. func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - var prefer []string + d.Next() // consume directive name + + prefer := []string{} + for _, arg := range d.RemainingArgs() { + mod, err := caddy.GetModule("http.encoders." + arg) + if err != nil { + return d.Errf("finding encoder module '%s': %v", mod, err) + } + encoding, ok := mod.New().(Encoding) + if !ok { + return d.Errf("module %s is not an HTTP encoding", mod) + } + if enc.EncodingsRaw == nil { + enc.EncodingsRaw = make(caddy.ModuleMap) + } + enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil) + prefer = append(prefer, arg) + } responseMatchers := make(map[string]caddyhttp.ResponseMatcher) - - for d.Next() { - for _, arg := range d.RemainingArgs() { - mod, err := caddy.GetModule("http.encoders." + arg) - if err != nil { - return d.Errf("finding encoder module '%s': %v", mod, err) + for d.NextBlock(0) { + switch d.Val() { + case "minimum_length": + if !d.NextArg() { + return d.ArgErr() } - encoding, ok := mod.New().(Encoding) + minLength, err := strconv.Atoi(d.Val()) + if err != nil { + return err + } + enc.MinLength = minLength + case "match": + err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), responseMatchers) + if err != nil { + return err + } + matcher := responseMatchers["match"] + enc.Matcher = &matcher + default: + name := d.Val() + modID := "http.encoders." + name + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + encoding, ok := unm.(Encoding) if !ok { - return d.Errf("module %s is not an HTTP encoding", mod) + return d.Errf("module %s is not an HTTP encoding; is %T", modID, unm) } if enc.EncodingsRaw == nil { enc.EncodingsRaw = make(caddy.ModuleMap) } - enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil) - prefer = append(prefer, arg) - } - - for d.NextBlock(0) { - switch d.Val() { - case "minimum_length": - if !d.NextArg() { - return d.ArgErr() - } - minLength, err := strconv.Atoi(d.Val()) - if err != nil { - return err - } - enc.MinLength = minLength - case "match": - err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), responseMatchers) - if err != nil { - return err - } - matcher := responseMatchers["match"] - enc.Matcher = &matcher - default: - name := d.Val() - modID := "http.encoders." + name - unm, err := caddyfile.UnmarshalModule(d, modID) - if err != nil { - return err - } - encoding, ok := unm.(Encoding) - if !ok { - return d.Errf("module %s is not an HTTP encoding; is %T", modID, unm) - } - if enc.EncodingsRaw == nil { - enc.EncodingsRaw = make(caddy.ModuleMap) - } - enc.EncodingsRaw[name] = caddyconfig.JSON(encoding, nil) - prefer = append(prefer, name) - } + enc.EncodingsRaw[name] = caddyconfig.JSON(encoding, nil) + prefer = append(prefer, name) } } diff --git a/modules/caddyhttp/encode/gzip/gzip.go b/modules/caddyhttp/encode/gzip/gzip.go index 0af38b92..40f37ab8 100644 --- a/modules/caddyhttp/encode/gzip/gzip.go +++ b/modules/caddyhttp/encode/gzip/gzip.go @@ -44,17 +44,16 @@ func (Gzip) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the handler from Caddyfile tokens. func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if !d.NextArg() { - continue - } - levelStr := d.Val() - level, err := strconv.Atoi(levelStr) - if err != nil { - return err - } - g.Level = level + d.Next() // consume option name + if !d.NextArg() { + return nil } + levelStr := d.Val() + level, err := strconv.Atoi(levelStr) + if err != nil { + return err + } + g.Level = level return nil } diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go index 94e75fdd..b18bcdce 100644 --- a/modules/caddyhttp/fileserver/caddyfile.go +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -223,7 +223,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) // parse out the optional try policy var tryPolicy string - for nesting := h.Nesting(); h.NextBlock(nesting); { + for h.NextBlock(0) { switch h.Val() { case "policy": if tryPolicy != "" { diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 310591bc..d4a40b58 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -127,6 +127,7 @@ func (MatchFile) CaddyModule() caddy.ModuleInfo { // try_policy first_exist|smallest_size|largest_size|most_recently_modified // } func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one for d.Next() { m.TryFiles = append(m.TryFiles, d.RemainingArgs()...) for d.NextBlock(0) { diff --git a/modules/caddyhttp/headers/caddyfile.go b/modules/caddyhttp/headers/caddyfile.go index 2b069100..821c0d83 100644 --- a/modules/caddyhttp/headers/caddyfile.go +++ b/modules/caddyhttp/headers/caddyfile.go @@ -47,14 +47,12 @@ func init() { // ? conditionally sets a value only if the header field is not already set, // and > sets a field with defer enabled. func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { - if !h.Next() { - return nil, h.ArgErr() - } - + h.Next() // consume directive name matcherSet, err := h.ExtractMatcherSet() if err != nil { return nil, err } + h.Next() // consume the directive name again (matcher parsing resets) makeHandler := func() Handler { return Handler{ @@ -65,73 +63,71 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) } handler, handlerWithRequire := makeHandler(), makeHandler() - for h.Next() { - // first see if headers are in the initial line - var hasArgs bool + // first see if headers are in the initial line + var hasArgs bool + if h.NextArg() { + hasArgs = true + field := h.Val() + var value, replacement string if h.NextArg() { - hasArgs = true - field := h.Val() - var value, replacement string - if h.NextArg() { - value = h.Val() - } - if h.NextArg() { - replacement = h.Val() - } - err := applyHeaderOp( - handler.Response.HeaderOps, - handler.Response, - field, - value, - replacement, - ) - if err != nil { - return nil, h.Err(err.Error()) - } - if len(handler.Response.HeaderOps.Delete) > 0 { - handler.Response.Deferred = true - } + value = h.Val() + } + if h.NextArg() { + replacement = h.Val() + } + err := applyHeaderOp( + handler.Response.HeaderOps, + handler.Response, + field, + value, + replacement, + ) + if err != nil { + return nil, h.Err(err.Error()) + } + if len(handler.Response.HeaderOps.Delete) > 0 { + handler.Response.Deferred = true + } + } + + // if not, they should be in a block + for h.NextBlock(0) { + field := h.Val() + if field == "defer" { + handler.Response.Deferred = true + continue + } + if hasArgs { + return nil, h.Err("cannot specify headers in both arguments and block") // because it would be weird } - // if not, they should be in a block - for h.NextBlock(0) { - field := h.Val() - if field == "defer" { - handler.Response.Deferred = true - continue - } - if hasArgs { - return nil, h.Err("cannot specify headers in both arguments and block") // because it would be weird - } + // sometimes it is habitual for users to suffix a field name with a colon, + // as if they were writing a curl command or something; see + // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 + field = strings.TrimSuffix(field, ":") - // sometimes it is habitual for users to suffix a field name with a colon, - // as if they were writing a curl command or something; see - // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 - field = strings.TrimSuffix(field, ":") + var value, replacement string + if h.NextArg() { + value = h.Val() + } + if h.NextArg() { + replacement = h.Val() + } - var value, replacement string - if h.NextArg() { - value = h.Val() - } - if h.NextArg() { - replacement = h.Val() - } + handlerToUse := handler + if strings.HasPrefix(field, "?") { + handlerToUse = handlerWithRequire + } - handlerToUse := handler - if strings.HasPrefix(field, "?") { - handlerToUse = handlerWithRequire - } - - err := applyHeaderOp( - handlerToUse.Response.HeaderOps, - handlerToUse.Response, - field, - value, - replacement, - ) - if err != nil { - return nil, h.Err(err.Error()) - } + err := applyHeaderOp( + handlerToUse.Response.HeaderOps, + handlerToUse.Response, + field, + value, + replacement, + ) + if err != nil { + return nil, h.Err(err.Error()) } } @@ -151,56 +147,52 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) // // request_header [] [[+|-] [] []] func parseReqHdrCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { - if !h.Next() { - return nil, h.ArgErr() - } - + h.Next() // consume directive name matcherSet, err := h.ExtractMatcherSet() if err != nil { return nil, err } + h.Next() // consume the directive name again (matcher parsing resets) configValues := []httpcaddyfile.ConfigValue{} - for h.Next() { - if !h.NextArg() { - return nil, h.ArgErr() - } - field := h.Val() + if !h.NextArg() { + return nil, h.ArgErr() + } + field := h.Val() - hdr := Handler{ - Request: &HeaderOps{}, - } + hdr := Handler{ + Request: &HeaderOps{}, + } - // sometimes it is habitual for users to suffix a field name with a colon, - // as if they were writing a curl command or something; see - // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 - field = strings.TrimSuffix(field, ":") - - var value, replacement string - if h.NextArg() { - value = h.Val() - } - if h.NextArg() { - replacement = h.Val() - if h.NextArg() { - return nil, h.ArgErr() - } - } - - if hdr.Request == nil { - hdr.Request = new(HeaderOps) - } - if err := CaddyfileHeaderOp(hdr.Request, field, value, replacement); err != nil { - return nil, h.Err(err.Error()) - } - - configValues = append(configValues, h.NewRoute(matcherSet, hdr)...) + // sometimes it is habitual for users to suffix a field name with a colon, + // as if they were writing a curl command or something; see + // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 + field = strings.TrimSuffix(field, ":") + var value, replacement string + if h.NextArg() { + value = h.Val() + } + if h.NextArg() { + replacement = h.Val() if h.NextArg() { return nil, h.ArgErr() } } + + if hdr.Request == nil { + hdr.Request = new(HeaderOps) + } + if err := CaddyfileHeaderOp(hdr.Request, field, value, replacement); err != nil { + return nil, h.Err(err.Error()) + } + + configValues = append(configValues, h.NewRoute(matcherSet, hdr)...) + + if h.NextArg() { + return nil, h.ArgErr() + } return configValues, nil } diff --git a/modules/caddyhttp/ip_matchers.go b/modules/caddyhttp/ip_matchers.go index 57a22957..30be6120 100644 --- a/modules/caddyhttp/ip_matchers.go +++ b/modules/caddyhttp/ip_matchers.go @@ -79,24 +79,23 @@ func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextArg() { - if d.Val() == "forwarded" { - if len(m.Ranges) > 0 { - return d.Err("if used, 'forwarded' must be first argument") - } - m.Forwarded = true - continue + d.Next() // consume matcher name + for d.NextArg() { + if d.Val() == "forwarded" { + if len(m.Ranges) > 0 { + return d.Err("if used, 'forwarded' must be first argument") } - if d.Val() == "private_ranges" { - m.Ranges = append(m.Ranges, PrivateRangesCIDR()...) - continue - } - m.Ranges = append(m.Ranges, d.Val()) + m.Forwarded = true + continue } - if d.NextBlock(0) { - return d.Err("malformed remote_ip matcher: blocks are not supported") + if d.Val() == "private_ranges" { + m.Ranges = append(m.Ranges, PrivateRangesCIDR()...) + continue } + m.Ranges = append(m.Ranges, d.Val()) + } + if d.NextBlock(0) { + return d.Err("malformed remote_ip matcher: blocks are not supported") } return nil } @@ -189,17 +188,16 @@ func (MatchClientIP) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextArg() { - if d.Val() == "private_ranges" { - m.Ranges = append(m.Ranges, PrivateRangesCIDR()...) - continue - } - m.Ranges = append(m.Ranges, d.Val()) - } - if d.NextBlock(0) { - return d.Err("malformed client_ip matcher: blocks are not supported") + d.Next() // consume matcher name + for d.NextArg() { + if d.Val() == "private_ranges" { + m.Ranges = append(m.Ranges, PrivateRangesCIDR()...) + continue } + m.Ranges = append(m.Ranges, d.Val()) + } + if d.NextBlock(0) { + return d.Err("malformed client_ip matcher: blocks are not supported") } return nil } diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go index 9cc7d8c6..8f7b5d34 100644 --- a/modules/caddyhttp/map/caddyfile.go +++ b/modules/caddyhttp/map/caddyfile.go @@ -42,74 +42,73 @@ func init() { // However, for convenience, there may be fewer outputs than destinations and any missing // outputs will be filled in implicitly. func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + var handler Handler - for h.Next() { - // source - if !h.NextArg() { - return nil, h.ArgErr() - } - handler.Source = h.Val() + // source + if !h.NextArg() { + return nil, h.ArgErr() + } + handler.Source = h.Val() - // destinations - handler.Destinations = h.RemainingArgs() - if len(handler.Destinations) == 0 { - return nil, h.Err("missing destination argument(s)") - } - for _, dest := range handler.Destinations { - if shorthand := httpcaddyfile.WasReplacedPlaceholderShorthand(dest); shorthand != "" { - return nil, h.Errf("destination %s conflicts with a Caddyfile placeholder shorthand", shorthand) - } - } - - // mappings - for h.NextBlock(0) { - // defaults are a special case - if h.Val() == "default" { - if len(handler.Defaults) > 0 { - return nil, h.Err("defaults already defined") - } - handler.Defaults = h.RemainingArgs() - for len(handler.Defaults) < len(handler.Destinations) { - handler.Defaults = append(handler.Defaults, "") - } - continue - } - - // every line maps an input value to one or more outputs - in := h.Val() - var outs []any - for h.NextArg() { - val := h.ScalarVal() - if val == "-" { - outs = append(outs, nil) - } else { - outs = append(outs, val) - } - } - - // cannot have more outputs than destinations - if len(outs) > len(handler.Destinations) { - return nil, h.Err("too many outputs") - } - - // for convenience, can have fewer outputs than destinations, but the - // underlying handler won't accept that, so we fill in nil values - for len(outs) < len(handler.Destinations) { - outs = append(outs, nil) - } - - // create the mapping - mapping := Mapping{Outputs: outs} - if strings.HasPrefix(in, "~") { - mapping.InputRegexp = in[1:] - } else { - mapping.Input = in - } - - handler.Mappings = append(handler.Mappings, mapping) + // destinations + handler.Destinations = h.RemainingArgs() + if len(handler.Destinations) == 0 { + return nil, h.Err("missing destination argument(s)") + } + for _, dest := range handler.Destinations { + if shorthand := httpcaddyfile.WasReplacedPlaceholderShorthand(dest); shorthand != "" { + return nil, h.Errf("destination %s conflicts with a Caddyfile placeholder shorthand", shorthand) } } + // mappings + for h.NextBlock(0) { + // defaults are a special case + if h.Val() == "default" { + if len(handler.Defaults) > 0 { + return nil, h.Err("defaults already defined") + } + handler.Defaults = h.RemainingArgs() + for len(handler.Defaults) < len(handler.Destinations) { + handler.Defaults = append(handler.Defaults, "") + } + continue + } + + // every line maps an input value to one or more outputs + in := h.Val() + var outs []any + for h.NextArg() { + val := h.ScalarVal() + if val == "-" { + outs = append(outs, nil) + } else { + outs = append(outs, val) + } + } + + // cannot have more outputs than destinations + if len(outs) > len(handler.Destinations) { + return nil, h.Err("too many outputs") + } + + // for convenience, can have fewer outputs than destinations, but the + // underlying handler won't accept that, so we fill in nil values + for len(outs) < len(handler.Destinations) { + outs = append(outs, nil) + } + + // create the mapping + mapping := Mapping{Outputs: outs} + if strings.HasPrefix(in, "~") { + mapping.InputRegexp = in[1:] + } else { + mapping.Input = in + } + + handler.Mappings = append(handler.Mappings, mapping) + } return handler, nil } diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 93211b22..f01f187f 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -225,6 +225,7 @@ func (MatchHost) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchHost) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one for d.Next() { *m = append(*m, d.RemainingArgs()...) if d.NextBlock(0) { @@ -632,6 +633,7 @@ func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one for d.Next() { *m = append(*m, d.RemainingArgs()...) if d.NextBlock(0) { @@ -716,6 +718,7 @@ func (MatchMethod) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one for d.Next() { *m = append(*m, d.RemainingArgs()...) if d.NextBlock(0) { @@ -770,6 +773,7 @@ func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if *m == nil { *m = make(map[string][]string) } + // iterate to merge multiple matchers into one for d.Next() { for _, query := range d.RemainingArgs() { if query == "" { @@ -868,6 +872,7 @@ func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if *m == nil { *m = make(map[string][]string) } + // iterate to merge multiple matchers into one for d.Next() { var field, val string if !d.Args(&field) { @@ -1002,6 +1007,7 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if *m == nil { *m = make(map[string]*MatchRegexp) } + // iterate to merge multiple matchers into one for d.Next() { var first, second, third string if !d.Args(&first, &second) { @@ -1166,6 +1172,7 @@ func (m MatchProtocol) Match(r *http.Request) bool { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one for d.Next() { var proto string if !d.Args(&proto) { @@ -1207,6 +1214,7 @@ func (MatchNot) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one for d.Next() { matcherSet, err := ParseCaddyfileNestedMatcherSet(d) if err != nil { @@ -1331,6 +1339,7 @@ func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one for d.Next() { // If this is the second iteration of the loop // then there's more than one path_regexp matcher diff --git a/modules/caddyhttp/proxyprotocol/module.go b/modules/caddyhttp/proxyprotocol/module.go index c6dc283e..75a156a2 100644 --- a/modules/caddyhttp/proxyprotocol/module.go +++ b/modules/caddyhttp/proxyprotocol/module.go @@ -39,40 +39,40 @@ func (ListenerWrapper) CaddyModule() caddy.ModuleInfo { // fallback_policy // } func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - // No same-line options are supported - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume wrapper name - for d.NextBlock(0) { - switch d.Val() { - case "timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("parsing proxy_protocol timeout duration: %v", err) - } - w.Timeout = caddy.Duration(dur) + // No same-line options are supported + if d.NextArg() { + return d.ArgErr() + } - case "allow": - w.Allow = append(w.Allow, d.RemainingArgs()...) - case "deny": - w.Deny = append(w.Deny, d.RemainingArgs()...) - case "fallback_policy": - if !d.NextArg() { - return d.ArgErr() - } - p, err := parsePolicy(d.Val()) - if err != nil { - return d.WrapErr(err) - } - w.FallbackPolicy = p - default: + for d.NextBlock(0) { + switch d.Val() { + case "timeout": + if !d.NextArg() { return d.ArgErr() } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("parsing proxy_protocol timeout duration: %v", err) + } + w.Timeout = caddy.Duration(dur) + + case "allow": + w.Allow = append(w.Allow, d.RemainingArgs()...) + case "deny": + w.Deny = append(w.Deny, d.RemainingArgs()...) + case "fallback_policy": + if !d.NextArg() { + return d.ArgErr() + } + p, err := parsePolicy(d.Val()) + if err != nil { + return d.WrapErr(err) + } + w.FallbackPolicy = p + default: + return d.ArgErr() } } return nil diff --git a/modules/caddyhttp/push/caddyfile.go b/modules/caddyhttp/push/caddyfile.go index 963117c1..07f6ccea 100644 --- a/modules/caddyhttp/push/caddyfile.go +++ b/modules/caddyhttp/push/caddyfile.go @@ -44,63 +44,63 @@ func init() { // Placeholders are accepted in resource and header field // name and value and replacement tokens. func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + handler := new(Handler) - for h.Next() { - if h.NextArg() { - handler.Resources = append(handler.Resources, Resource{Target: h.Val()}) - } - - // optional block - for outerNesting := h.Nesting(); h.NextBlock(outerNesting); { - switch h.Val() { - case "headers": - if h.NextArg() { - return nil, h.ArgErr() - } - for innerNesting := h.Nesting(); h.NextBlock(innerNesting); { - var err error - - // include current token, which we treat as an argument here - args := []string{h.Val()} - args = append(args, h.RemainingArgs()...) - - if handler.Headers == nil { - handler.Headers = new(HeaderConfig) - } - - switch len(args) { - case 1: - err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], "", "") - case 2: - err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], "") - case 3: - err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], args[2]) - default: - return nil, h.ArgErr() - } - - if err != nil { - return nil, h.Err(err.Error()) - } - } - - case "GET", "HEAD": - method := h.Val() - if !h.NextArg() { - return nil, h.ArgErr() - } - target := h.Val() - handler.Resources = append(handler.Resources, Resource{ - Method: method, - Target: target, - }) - - default: - handler.Resources = append(handler.Resources, Resource{Target: h.Val()}) - } - } + // inline resources + if h.NextArg() { + handler.Resources = append(handler.Resources, Resource{Target: h.Val()}) } + // optional block + for h.NextBlock(0) { + switch h.Val() { + case "headers": + if h.NextArg() { + return nil, h.ArgErr() + } + for nesting := h.Nesting(); h.NextBlock(nesting); { + var err error + + // include current token, which we treat as an argument here + args := []string{h.Val()} + args = append(args, h.RemainingArgs()...) + + if handler.Headers == nil { + handler.Headers = new(HeaderConfig) + } + + switch len(args) { + case 1: + err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], "", "") + case 2: + err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], "") + case 3: + err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], args[2]) + default: + return nil, h.ArgErr() + } + + if err != nil { + return nil, h.Err(err.Error()) + } + } + + case "GET", "HEAD": + method := h.Val() + if !h.NextArg() { + return nil, h.ArgErr() + } + target := h.Val() + handler.Resources = append(handler.Resources, Resource{ + Method: method, + Target: target, + }) + + default: + handler.Resources = append(handler.Resources, Resource{Target: h.Val()}) + } + } return handler, nil } diff --git a/modules/caddyhttp/requestbody/caddyfile.go b/modules/caddyhttp/requestbody/caddyfile.go index 8a92909f..d2956cae 100644 --- a/modules/caddyhttp/requestbody/caddyfile.go +++ b/modules/caddyhttp/requestbody/caddyfile.go @@ -26,25 +26,26 @@ func init() { } func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + rb := new(RequestBody) - for h.Next() { - // configuration should be in a block - for h.NextBlock(0) { - switch h.Val() { - case "max_size": - var sizeStr string - if !h.AllArgs(&sizeStr) { - return nil, h.ArgErr() - } - size, err := humanize.ParseBytes(sizeStr) - if err != nil { - return nil, h.Errf("parsing max_size: %v", err) - } - rb.MaxSize = int64(size) - default: - return nil, h.Errf("unrecognized servers option '%s'", h.Val()) + // configuration should be in a block + for h.NextBlock(0) { + switch h.Val() { + case "max_size": + var sizeStr string + if !h.AllArgs(&sizeStr) { + return nil, h.ArgErr() } + size, err := humanize.ParseBytes(sizeStr) + if err != nil { + return nil, h.Errf("parsing max_size: %v", err) + } + rb.MaxSize = int64(size) + + default: + return nil, h.Errf("unrecognized servers option '%s'", h.Val()) } } diff --git a/modules/caddyhttp/responsematchers.go b/modules/caddyhttp/responsematchers.go index 6bac7800..cf96b00c 100644 --- a/modules/caddyhttp/responsematchers.go +++ b/modules/caddyhttp/responsematchers.go @@ -67,55 +67,53 @@ func (rm ResponseMatcher) matchStatusCode(statusCode int) bool { // // @name [header []] | [status ] func ParseNamedResponseMatcher(d *caddyfile.Dispenser, matchers map[string]ResponseMatcher) error { - for d.Next() { - definitionName := d.Val() + d.Next() // consume matcher name + definitionName := d.Val() - if _, ok := matchers[definitionName]; ok { - return d.Errf("matcher is defined more than once: %s", definitionName) - } - - matcher := ResponseMatcher{} - for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { - switch d.Val() { - case "header": - if matcher.Headers == nil { - matcher.Headers = http.Header{} - } - - // reuse the header request matcher's unmarshaler - headerMatcher := MatchHeader(matcher.Headers) - err := headerMatcher.UnmarshalCaddyfile(d.NewFromNextSegment()) - if err != nil { - return err - } - - matcher.Headers = http.Header(headerMatcher) - case "status": - if matcher.StatusCode == nil { - matcher.StatusCode = []int{} - } - - args := d.RemainingArgs() - if len(args) == 0 { - return d.ArgErr() - } - - for _, arg := range args { - if len(arg) == 3 && strings.HasSuffix(arg, "xx") { - arg = arg[:1] - } - statusNum, err := strconv.Atoi(arg) - if err != nil { - return d.Errf("bad status value '%s': %v", arg, err) - } - matcher.StatusCode = append(matcher.StatusCode, statusNum) - } - default: - return d.Errf("unrecognized response matcher %s", d.Val()) - } - } - - matchers[definitionName] = matcher + if _, ok := matchers[definitionName]; ok { + return d.Errf("matcher is defined more than once: %s", definitionName) } + + matcher := ResponseMatcher{} + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + switch d.Val() { + case "header": + if matcher.Headers == nil { + matcher.Headers = http.Header{} + } + + // reuse the header request matcher's unmarshaler + headerMatcher := MatchHeader(matcher.Headers) + err := headerMatcher.UnmarshalCaddyfile(d.NewFromNextSegment()) + if err != nil { + return err + } + + matcher.Headers = http.Header(headerMatcher) + case "status": + if matcher.StatusCode == nil { + matcher.StatusCode = []int{} + } + + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + + for _, arg := range args { + if len(arg) == 3 && strings.HasSuffix(arg, "xx") { + arg = arg[:1] + } + statusNum, err := strconv.Atoi(arg) + if err != nil { + return d.Errf("bad status value '%s': %v", arg, err) + } + matcher.StatusCode = append(matcher.StatusCode, statusNum) + } + default: + return d.Errf("unrecognized response matcher %s", d.Val()) + } + } + matchers[definitionName] = matcher return nil } diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go index 4e01c7a1..42fd6f99 100644 --- a/modules/caddyhttp/reverseproxy/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -210,7 +210,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } } - for nesting := d.Nesting(); d.NextBlock(nesting); { + for d.NextBlock(0) { // if the subdirective has an "@" prefix then we // parse it as a response matcher for use with "handle_response" if strings.HasPrefix(d.Val(), matcherPrefix) { @@ -774,7 +774,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } // make sure there's no block, cause it doesn't make sense - if d.NextBlock(1) { + if nesting := d.Nesting(); d.NextBlock(nesting) { return d.Errf("cannot define routes for 'replace_status', use 'handle_response' instead.") } @@ -930,297 +930,296 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error // max_idle_conns_per_host // } func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextBlock(0) { - switch d.Val() { - case "read_buffer": - if !d.NextArg() { - return d.ArgErr() - } - size, err := humanize.ParseBytes(d.Val()) - if err != nil { - return d.Errf("invalid read buffer size '%s': %v", d.Val(), err) - } - h.ReadBufferSize = int(size) + d.Next() // consume transport name + for d.NextBlock(0) { + switch d.Val() { + case "read_buffer": + if !d.NextArg() { + return d.ArgErr() + } + size, err := humanize.ParseBytes(d.Val()) + if err != nil { + return d.Errf("invalid read buffer size '%s': %v", d.Val(), err) + } + h.ReadBufferSize = int(size) - case "write_buffer": - if !d.NextArg() { - return d.ArgErr() - } - size, err := humanize.ParseBytes(d.Val()) - if err != nil { - return d.Errf("invalid write buffer size '%s': %v", d.Val(), err) - } - h.WriteBufferSize = int(size) + case "write_buffer": + if !d.NextArg() { + return d.ArgErr() + } + size, err := humanize.ParseBytes(d.Val()) + if err != nil { + return d.Errf("invalid write buffer size '%s': %v", d.Val(), err) + } + h.WriteBufferSize = int(size) - case "read_timeout": - if !d.NextArg() { - return d.ArgErr() - } - timeout, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("invalid read timeout duration '%s': %v", d.Val(), err) - } - h.ReadTimeout = caddy.Duration(timeout) + case "read_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeout, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid read timeout duration '%s': %v", d.Val(), err) + } + h.ReadTimeout = caddy.Duration(timeout) - case "write_timeout": - if !d.NextArg() { - return d.ArgErr() - } - timeout, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("invalid write timeout duration '%s': %v", d.Val(), err) - } - h.WriteTimeout = caddy.Duration(timeout) + case "write_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeout, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid write timeout duration '%s': %v", d.Val(), err) + } + h.WriteTimeout = caddy.Duration(timeout) - case "max_response_header": - if !d.NextArg() { - return d.ArgErr() - } - size, err := humanize.ParseBytes(d.Val()) - if err != nil { - return d.Errf("invalid max response header size '%s': %v", d.Val(), err) - } - h.MaxResponseHeaderSize = int64(size) + case "max_response_header": + if !d.NextArg() { + return d.ArgErr() + } + size, err := humanize.ParseBytes(d.Val()) + if err != nil { + return d.Errf("invalid max response header size '%s': %v", d.Val(), err) + } + h.MaxResponseHeaderSize = int64(size) - case "proxy_protocol": - if !d.NextArg() { - return d.ArgErr() - } - switch proxyProtocol := d.Val(); proxyProtocol { - case "v1", "v2": - h.ProxyProtocol = proxyProtocol - default: - return d.Errf("invalid proxy protocol version '%s'", proxyProtocol) - } + case "proxy_protocol": + if !d.NextArg() { + return d.ArgErr() + } + switch proxyProtocol := d.Val(); proxyProtocol { + case "v1", "v2": + h.ProxyProtocol = proxyProtocol + default: + return d.Errf("invalid proxy protocol version '%s'", proxyProtocol) + } - case "dial_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value '%s': %v", d.Val(), err) - } - h.DialTimeout = caddy.Duration(dur) + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + h.DialTimeout = caddy.Duration(dur) - case "dial_fallback_delay": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad fallback delay value '%s': %v", d.Val(), err) - } - h.FallbackDelay = caddy.Duration(dur) + case "dial_fallback_delay": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad fallback delay value '%s': %v", d.Val(), err) + } + h.FallbackDelay = caddy.Duration(dur) - case "response_header_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value '%s': %v", d.Val(), err) - } - h.ResponseHeaderTimeout = caddy.Duration(dur) + case "response_header_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + h.ResponseHeaderTimeout = caddy.Duration(dur) - case "expect_continue_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value '%s': %v", d.Val(), err) - } - h.ExpectContinueTimeout = caddy.Duration(dur) + case "expect_continue_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + h.ExpectContinueTimeout = caddy.Duration(dur) - case "resolvers": - if h.Resolver == nil { - h.Resolver = new(UpstreamResolver) - } - h.Resolver.Addresses = d.RemainingArgs() - if len(h.Resolver.Addresses) == 0 { - return d.Errf("must specify at least one resolver address") - } + case "resolvers": + if h.Resolver == nil { + h.Resolver = new(UpstreamResolver) + } + h.Resolver.Addresses = d.RemainingArgs() + if len(h.Resolver.Addresses) == 0 { + return d.Errf("must specify at least one resolver address") + } - case "tls": - if h.TLS == nil { - h.TLS = new(TLSConfig) - } + case "tls": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } - case "tls_client_auth": - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - args := d.RemainingArgs() - switch len(args) { - case 1: - h.TLS.ClientCertificateAutomate = args[0] - case 2: - h.TLS.ClientCertificateFile = args[0] - h.TLS.ClientCertificateKeyFile = args[1] - default: - return d.ArgErr() - } + case "tls_client_auth": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + args := d.RemainingArgs() + switch len(args) { + case 1: + h.TLS.ClientCertificateAutomate = args[0] + case 2: + h.TLS.ClientCertificateFile = args[0] + h.TLS.ClientCertificateKeyFile = args[1] + default: + return d.ArgErr() + } - case "tls_insecure_skip_verify": - if d.NextArg() { - return d.ArgErr() - } - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - h.TLS.InsecureSkipVerify = true + case "tls_insecure_skip_verify": + if d.NextArg() { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.InsecureSkipVerify = true - case "tls_curves": - args := d.RemainingArgs() - if len(args) == 0 { - return d.ArgErr() - } - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - h.TLS.Curves = args + case "tls_curves": + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.Curves = args - case "tls_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value '%s': %v", d.Val(), err) - } - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - h.TLS.HandshakeTimeout = caddy.Duration(dur) + case "tls_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.HandshakeTimeout = caddy.Duration(dur) - case "tls_trusted_ca_certs": - args := d.RemainingArgs() - if len(args) == 0 { - return d.ArgErr() - } - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - h.TLS.RootCAPEMFiles = args + case "tls_trusted_ca_certs": + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.RootCAPEMFiles = args - case "tls_server_name": - if !d.NextArg() { - return d.ArgErr() - } - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - h.TLS.ServerName = d.Val() + case "tls_server_name": + if !d.NextArg() { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.ServerName = d.Val() - case "tls_renegotiation": - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - if !d.NextArg() { - return d.ArgErr() - } - switch renegotiation := d.Val(); renegotiation { - case "never", "once", "freely": - h.TLS.Renegotiation = renegotiation - default: - return d.ArgErr() - } + case "tls_renegotiation": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + if !d.NextArg() { + return d.ArgErr() + } + switch renegotiation := d.Val(); renegotiation { + case "never", "once", "freely": + h.TLS.Renegotiation = renegotiation + default: + return d.ArgErr() + } - case "tls_except_ports": - if h.TLS == nil { - h.TLS = new(TLSConfig) - } - h.TLS.ExceptPorts = d.RemainingArgs() - if len(h.TLS.ExceptPorts) == 0 { - return d.ArgErr() - } + case "tls_except_ports": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.ExceptPorts = d.RemainingArgs() + if len(h.TLS.ExceptPorts) == 0 { + return d.ArgErr() + } - case "keepalive": - if !d.NextArg() { - return d.ArgErr() - } - if h.KeepAlive == nil { - h.KeepAlive = new(KeepAlive) - } + case "keepalive": + if !d.NextArg() { + return d.ArgErr() + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + if d.Val() == "off" { + var disable bool + h.KeepAlive.Enabled = &disable + break + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value '%s': %v", d.Val(), err) + } + h.KeepAlive.IdleConnTimeout = caddy.Duration(dur) + + case "keepalive_interval": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad interval value '%s': %v", d.Val(), err) + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + h.KeepAlive.ProbeInterval = caddy.Duration(dur) + + case "keepalive_idle_conns": + if !d.NextArg() { + return d.ArgErr() + } + num, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad integer value '%s': %v", d.Val(), err) + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + h.KeepAlive.MaxIdleConns = num + + case "keepalive_idle_conns_per_host": + if !d.NextArg() { + return d.ArgErr() + } + num, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad integer value '%s': %v", d.Val(), err) + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + h.KeepAlive.MaxIdleConnsPerHost = num + + case "versions": + h.Versions = d.RemainingArgs() + if len(h.Versions) == 0 { + return d.ArgErr() + } + + case "compression": + if d.NextArg() { if d.Val() == "off" { var disable bool - h.KeepAlive.Enabled = &disable - break + h.Compression = &disable } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad duration value '%s': %v", d.Val(), err) - } - h.KeepAlive.IdleConnTimeout = caddy.Duration(dur) - - case "keepalive_interval": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad interval value '%s': %v", d.Val(), err) - } - if h.KeepAlive == nil { - h.KeepAlive = new(KeepAlive) - } - h.KeepAlive.ProbeInterval = caddy.Duration(dur) - - case "keepalive_idle_conns": - if !d.NextArg() { - return d.ArgErr() - } - num, err := strconv.Atoi(d.Val()) - if err != nil { - return d.Errf("bad integer value '%s': %v", d.Val(), err) - } - if h.KeepAlive == nil { - h.KeepAlive = new(KeepAlive) - } - h.KeepAlive.MaxIdleConns = num - - case "keepalive_idle_conns_per_host": - if !d.NextArg() { - return d.ArgErr() - } - num, err := strconv.Atoi(d.Val()) - if err != nil { - return d.Errf("bad integer value '%s': %v", d.Val(), err) - } - if h.KeepAlive == nil { - h.KeepAlive = new(KeepAlive) - } - h.KeepAlive.MaxIdleConnsPerHost = num - - case "versions": - h.Versions = d.RemainingArgs() - if len(h.Versions) == 0 { - return d.ArgErr() - } - - case "compression": - if d.NextArg() { - if d.Val() == "off" { - var disable bool - h.Compression = &disable - } - } - - case "max_conns_per_host": - if !d.NextArg() { - return d.ArgErr() - } - num, err := strconv.Atoi(d.Val()) - if err != nil { - return d.Errf("bad integer value '%s': %v", d.Val(), err) - } - h.MaxConnsPerHost = num - - default: - return d.Errf("unrecognized subdirective %s", d.Val()) } + + case "max_conns_per_host": + if !d.NextArg() { + return d.ArgErr() + } + num, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad integer value '%s': %v", d.Val(), err) + } + h.MaxConnsPerHost = num + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) } } return nil @@ -1241,25 +1240,25 @@ func parseCopyResponseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHan // status // } func (h *CopyResponseHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - if len(args) == 1 { - if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { - h.StatusCode = caddyhttp.WeakString(args[0]) - break - } - } + d.Next() // consume directive name - for d.NextBlock(0) { - switch d.Val() { - case "status": - if !d.NextArg() { - return d.ArgErr() - } - h.StatusCode = caddyhttp.WeakString(d.Val()) - default: - return d.Errf("unrecognized subdirective '%s'", d.Val()) + args := d.RemainingArgs() + if len(args) == 1 { + if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { + h.StatusCode = caddyhttp.WeakString(args[0]) + return nil + } + } + + for d.NextBlock(0) { + switch d.Val() { + case "status": + if !d.NextArg() { + return d.ArgErr() } + h.StatusCode = caddyhttp.WeakString(d.Val()) + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) } } return nil @@ -1281,23 +1280,23 @@ func parseCopyResponseHeadersCaddyfile(h httpcaddyfile.Helper) (caddyhttp.Middle // exclude // } func (h *CopyResponseHeadersHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - if len(args) > 0 { - return d.ArgErr() - } + d.Next() // consume directive name - for d.NextBlock(0) { - switch d.Val() { - case "include": - h.Include = append(h.Include, d.RemainingArgs()...) + args := d.RemainingArgs() + if len(args) > 0 { + return d.ArgErr() + } - case "exclude": - h.Exclude = append(h.Exclude, d.RemainingArgs()...) + for d.NextBlock(0) { + switch d.Val() { + case "include": + h.Include = append(h.Include, d.RemainingArgs()...) - default: - return d.Errf("unrecognized subdirective '%s'", d.Val()) - } + case "exclude": + h.Exclude = append(h.Exclude, d.RemainingArgs()...) + + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) } } return nil @@ -1315,89 +1314,88 @@ func (h *CopyResponseHeadersHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) // dial_fallback_delay // } func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - if len(args) > 1 { - return d.ArgErr() - } - if len(args) > 0 { - u.Name = args[0] - } + d.Next() // consume upstream source name - for d.NextBlock(0) { - switch d.Val() { - case "service": - if !d.NextArg() { - return d.ArgErr() - } - if u.Service != "" { - return d.Errf("srv service has already been specified") - } - u.Service = d.Val() - - case "proto": - if !d.NextArg() { - return d.ArgErr() - } - if u.Proto != "" { - return d.Errf("srv proto has already been specified") - } - u.Proto = d.Val() - - case "name": - if !d.NextArg() { - return d.ArgErr() - } - if u.Name != "" { - return d.Errf("srv name has already been specified") - } - u.Name = d.Val() - - case "refresh": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("parsing refresh interval duration: %v", err) - } - u.Refresh = caddy.Duration(dur) - - case "resolvers": - if u.Resolver == nil { - u.Resolver = new(UpstreamResolver) - } - u.Resolver.Addresses = d.RemainingArgs() - if len(u.Resolver.Addresses) == 0 { - return d.Errf("must specify at least one resolver address") - } - - case "dial_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value '%s': %v", d.Val(), err) - } - u.DialTimeout = caddy.Duration(dur) - - case "dial_fallback_delay": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad delay value '%s': %v", d.Val(), err) - } - u.FallbackDelay = caddy.Duration(dur) - - default: - return d.Errf("unrecognized srv option '%s'", d.Val()) - } - } + args := d.RemainingArgs() + if len(args) > 1 { + return d.ArgErr() + } + if len(args) > 0 { + u.Name = args[0] } + for d.NextBlock(0) { + switch d.Val() { + case "service": + if !d.NextArg() { + return d.ArgErr() + } + if u.Service != "" { + return d.Errf("srv service has already been specified") + } + u.Service = d.Val() + + case "proto": + if !d.NextArg() { + return d.ArgErr() + } + if u.Proto != "" { + return d.Errf("srv proto has already been specified") + } + u.Proto = d.Val() + + case "name": + if !d.NextArg() { + return d.ArgErr() + } + if u.Name != "" { + return d.Errf("srv name has already been specified") + } + u.Name = d.Val() + + case "refresh": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("parsing refresh interval duration: %v", err) + } + u.Refresh = caddy.Duration(dur) + + case "resolvers": + if u.Resolver == nil { + u.Resolver = new(UpstreamResolver) + } + u.Resolver.Addresses = d.RemainingArgs() + if len(u.Resolver.Addresses) == 0 { + return d.Errf("must specify at least one resolver address") + } + + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + u.DialTimeout = caddy.Duration(dur) + + case "dial_fallback_delay": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad delay value '%s': %v", d.Val(), err) + } + u.FallbackDelay = caddy.Duration(dur) + + default: + return d.Errf("unrecognized srv option '%s'", d.Val()) + } + } return nil } @@ -1413,105 +1411,104 @@ func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // versions ipv4|ipv6 // } func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - if len(args) > 2 { - return d.ArgErr() - } - if len(args) > 0 { - u.Name = args[0] - if len(args) == 2 { - u.Port = args[1] - } - } + d.Next() // consume upstream source name - for d.NextBlock(0) { - switch d.Val() { - case "name": - if !d.NextArg() { - return d.ArgErr() - } - if u.Name != "" { - return d.Errf("a name has already been specified") - } - u.Name = d.Val() - - case "port": - if !d.NextArg() { - return d.ArgErr() - } - if u.Port != "" { - return d.Errf("a port has already been specified") - } - u.Port = d.Val() - - case "refresh": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("parsing refresh interval duration: %v", err) - } - u.Refresh = caddy.Duration(dur) - - case "resolvers": - if u.Resolver == nil { - u.Resolver = new(UpstreamResolver) - } - u.Resolver.Addresses = d.RemainingArgs() - if len(u.Resolver.Addresses) == 0 { - return d.Errf("must specify at least one resolver address") - } - - case "dial_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value '%s': %v", d.Val(), err) - } - u.DialTimeout = caddy.Duration(dur) - - case "dial_fallback_delay": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad delay value '%s': %v", d.Val(), err) - } - u.FallbackDelay = caddy.Duration(dur) - - case "versions": - args := d.RemainingArgs() - if len(args) == 0 { - return d.Errf("must specify at least one version") - } - - if u.Versions == nil { - u.Versions = &IPVersions{} - } - - trueBool := true - for _, arg := range args { - switch arg { - case "ipv4": - u.Versions.IPv4 = &trueBool - case "ipv6": - u.Versions.IPv6 = &trueBool - default: - return d.Errf("unsupported version: '%s'", arg) - } - } - - default: - return d.Errf("unrecognized a option '%s'", d.Val()) - } + args := d.RemainingArgs() + if len(args) > 2 { + return d.ArgErr() + } + if len(args) > 0 { + u.Name = args[0] + if len(args) == 2 { + u.Port = args[1] } } + for d.NextBlock(0) { + switch d.Val() { + case "name": + if !d.NextArg() { + return d.ArgErr() + } + if u.Name != "" { + return d.Errf("a name has already been specified") + } + u.Name = d.Val() + + case "port": + if !d.NextArg() { + return d.ArgErr() + } + if u.Port != "" { + return d.Errf("a port has already been specified") + } + u.Port = d.Val() + + case "refresh": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("parsing refresh interval duration: %v", err) + } + u.Refresh = caddy.Duration(dur) + + case "resolvers": + if u.Resolver == nil { + u.Resolver = new(UpstreamResolver) + } + u.Resolver.Addresses = d.RemainingArgs() + if len(u.Resolver.Addresses) == 0 { + return d.Errf("must specify at least one resolver address") + } + + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + u.DialTimeout = caddy.Duration(dur) + + case "dial_fallback_delay": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad delay value '%s': %v", d.Val(), err) + } + u.FallbackDelay = caddy.Duration(dur) + + case "versions": + args := d.RemainingArgs() + if len(args) == 0 { + return d.Errf("must specify at least one version") + } + + if u.Versions == nil { + u.Versions = &IPVersions{} + } + + trueBool := true + for _, arg := range args { + switch arg { + case "ipv4": + u.Versions.IPv4 = &trueBool + case "ipv6": + u.Versions.IPv6 = &trueBool + default: + return d.Errf("unsupported version: '%s'", arg) + } + } + + default: + return d.Errf("unrecognized a option '%s'", d.Val()) + } + } return nil } @@ -1521,26 +1518,25 @@ func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // [...] // } func (u *MultiUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume upstream source name - for nesting := d.Nesting(); d.NextBlock(nesting); { - dynModule := d.Val() - modID := "http.reverse_proxy.upstreams." + dynModule - unm, err := caddyfile.UnmarshalModule(d, modID) - if err != nil { - return err - } - source, ok := unm.(UpstreamSource) - if !ok { - return d.Errf("module %s (%T) is not an UpstreamSource", modID, unm) - } - u.SourcesRaw = append(u.SourcesRaw, caddyconfig.JSONModuleObject(source, "source", dynModule, nil)) - } + if d.NextArg() { + return d.ArgErr() } + for d.NextBlock(0) { + dynModule := d.Val() + modID := "http.reverse_proxy.upstreams." + dynModule + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + source, ok := unm.(UpstreamSource) + if !ok { + return d.Errf("module %s (%T) is not an UpstreamSource", modID, unm) + } + u.SourcesRaw = append(u.SourcesRaw, caddyconfig.JSONModuleObject(source, "source", dynModule, nil)) + } return nil } diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go index a24a3ed6..68eee32b 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go @@ -46,76 +46,75 @@ func init() { // capture_stderr // } func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextBlock(0) { - switch d.Val() { - case "root": - if !d.NextArg() { - return d.ArgErr() - } - t.Root = d.Val() - - case "split": - t.SplitPath = d.RemainingArgs() - if len(t.SplitPath) == 0 { - return d.ArgErr() - } - - case "env": - args := d.RemainingArgs() - if len(args) != 2 { - return d.ArgErr() - } - if t.EnvVars == nil { - t.EnvVars = make(map[string]string) - } - t.EnvVars[args[0]] = args[1] - - case "resolve_root_symlink": - if d.NextArg() { - return d.ArgErr() - } - t.ResolveRootSymlink = true - - case "dial_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value %s: %v", d.Val(), err) - } - t.DialTimeout = caddy.Duration(dur) - - case "read_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value %s: %v", d.Val(), err) - } - t.ReadTimeout = caddy.Duration(dur) - - case "write_timeout": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("bad timeout value %s: %v", d.Val(), err) - } - t.WriteTimeout = caddy.Duration(dur) - - case "capture_stderr": - if d.NextArg() { - return d.ArgErr() - } - t.CaptureStderr = true - - default: - return d.Errf("unrecognized subdirective %s", d.Val()) + d.Next() // consume transport name + for d.NextBlock(0) { + switch d.Val() { + case "root": + if !d.NextArg() { + return d.ArgErr() } + t.Root = d.Val() + + case "split": + t.SplitPath = d.RemainingArgs() + if len(t.SplitPath) == 0 { + return d.ArgErr() + } + + case "env": + args := d.RemainingArgs() + if len(args) != 2 { + return d.ArgErr() + } + if t.EnvVars == nil { + t.EnvVars = make(map[string]string) + } + t.EnvVars[args[0]] = args[1] + + case "resolve_root_symlink": + if d.NextArg() { + return d.ArgErr() + } + t.ResolveRootSymlink = true + + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value %s: %v", d.Val(), err) + } + t.DialTimeout = caddy.Duration(dur) + + case "read_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value %s: %v", d.Val(), err) + } + t.ReadTimeout = caddy.Duration(dur) + + case "write_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value %s: %v", d.Val(), err) + } + t.WriteTimeout = caddy.Duration(dur) + + case "capture_stderr": + if d.NextArg() { + return d.ArgErr() + } + t.CaptureStderr = true + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) } } return nil diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies.go b/modules/caddyhttp/reverseproxy/selectionpolicies.go index acb069a7..b56c8074 100644 --- a/modules/caddyhttp/reverseproxy/selectionpolicies.go +++ b/modules/caddyhttp/reverseproxy/selectionpolicies.go @@ -68,10 +68,9 @@ func (r RandomSelection) Select(pool UpstreamPool, request *http.Request, _ http // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *RandomSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -98,22 +97,22 @@ func (WeightedRoundRobinSelection) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *WeightedRoundRobinSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - if len(args) == 0 { - return d.ArgErr() - } + d.Next() // consume policy name - for _, weight := range args { - weightInt, err := strconv.Atoi(weight) - if err != nil { - return d.Errf("invalid weight value '%s': %v", weight, err) - } - if weightInt < 1 { - return d.Errf("invalid weight value '%s': weight should be non-zero and positive", weight) - } - r.Weights = append(r.Weights, weightInt) + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + + for _, weight := range args { + weightInt, err := strconv.Atoi(weight) + if err != nil { + return d.Errf("invalid weight value '%s': %v", weight, err) } + if weightInt < 1 { + return d.Errf("invalid weight value '%s': weight should be non-zero and positive", weight) + } + r.Weights = append(r.Weights, weightInt) } return nil } @@ -179,17 +178,17 @@ func (RandomChoiceSelection) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *RandomChoiceSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if !d.NextArg() { - return d.ArgErr() - } - chooseStr := d.Val() - choose, err := strconv.Atoi(chooseStr) - if err != nil { - return d.Errf("invalid choice value '%s': %v", chooseStr, err) - } - r.Choose = choose + d.Next() // consume policy name + + if !d.NextArg() { + return d.ArgErr() } + chooseStr := d.Val() + choose, err := strconv.Atoi(chooseStr) + if err != nil { + return d.Errf("invalid choice value '%s': %v", chooseStr, err) + } + r.Choose = choose return nil } @@ -280,10 +279,9 @@ func (LeastConnSelection) Select(pool UpstreamPool, _ *http.Request, _ http.Resp // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *LeastConnSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -320,10 +318,9 @@ func (r *RoundRobinSelection) Select(pool UpstreamPool, _ *http.Request, _ http. // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *RoundRobinSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -352,10 +349,9 @@ func (FirstSelection) Select(pool UpstreamPool, _ *http.Request, _ http.Response // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *FirstSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -383,10 +379,9 @@ func (IPHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.Respo // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *IPHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -416,10 +411,9 @@ func (ClientIPHashSelection) Select(pool UpstreamPool, req *http.Request, _ http // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *ClientIPHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -443,10 +437,9 @@ func (URIHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.Resp // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (r *URIHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -504,13 +497,14 @@ func (s QueryHashSelection) Select(pool UpstreamPool, req *http.Request, _ http. // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (s *QueryHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if !d.NextArg() { - return d.ArgErr() - } - s.Key = d.Val() + d.Next() // consume policy name + + if !d.NextArg() { + return d.ArgErr() } - for nesting := d.Nesting(); d.NextBlock(nesting); { + s.Key = d.Val() + + for d.NextBlock(0) { switch d.Val() { case "fallback": if !d.NextArg() { @@ -583,13 +577,14 @@ func (s HeaderHashSelection) Select(pool UpstreamPool, req *http.Request, _ http // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (s *HeaderHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if !d.NextArg() { - return d.ArgErr() - } - s.Field = d.Val() + d.Next() // consume policy name + + if !d.NextArg() { + return d.ArgErr() } - for nesting := d.Nesting(); d.NextBlock(nesting); { + s.Field = d.Val() + + for d.NextBlock(0) { switch d.Val() { case "fallback": if !d.NextArg() { @@ -708,7 +703,7 @@ func (s *CookieHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { default: return d.ArgErr() } - for nesting := d.Nesting(); d.NextBlock(nesting); { + for d.NextBlock(0) { switch d.Val() { case "fallback": if !d.NextArg() { diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go index 5d7bd32e..363dbfdb 100644 --- a/modules/caddyhttp/rewrite/caddyfile.go +++ b/modules/caddyhttp/rewrite/caddyfile.go @@ -39,10 +39,7 @@ func init() { // Only URI components which are given in will be set in the resulting URI. // See the docs for the rewrite handler for more information. func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.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() @@ -66,26 +63,9 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, if err != nil { return nil, err } + h.Next() // consume directive name again, matcher parsing does a reset + h.Next() // advance to the rewrite URI - // consume directive name, again, because extracting matcher does a reset - if !h.NextArg() { - return nil, h.ArgErr() - } - // advance to the rewrite URI - if !h.NextArg() { - return nil, h.ArgErr() - } - - var rewr Rewrite - for h.Next() { - if !h.NextArg() { - return nil, h.ArgErr() - } - rewr.URI = h.Val() - if h.NextArg() { - return nil, h.ArgErr() - } - } return h.NewRoute(userMatcherSet, Rewrite{URI: h.Val()}), nil } @@ -93,17 +73,14 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, // // method [] func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { - var rewr Rewrite - for h.Next() { - if !h.NextArg() { - return nil, h.ArgErr() - } - rewr.Method = h.Val() - if h.NextArg() { - return nil, h.ArgErr() - } + h.Next() // consume directive name + if !h.NextArg() { + return nil, h.ArgErr() } - return rewr, nil + if h.NextArg() { + return nil, h.ArgErr() + } + return Rewrite{Method: h.Val()}, nil } // parseCaddyfileURI sets up a handler for manipulating (but not "rewriting") the @@ -118,65 +95,71 @@ func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, // path_regexp is used, then regular expression replacements will be performed // on the path portion of the URI (and a limit cannot be set). func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + + args := h.RemainingArgs() + if len(args) < 2 { + return nil, h.ArgErr() + } + var rewr Rewrite - for h.Next() { - args := h.RemainingArgs() - if len(args) < 2 { + + switch args[0] { + case "strip_prefix": + if len(args) > 2 { return nil, h.ArgErr() } - switch args[0] { - case "strip_prefix": - if len(args) > 2 { - return nil, h.ArgErr() - } - rewr.StripPathPrefix = args[1] - if !strings.HasPrefix(rewr.StripPathPrefix, "/") { - rewr.StripPathPrefix = "/" + rewr.StripPathPrefix - } - case "strip_suffix": - if len(args) > 2 { - return nil, h.ArgErr() - } - rewr.StripPathSuffix = args[1] - case "replace": - var find, replace, lim string - switch len(args) { - case 4: - lim = args[3] - fallthrough - case 3: - find = args[1] - replace = args[2] - default: - return nil, h.ArgErr() - } - - var limInt int - if lim != "" { - var err error - limInt, err = strconv.Atoi(lim) - if err != nil { - return nil, h.Errf("limit must be an integer; invalid: %v", err) - } - } - - rewr.URISubstring = append(rewr.URISubstring, substrReplacer{ - Find: find, - Replace: replace, - Limit: limInt, - }) - case "path_regexp": - if len(args) != 3 { - return nil, h.ArgErr() - } - find, replace := args[1], args[2] - rewr.PathRegexp = append(rewr.PathRegexp, ®exReplacer{ - Find: find, - Replace: replace, - }) - default: - return nil, h.Errf("unrecognized URI manipulation '%s'", args[0]) + rewr.StripPathPrefix = args[1] + if !strings.HasPrefix(rewr.StripPathPrefix, "/") { + rewr.StripPathPrefix = "/" + rewr.StripPathPrefix } + + case "strip_suffix": + if len(args) > 2 { + return nil, h.ArgErr() + } + rewr.StripPathSuffix = args[1] + + case "replace": + var find, replace, lim string + switch len(args) { + case 4: + lim = args[3] + fallthrough + case 3: + find = args[1] + replace = args[2] + default: + return nil, h.ArgErr() + } + + var limInt int + if lim != "" { + var err error + limInt, err = strconv.Atoi(lim) + if err != nil { + return nil, h.Errf("limit must be an integer; invalid: %v", err) + } + } + + rewr.URISubstring = append(rewr.URISubstring, substrReplacer{ + Find: find, + Replace: replace, + Limit: limInt, + }) + + case "path_regexp": + if len(args) != 3 { + return nil, h.ArgErr() + } + find, replace := args[1], args[2] + rewr.PathRegexp = append(rewr.PathRegexp, ®exReplacer{ + Find: find, + Replace: replace, + }) + + default: + return nil, h.Errf("unrecognized URI manipulation '%s'", args[0]) } return rewr, nil } @@ -190,9 +173,9 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err // Only path matchers (with a `/` prefix) are supported as this is a shortcut // for the handle directive with a strip_prefix rewrite. func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { - if !h.Next() { - return nil, h.ArgErr() - } + h.Next() // consume directive name + + // there must be a path matcher if !h.NextArg() { return nil, h.ArgErr() } diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go index 7cf1a3e9..b6e10ff3 100644 --- a/modules/caddyhttp/staticerror.go +++ b/modules/caddyhttp/staticerror.go @@ -60,36 +60,35 @@ func (StaticError) CaddyModule() caddy.ModuleInfo { // If there is just one argument (other than the matcher), it is considered // to be a status code if it's a valid positive integer of 3 digits. func (e *StaticError) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - switch len(args) { - case 1: - if len(args[0]) == 3 { - if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { - e.StatusCode = WeakString(args[0]) - break - } + d.Next() // consume directive name + args := d.RemainingArgs() + switch len(args) { + case 1: + if len(args[0]) == 3 { + if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { + e.StatusCode = WeakString(args[0]) + break } - e.Error = args[0] - case 2: - e.Error = args[0] - e.StatusCode = WeakString(args[1]) - default: - return d.ArgErr() } + e.Error = args[0] + case 2: + e.Error = args[0] + e.StatusCode = WeakString(args[1]) + default: + return d.ArgErr() + } - for d.NextBlock(0) { - switch d.Val() { - case "message": - if e.Error != "" { - return d.Err("message already specified") - } - if !d.AllArgs(&e.Error) { - return d.ArgErr() - } - default: - return d.Errf("unrecognized subdirective '%s'", d.Val()) + for d.NextBlock(0) { + switch d.Val() { + case "message": + if e.Error != "" { + return d.Err("message already specified") } + if !d.AllArgs(&e.Error) { + return d.ArgErr() + } + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) } } return nil diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go index d6befd68..3ec76f29 100644 --- a/modules/caddyhttp/staticresp.go +++ b/modules/caddyhttp/staticresp.go @@ -138,41 +138,40 @@ func (StaticResponse) CaddyModule() caddy.ModuleInfo { // If there is just one argument (other than the matcher), it is considered // to be a status code if it's a valid positive integer of 3 digits. func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - switch len(args) { - case 1: - if len(args[0]) == 3 { - if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { - s.StatusCode = WeakString(args[0]) - break - } + d.Next() // consume directive name + args := d.RemainingArgs() + switch len(args) { + case 1: + if len(args[0]) == 3 { + if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { + s.StatusCode = WeakString(args[0]) + break } - s.Body = args[0] - case 2: - s.Body = args[0] - s.StatusCode = WeakString(args[1]) - default: - return d.ArgErr() } + s.Body = args[0] + case 2: + s.Body = args[0] + s.StatusCode = WeakString(args[1]) + default: + return d.ArgErr() + } - for d.NextBlock(0) { - switch d.Val() { - case "body": - if s.Body != "" { - return d.Err("body already specified") - } - if !d.AllArgs(&s.Body) { - return d.ArgErr() - } - case "close": - if s.Close { - return d.Err("close already specified") - } - s.Close = true - default: - return d.Errf("unrecognized subdirective '%s'", d.Val()) + for d.NextBlock(0) { + switch d.Val() { + case "body": + if s.Body != "" { + return d.Err("body already specified") } + if !d.AllArgs(&s.Body) { + return d.ArgErr() + } + case "close": + if s.Close { + return d.Err("close already specified") + } + s.Close = true + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) } } return nil diff --git a/modules/caddyhttp/templates/caddyfile.go b/modules/caddyhttp/templates/caddyfile.go index c3039aa8..d2349348 100644 --- a/modules/caddyhttp/templates/caddyfile.go +++ b/modules/caddyhttp/templates/caddyfile.go @@ -34,47 +34,46 @@ func init() { // root // } func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name t := new(Templates) - for h.Next() { - for h.NextBlock(0) { - switch h.Val() { - case "mime": - t.MIMETypes = h.RemainingArgs() - if len(t.MIMETypes) == 0 { - return nil, h.ArgErr() + for h.NextBlock(0) { + switch h.Val() { + case "mime": + t.MIMETypes = h.RemainingArgs() + if len(t.MIMETypes) == 0 { + return nil, h.ArgErr() + } + case "between": + t.Delimiters = h.RemainingArgs() + if len(t.Delimiters) != 2 { + return nil, h.ArgErr() + } + case "root": + if !h.Args(&t.FileRoot) { + return nil, h.ArgErr() + } + case "extensions": + if h.NextArg() { + return nil, h.ArgErr() + } + if t.ExtensionsRaw != nil { + return nil, h.Err("extensions already specified") + } + for nesting := h.Nesting(); h.NextBlock(nesting); { + extensionModuleName := h.Val() + modID := "http.handlers.templates.functions." + extensionModuleName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err } - case "between": - t.Delimiters = h.RemainingArgs() - if len(t.Delimiters) != 2 { - return nil, h.ArgErr() + cf, ok := unm.(CustomFunctions) + if !ok { + return nil, h.Errf("module %s (%T) does not provide template functions", modID, unm) } - case "root": - if !h.Args(&t.FileRoot) { - return nil, h.ArgErr() - } - case "extensions": - if h.NextArg() { - return nil, h.ArgErr() - } - if t.ExtensionsRaw != nil { - return nil, h.Err("extensions already specified") - } - for nesting := h.Nesting(); h.NextBlock(nesting); { - extensionModuleName := h.Val() - modID := "http.handlers.templates.functions." + extensionModuleName - unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) - if err != nil { - return nil, err - } - cf, ok := unm.(CustomFunctions) - if !ok { - return nil, h.Errf("module %s (%T) does not provide template functions", modID, unm) - } - if t.ExtensionsRaw == nil { - t.ExtensionsRaw = make(caddy.ModuleMap) - } - t.ExtensionsRaw[extensionModuleName] = caddyconfig.JSON(cf, nil) + if t.ExtensionsRaw == nil { + t.ExtensionsRaw = make(caddy.ModuleMap) } + t.ExtensionsRaw[extensionModuleName] = caddyconfig.JSON(cf, nil) } } } diff --git a/modules/caddyhttp/tracing/module.go b/modules/caddyhttp/tracing/module.go index fd117c53..85fd6300 100644 --- a/modules/caddyhttp/tracing/module.go +++ b/modules/caddyhttp/tracing/module.go @@ -88,20 +88,18 @@ func (ot *Tracing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { "span": &ot.SpanName, } - for d.Next() { - args := d.RemainingArgs() - if len(args) > 0 { - return d.ArgErr() - } + d.Next() // consume directive name + if d.NextArg() { + return d.ArgErr() + } - for d.NextBlock(0) { - if dst, ok := paramsMap[d.Val()]; ok { - if err := setParameter(d, dst); err != nil { - return err - } - } else { - return d.ArgErr() + for d.NextBlock(0) { + if dst, ok := paramsMap[d.Val()]; ok { + if err := setParameter(d, dst); err != nil { + return err } + } else { + return d.ArgErr() } } return nil diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index d2d7f62c..917a1505 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -68,6 +68,8 @@ func (m VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next H // ... // } func (m *VarsMiddleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + if *m == nil { *m = make(VarsMiddleware) } @@ -94,15 +96,13 @@ func (m *VarsMiddleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } - for d.Next() { - if err := nextVar(true); err != nil { + if err := nextVar(true); err != nil { + return err + } + for d.NextBlock(0) { + if err := nextVar(false); err != nil { return err } - for nesting := d.Nesting(); d.NextBlock(nesting); { - if err := nextVar(false); err != nil { - return err - } - } } return nil @@ -135,6 +135,7 @@ func (m *VarsMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if *m == nil { *m = make(map[string][]string) } + // iterate to merge multiple matchers into one for d.Next() { var field string if !d.Args(&field) { @@ -216,6 +217,7 @@ func (m *MatchVarsRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if *m == nil { *m = make(map[string]*MatchRegexp) } + // iterate to merge multiple matchers into one for d.Next() { var first, second, third string if !d.Args(&first, &second) { diff --git a/modules/caddypki/acmeserver/caddyfile.go b/modules/caddypki/acmeserver/caddyfile.go index 3b52113b..51290eba 100644 --- a/modules/caddypki/acmeserver/caddyfile.go +++ b/modules/caddypki/acmeserver/caddyfile.go @@ -34,53 +34,52 @@ func init() { // resolvers // } func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { - if !h.Next() { - return nil, h.ArgErr() - } - + h.Next() // consume directive name matcherSet, err := h.ExtractMatcherSet() if err != nil { return nil, err } + h.Next() // consume the directive name again (matcher parsing resets) + + // no inline args allowed + if h.NextArg() { + return nil, h.ArgErr() + } var acmeServer Handler var ca *caddypki.CA - for h.Next() { - if h.NextArg() { - return nil, h.ArgErr() - } - for h.NextBlock(0) { - switch h.Val() { - case "ca": - if !h.AllArgs(&acmeServer.CA) { - return nil, h.ArgErr() - } - if ca == nil { - ca = new(caddypki.CA) - } - ca.ID = acmeServer.CA - case "lifetime": - if !h.NextArg() { - return nil, h.ArgErr() - } + for h.NextBlock(0) { + switch h.Val() { + case "ca": + if !h.AllArgs(&acmeServer.CA) { + return nil, h.ArgErr() + } + if ca == nil { + ca = new(caddypki.CA) + } + ca.ID = acmeServer.CA - dur, err := caddy.ParseDuration(h.Val()) - if err != nil { - return nil, err - } + case "lifetime": + if !h.NextArg() { + return nil, h.ArgErr() + } - if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d { - return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d) - } + dur, err := caddy.ParseDuration(h.Val()) + if err != nil { + return nil, err + } - acmeServer.Lifetime = caddy.Duration(dur) + if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d { + return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d) + } - case "resolvers": - acmeServer.Resolvers = h.RemainingArgs() - if len(acmeServer.Resolvers) == 0 { - return nil, h.Errf("must specify at least one resolver address") - } + acmeServer.Lifetime = caddy.Duration(dur) + + case "resolvers": + acmeServer.Resolvers = h.RemainingArgs() + if len(acmeServer.Resolvers) == 0 { + return nil, h.Errf("must specify at least one resolver address") } } } diff --git a/modules/caddytls/acmeissuer.go b/modules/caddytls/acmeissuer.go index a7dbd26e..036e79b1 100644 --- a/modules/caddytls/acmeissuer.go +++ b/modules/caddytls/acmeissuer.go @@ -277,218 +277,219 @@ func (iss *ACMEIssuer) GetACMEIssuer() *ACMEIssuer { return iss } // } // } func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { + d.Next() // consume issuer name + + if d.NextArg() { + iss.CA = d.Val() if d.NextArg() { - iss.CA = d.Val() + return d.ArgErr() + } + } + + for d.NextBlock(0) { + switch d.Val() { + case "dir": + if iss.CA != "" { + return d.Errf("directory is already specified: %s", iss.CA) + } + if !d.AllArgs(&iss.CA) { + return d.ArgErr() + } + + case "test_dir": + if !d.AllArgs(&iss.TestCA) { + return d.ArgErr() + } + + case "email": + if !d.AllArgs(&iss.Email) { + return d.ArgErr() + } + + case "timeout": + var timeoutStr string + if !d.AllArgs(&timeoutStr) { + return d.ArgErr() + } + timeout, err := caddy.ParseDuration(timeoutStr) + if err != nil { + return d.Errf("invalid timeout duration %s: %v", timeoutStr, err) + } + iss.ACMETimeout = caddy.Duration(timeout) + + case "disable_http_challenge": if d.NextArg() { return d.ArgErr() } - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "dir": - if iss.CA != "" { - return d.Errf("directory is already specified: %s", iss.CA) - } - if !d.AllArgs(&iss.CA) { - return d.ArgErr() - } - - case "test_dir": - if !d.AllArgs(&iss.TestCA) { - return d.ArgErr() - } - - case "email": - if !d.AllArgs(&iss.Email) { - return d.ArgErr() - } - - case "timeout": - var timeoutStr string - if !d.AllArgs(&timeoutStr) { - return d.ArgErr() - } - timeout, err := caddy.ParseDuration(timeoutStr) - if err != nil { - return d.Errf("invalid timeout duration %s: %v", timeoutStr, err) - } - iss.ACMETimeout = caddy.Duration(timeout) - - case "disable_http_challenge": - if d.NextArg() { - return d.ArgErr() - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.HTTP == nil { - iss.Challenges.HTTP = new(HTTPChallengeConfig) - } - iss.Challenges.HTTP.Disabled = true - - case "disable_tlsalpn_challenge": - if d.NextArg() { - return d.ArgErr() - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.TLSALPN == nil { - iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig) - } - iss.Challenges.TLSALPN.Disabled = true - - case "alt_http_port": - if !d.NextArg() { - return d.ArgErr() - } - port, err := strconv.Atoi(d.Val()) - if err != nil { - return d.Errf("invalid port %s: %v", d.Val(), err) - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.HTTP == nil { - iss.Challenges.HTTP = new(HTTPChallengeConfig) - } - iss.Challenges.HTTP.AlternatePort = port - - case "alt_tlsalpn_port": - if !d.NextArg() { - return d.ArgErr() - } - port, err := strconv.Atoi(d.Val()) - if err != nil { - return d.Errf("invalid port %s: %v", d.Val(), err) - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.TLSALPN == nil { - iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig) - } - iss.Challenges.TLSALPN.AlternatePort = port - - case "eab": - iss.ExternalAccount = new(acme.EAB) - if !d.AllArgs(&iss.ExternalAccount.KeyID, &iss.ExternalAccount.MACKey) { - return d.ArgErr() - } - - case "trusted_roots": - iss.TrustedRootsPEMFiles = d.RemainingArgs() - - case "dns": - if !d.NextArg() { - return d.ArgErr() - } - provName := d.Val() - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.DNS == nil { - iss.Challenges.DNS = new(DNSChallengeConfig) - } - unm, err := caddyfile.UnmarshalModule(d, "dns.providers."+provName) - if err != nil { - return err - } - iss.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, nil) - - case "propagation_delay": - if !d.NextArg() { - return d.ArgErr() - } - delayStr := d.Val() - delay, err := caddy.ParseDuration(delayStr) - if err != nil { - return d.Errf("invalid propagation_delay duration %s: %v", delayStr, err) - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.DNS == nil { - iss.Challenges.DNS = new(DNSChallengeConfig) - } - iss.Challenges.DNS.PropagationDelay = caddy.Duration(delay) - - case "propagation_timeout": - if !d.NextArg() { - return d.ArgErr() - } - timeoutStr := d.Val() - var timeout time.Duration - if timeoutStr == "-1" { - timeout = time.Duration(-1) - } else { - var err error - timeout, err = caddy.ParseDuration(timeoutStr) - if err != nil { - return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err) - } - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.DNS == nil { - iss.Challenges.DNS = new(DNSChallengeConfig) - } - iss.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout) - - case "resolvers": - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.DNS == nil { - iss.Challenges.DNS = new(DNSChallengeConfig) - } - iss.Challenges.DNS.Resolvers = d.RemainingArgs() - if len(iss.Challenges.DNS.Resolvers) == 0 { - return d.ArgErr() - } - - case "dns_ttl": - if !d.NextArg() { - return d.ArgErr() - } - ttlStr := d.Val() - ttl, err := caddy.ParseDuration(ttlStr) - if err != nil { - return d.Errf("invalid dns_ttl duration %s: %v", ttlStr, err) - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.DNS == nil { - iss.Challenges.DNS = new(DNSChallengeConfig) - } - iss.Challenges.DNS.TTL = caddy.Duration(ttl) - - case "dns_challenge_override_domain": - arg := d.RemainingArgs() - if len(arg) != 1 { - return d.ArgErr() - } - if iss.Challenges == nil { - iss.Challenges = new(ChallengesConfig) - } - if iss.Challenges.DNS == nil { - iss.Challenges.DNS = new(DNSChallengeConfig) - } - iss.Challenges.DNS.OverrideDomain = arg[0] - - case "preferred_chains": - chainPref, err := ParseCaddyfilePreferredChainsOptions(d) - if err != nil { - return err - } - iss.PreferredChains = chainPref - - default: - return d.Errf("unrecognized ACME issuer property: %s", d.Val()) + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) } + if iss.Challenges.HTTP == nil { + iss.Challenges.HTTP = new(HTTPChallengeConfig) + } + iss.Challenges.HTTP.Disabled = true + + case "disable_tlsalpn_challenge": + if d.NextArg() { + return d.ArgErr() + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.TLSALPN == nil { + iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig) + } + iss.Challenges.TLSALPN.Disabled = true + + case "alt_http_port": + if !d.NextArg() { + return d.ArgErr() + } + port, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid port %s: %v", d.Val(), err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.HTTP == nil { + iss.Challenges.HTTP = new(HTTPChallengeConfig) + } + iss.Challenges.HTTP.AlternatePort = port + + case "alt_tlsalpn_port": + if !d.NextArg() { + return d.ArgErr() + } + port, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid port %s: %v", d.Val(), err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.TLSALPN == nil { + iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig) + } + iss.Challenges.TLSALPN.AlternatePort = port + + case "eab": + iss.ExternalAccount = new(acme.EAB) + if !d.AllArgs(&iss.ExternalAccount.KeyID, &iss.ExternalAccount.MACKey) { + return d.ArgErr() + } + + case "trusted_roots": + iss.TrustedRootsPEMFiles = d.RemainingArgs() + + case "dns": + if !d.NextArg() { + return d.ArgErr() + } + provName := d.Val() + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + unm, err := caddyfile.UnmarshalModule(d, "dns.providers."+provName) + if err != nil { + return err + } + iss.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, nil) + + case "propagation_delay": + if !d.NextArg() { + return d.ArgErr() + } + delayStr := d.Val() + delay, err := caddy.ParseDuration(delayStr) + if err != nil { + return d.Errf("invalid propagation_delay duration %s: %v", delayStr, err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.PropagationDelay = caddy.Duration(delay) + + case "propagation_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeoutStr := d.Val() + var timeout time.Duration + if timeoutStr == "-1" { + timeout = time.Duration(-1) + } else { + var err error + timeout, err = caddy.ParseDuration(timeoutStr) + if err != nil { + return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err) + } + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout) + + case "resolvers": + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.Resolvers = d.RemainingArgs() + if len(iss.Challenges.DNS.Resolvers) == 0 { + return d.ArgErr() + } + + case "dns_ttl": + if !d.NextArg() { + return d.ArgErr() + } + ttlStr := d.Val() + ttl, err := caddy.ParseDuration(ttlStr) + if err != nil { + return d.Errf("invalid dns_ttl duration %s: %v", ttlStr, err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.TTL = caddy.Duration(ttl) + + case "dns_challenge_override_domain": + arg := d.RemainingArgs() + if len(arg) != 1 { + return d.ArgErr() + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.OverrideDomain = arg[0] + + case "preferred_chains": + chainPref, err := ParseCaddyfilePreferredChainsOptions(d) + if err != nil { + return err + } + iss.PreferredChains = chainPref + + default: + return d.Errf("unrecognized ACME issuer property: %s", d.Val()) } } return nil diff --git a/modules/caddytls/certmanagers.go b/modules/caddytls/certmanagers.go index ad26468a..9bb436a3 100644 --- a/modules/caddytls/certmanagers.go +++ b/modules/caddytls/certmanagers.go @@ -72,10 +72,9 @@ func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHell // // ... tailscale func (Tailscale) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume cert manager name + if d.NextArg() { + return d.ArgErr() } return nil } @@ -169,17 +168,18 @@ func (hcg HTTPCertGetter) GetCertificate(ctx context.Context, hello *tls.ClientH // // ... http func (hcg *HTTPCertGetter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if !d.NextArg() { - return d.ArgErr() - } - hcg.URL = d.Val() - if d.NextArg() { - return d.ArgErr() - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - return d.Err("block not allowed here") - } + d.Next() // consume cert manager name + + if !d.NextArg() { + return d.ArgErr() + } + hcg.URL = d.Val() + + if d.NextArg() { + return d.ArgErr() + } + if d.NextBlock(0) { + return d.Err("block not allowed here") } return nil } diff --git a/modules/caddytls/internalissuer.go b/modules/caddytls/internalissuer.go index 1cf2461a..cdc4f493 100644 --- a/modules/caddytls/internalissuer.go +++ b/modules/caddytls/internalissuer.go @@ -155,31 +155,30 @@ func (iss InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateReques // sign_with_root // } func (iss *InternalIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextBlock(0) { - switch d.Val() { - case "ca": - if !d.AllArgs(&iss.CA) { - return d.ArgErr() - } - - case "lifetime": - if !d.NextArg() { - return d.ArgErr() - } - dur, err := caddy.ParseDuration(d.Val()) - if err != nil { - return err - } - iss.Lifetime = caddy.Duration(dur) - - case "sign_with_root": - if d.NextArg() { - return d.ArgErr() - } - iss.SignWithRoot = true - + d.Next() // consume issuer name + for d.NextBlock(0) { + switch d.Val() { + case "ca": + if !d.AllArgs(&iss.CA) { + return d.ArgErr() } + + case "lifetime": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return err + } + iss.Lifetime = caddy.Duration(dur) + + case "sign_with_root": + if d.NextArg() { + return d.ArgErr() + } + iss.SignWithRoot = true + } } return nil diff --git a/modules/caddytls/zerosslissuer.go b/modules/caddytls/zerosslissuer.go index 697bab07..1c091a07 100644 --- a/modules/caddytls/zerosslissuer.go +++ b/modules/caddytls/zerosslissuer.go @@ -208,21 +208,20 @@ func (iss *ZeroSSLIssuer) Revoke(ctx context.Context, cert certmagic.Certificate // // Any of the subdirectives for the ACME issuer can be used in the block. func (iss *ZeroSSLIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { + d.Next() // consume issuer name + if d.NextArg() { + iss.APIKey = d.Val() if d.NextArg() { - iss.APIKey = d.Val() - if d.NextArg() { - return d.ArgErr() - } + return d.ArgErr() } + } - if iss.ACMEIssuer == nil { - iss.ACMEIssuer = new(ACMEIssuer) - } - err := iss.ACMEIssuer.UnmarshalCaddyfile(d.NewFromNextSegment()) - if err != nil { - return err - } + if iss.ACMEIssuer == nil { + iss.ACMEIssuer = new(ACMEIssuer) + } + err := iss.ACMEIssuer.UnmarshalCaddyfile(d.NewFromNextSegment()) + if err != nil { + return err } return nil } diff --git a/modules/filestorage/filestorage.go b/modules/filestorage/filestorage.go index 672c66cb..76aff4e8 100644 --- a/modules/filestorage/filestorage.go +++ b/modules/filestorage/filestorage.go @@ -55,7 +55,7 @@ func (s *FileStorage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if d.NextArg() { return d.ArgErr() } - for nesting := d.Nesting(); d.NextBlock(nesting); { + for d.NextBlock(0) { switch d.Val() { case "root": if !d.NextArg() { diff --git a/modules/logging/encoders.go b/modules/logging/encoders.go index a4409e7e..7399423b 100644 --- a/modules/logging/encoders.go +++ b/modules/logging/encoders.go @@ -65,14 +65,13 @@ func (ce *ConsoleEncoder) Provision(_ caddy.Context) error { // See the godoc on the LogEncoderConfig type for the syntax of // subdirectives that are common to most/all encoders. func (ce *ConsoleEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } - err := ce.LogEncoderConfig.UnmarshalCaddyfile(d) - if err != nil { - return err - } + d.Next() // consume encoder name + if d.NextArg() { + return d.ArgErr() + } + err := ce.LogEncoderConfig.UnmarshalCaddyfile(d) + if err != nil { + return err } return nil } @@ -106,14 +105,13 @@ func (je *JSONEncoder) Provision(_ caddy.Context) error { // See the godoc on the LogEncoderConfig type for the syntax of // subdirectives that are common to most/all encoders. func (je *JSONEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - return d.ArgErr() - } - err := je.LogEncoderConfig.UnmarshalCaddyfile(d) - if err != nil { - return err - } + d.Next() // consume encoder name + if d.NextArg() { + return d.ArgErr() + } + err := je.LogEncoderConfig.UnmarshalCaddyfile(d) + if err != nil { + return err } return nil } @@ -149,7 +147,7 @@ type LogEncoderConfig struct { // level_format // } func (lec *LogEncoderConfig) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for nesting := d.Nesting(); d.NextBlock(nesting); { + for d.NextBlock(0) { subdir := d.Val() switch subdir { case "time_local": diff --git a/modules/logging/filewriter.go b/modules/logging/filewriter.go index 11c051d7..3b1001b7 100644 --- a/modules/logging/filewriter.go +++ b/modules/logging/filewriter.go @@ -154,73 +154,72 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) { // omitted or set to a zero value, then Caddy's default value for that // subdirective is used. func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if !d.NextArg() { - return d.ArgErr() - } - fw.Filename = d.Val() - if d.NextArg() { - return d.ArgErr() - } + d.Next() // consume writer name + if !d.NextArg() { + return d.ArgErr() + } + fw.Filename = d.Val() + if d.NextArg() { + return d.ArgErr() + } - for d.NextBlock(0) { - switch d.Val() { - case "roll_disabled": - var f bool - fw.Roll = &f - if d.NextArg() { - return d.ArgErr() - } - - case "roll_size": - var sizeStr string - if !d.AllArgs(&sizeStr) { - return d.ArgErr() - } - size, err := humanize.ParseBytes(sizeStr) - if err != nil { - return d.Errf("parsing size: %v", err) - } - fw.RollSizeMB = int(math.Ceil(float64(size) / humanize.MiByte)) - - case "roll_uncompressed": - var f bool - fw.RollCompress = &f - if d.NextArg() { - return d.ArgErr() - } - - case "roll_local_time": - fw.RollLocalTime = true - if d.NextArg() { - return d.ArgErr() - } - - case "roll_keep": - var keepStr string - if !d.AllArgs(&keepStr) { - return d.ArgErr() - } - keep, err := strconv.Atoi(keepStr) - if err != nil { - return d.Errf("parsing roll_keep number: %v", err) - } - fw.RollKeep = keep - - case "roll_keep_for": - var keepForStr string - if !d.AllArgs(&keepForStr) { - return d.ArgErr() - } - keepFor, err := caddy.ParseDuration(keepForStr) - if err != nil { - return d.Errf("parsing roll_keep_for duration: %v", err) - } - if keepFor < 0 { - return d.Errf("negative roll_keep_for duration: %v", keepFor) - } - fw.RollKeepDays = int(math.Ceil(keepFor.Hours() / 24)) + for d.NextBlock(0) { + switch d.Val() { + case "roll_disabled": + var f bool + fw.Roll = &f + if d.NextArg() { + return d.ArgErr() } + + case "roll_size": + var sizeStr string + if !d.AllArgs(&sizeStr) { + return d.ArgErr() + } + size, err := humanize.ParseBytes(sizeStr) + if err != nil { + return d.Errf("parsing size: %v", err) + } + fw.RollSizeMB = int(math.Ceil(float64(size) / humanize.MiByte)) + + case "roll_uncompressed": + var f bool + fw.RollCompress = &f + if d.NextArg() { + return d.ArgErr() + } + + case "roll_local_time": + fw.RollLocalTime = true + if d.NextArg() { + return d.ArgErr() + } + + case "roll_keep": + var keepStr string + if !d.AllArgs(&keepStr) { + return d.ArgErr() + } + keep, err := strconv.Atoi(keepStr) + if err != nil { + return d.Errf("parsing roll_keep number: %v", err) + } + fw.RollKeep = keep + + case "roll_keep_for": + var keepForStr string + if !d.AllArgs(&keepForStr) { + return d.ArgErr() + } + keepFor, err := caddy.ParseDuration(keepForStr) + if err != nil { + return d.Errf("parsing roll_keep_for duration: %v", err) + } + if keepFor < 0 { + return d.Errf("negative roll_keep_for duration: %v", keepFor) + } + fw.RollKeepDays = int(math.Ceil(keepFor.Hours() / 24)) } } return nil diff --git a/modules/logging/filterencoder.go b/modules/logging/filterencoder.go index 4d51e645..735d7d42 100644 --- a/modules/logging/filterencoder.go +++ b/modules/logging/filterencoder.go @@ -108,50 +108,49 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error { // } // } func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextBlock(0) { - switch d.Val() { - case "wrap": + d.Next() // consume encoder name + for d.NextBlock(0) { + switch d.Val() { + case "wrap": + if !d.NextArg() { + return d.ArgErr() + } + moduleName := d.Val() + moduleID := "caddy.logging.encoders." + moduleName + unm, err := caddyfile.UnmarshalModule(d, moduleID) + if err != nil { + return err + } + enc, ok := unm.(zapcore.Encoder) + if !ok { + return d.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm) + } + fe.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil) + + case "fields": + for nesting := d.Nesting(); d.NextBlock(nesting); { + field := d.Val() if !d.NextArg() { return d.ArgErr() } - moduleName := d.Val() - moduleID := "caddy.logging.encoders." + moduleName + filterName := d.Val() + moduleID := "caddy.logging.encoders.filter." + filterName unm, err := caddyfile.UnmarshalModule(d, moduleID) if err != nil { return err } - enc, ok := unm.(zapcore.Encoder) + filter, ok := unm.(LogFieldFilter) if !ok { - return d.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm) + return d.Errf("module %s (%T) is not a logging.LogFieldFilter", moduleID, unm) } - fe.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil) - - case "fields": - for d.NextBlock(1) { - field := d.Val() - if !d.NextArg() { - return d.ArgErr() - } - filterName := d.Val() - moduleID := "caddy.logging.encoders.filter." + filterName - unm, err := caddyfile.UnmarshalModule(d, moduleID) - if err != nil { - return err - } - filter, ok := unm.(LogFieldFilter) - if !ok { - return d.Errf("module %s (%T) is not a logging.LogFieldFilter", moduleID, unm) - } - if fe.FieldsRaw == nil { - fe.FieldsRaw = make(map[string]json.RawMessage) - } - fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(filter, "filter", filterName, nil) + if fe.FieldsRaw == nil { + fe.FieldsRaw = make(map[string]json.RawMessage) } - - default: - return d.Errf("unrecognized subdirective %s", d.Val()) + fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(filter, "filter", filterName, nil) } + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) } } return nil diff --git a/modules/logging/filters.go b/modules/logging/filters.go index 233d5d71..f38f8c18 100644 --- a/modules/logging/filters.go +++ b/modules/logging/filters.go @@ -128,10 +128,9 @@ func (ReplaceFilter) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (f *ReplaceFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - f.Value = d.Val() - } + d.Next() // consume filter name + if d.NextArg() { + f.Value = d.Val() } return nil } @@ -169,32 +168,31 @@ func (IPMaskFilter) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (m *IPMaskFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextBlock(0) { - switch d.Val() { - case "ipv4": - if !d.NextArg() { - return d.ArgErr() - } - val, err := strconv.Atoi(d.Val()) - if err != nil { - return d.Errf("error parsing %s: %v", d.Val(), err) - } - m.IPv4MaskRaw = val - - case "ipv6": - if !d.NextArg() { - return d.ArgErr() - } - val, err := strconv.Atoi(d.Val()) - if err != nil { - return d.Errf("error parsing %s: %v", d.Val(), err) - } - m.IPv6MaskRaw = val - - default: - return d.Errf("unrecognized subdirective %s", d.Val()) + d.Next() // consume filter name + for d.NextBlock(0) { + switch d.Val() { + case "ipv4": + if !d.NextArg() { + return d.ArgErr() } + val, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("error parsing %s: %v", d.Val(), err) + } + m.IPv4MaskRaw = val + + case "ipv6": + if !d.NextArg() { + return d.ArgErr() + } + val, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("error parsing %s: %v", d.Val(), err) + } + m.IPv6MaskRaw = val + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) } } return nil @@ -328,45 +326,44 @@ func (QueryFilter) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (m *QueryFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextBlock(0) { - qfa := queryFilterAction{} - switch d.Val() { - case "replace": - if !d.NextArg() { - return d.ArgErr() - } - - qfa.Type = replaceAction - qfa.Parameter = d.Val() - - if !d.NextArg() { - return d.ArgErr() - } - qfa.Value = d.Val() - - case "hash": - if !d.NextArg() { - return d.ArgErr() - } - - qfa.Type = hashAction - qfa.Parameter = d.Val() - - case "delete": - if !d.NextArg() { - return d.ArgErr() - } - - qfa.Type = deleteAction - qfa.Parameter = d.Val() - - default: - return d.Errf("unrecognized subdirective %s", d.Val()) + d.Next() // consume filter name + for d.NextBlock(0) { + qfa := queryFilterAction{} + switch d.Val() { + case "replace": + if !d.NextArg() { + return d.ArgErr() } - m.Actions = append(m.Actions, qfa) + qfa.Type = replaceAction + qfa.Parameter = d.Val() + + if !d.NextArg() { + return d.ArgErr() + } + qfa.Value = d.Val() + + case "hash": + if !d.NextArg() { + return d.ArgErr() + } + + qfa.Type = hashAction + qfa.Parameter = d.Val() + + case "delete": + if !d.NextArg() { + return d.ArgErr() + } + + qfa.Type = deleteAction + qfa.Parameter = d.Val() + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) } + + m.Actions = append(m.Actions, qfa) } return nil } @@ -460,45 +457,44 @@ func (CookieFilter) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (m *CookieFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - for d.NextBlock(0) { - cfa := cookieFilterAction{} - switch d.Val() { - case "replace": - if !d.NextArg() { - return d.ArgErr() - } - - cfa.Type = replaceAction - cfa.Name = d.Val() - - if !d.NextArg() { - return d.ArgErr() - } - cfa.Value = d.Val() - - case "hash": - if !d.NextArg() { - return d.ArgErr() - } - - cfa.Type = hashAction - cfa.Name = d.Val() - - case "delete": - if !d.NextArg() { - return d.ArgErr() - } - - cfa.Type = deleteAction - cfa.Name = d.Val() - - default: - return d.Errf("unrecognized subdirective %s", d.Val()) + d.Next() // consume filter name + for d.NextBlock(0) { + cfa := cookieFilterAction{} + switch d.Val() { + case "replace": + if !d.NextArg() { + return d.ArgErr() } - m.Actions = append(m.Actions, cfa) + cfa.Type = replaceAction + cfa.Name = d.Val() + + if !d.NextArg() { + return d.ArgErr() + } + cfa.Value = d.Val() + + case "hash": + if !d.NextArg() { + return d.ArgErr() + } + + cfa.Type = hashAction + cfa.Name = d.Val() + + case "delete": + if !d.NextArg() { + return d.ArgErr() + } + + cfa.Type = deleteAction + cfa.Name = d.Val() + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) } + + m.Actions = append(m.Actions, cfa) } return nil } @@ -571,13 +567,12 @@ func (RegexpFilter) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (f *RegexpFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - f.RawRegexp = d.Val() - } - if d.NextArg() { - f.Value = d.Val() - } + d.Next() // consume filter name + if d.NextArg() { + f.RawRegexp = d.Val() + } + if d.NextArg() { + f.Value = d.Val() } return nil } @@ -625,10 +620,9 @@ func (RenameFilter) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile sets up the module from Caddyfile tokens. func (f *RenameFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if d.NextArg() { - f.Name = d.Val() - } + d.Next() // consume filter name + if d.NextArg() { + f.Name = d.Val() } return nil } diff --git a/modules/logging/netwriter.go b/modules/logging/netwriter.go index 1939cb71..dc2b0922 100644 --- a/modules/logging/netwriter.go +++ b/modules/logging/netwriter.go @@ -117,35 +117,34 @@ func (nw NetWriter) OpenWriter() (io.WriteCloser, error) { // soft_start // } func (nw *NetWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - if !d.NextArg() { - return d.ArgErr() - } - nw.Address = d.Val() - if d.NextArg() { - return d.ArgErr() - } - for nesting := d.Nesting(); d.NextBlock(nesting); { - switch d.Val() { - case "dial_timeout": - if !d.NextArg() { - return d.ArgErr() - } - timeout, err := caddy.ParseDuration(d.Val()) - if err != nil { - return d.Errf("invalid duration: %s", d.Val()) - } - if d.NextArg() { - return d.ArgErr() - } - nw.DialTimeout = caddy.Duration(timeout) - - case "soft_start": - if d.NextArg() { - return d.ArgErr() - } - nw.SoftStart = true + d.Next() // consume writer name + if !d.NextArg() { + return d.ArgErr() + } + nw.Address = d.Val() + if d.NextArg() { + return d.ArgErr() + } + for d.NextBlock(0) { + switch d.Val() { + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() } + timeout, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid duration: %s", d.Val()) + } + if d.NextArg() { + return d.ArgErr() + } + nw.DialTimeout = caddy.Duration(timeout) + + case "soft_start": + if d.NextArg() { + return d.ArgErr() + } + nw.SoftStart = true } } return nil diff --git a/modules/metrics/metrics.go b/modules/metrics/metrics.go index a9e0f0ef..dc6196a1 100644 --- a/modules/metrics/metrics.go +++ b/modules/metrics/metrics.go @@ -78,19 +78,18 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) // disable_openmetrics // } func (m *Metrics) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - for d.Next() { - args := d.RemainingArgs() - if len(args) > 0 { - return d.ArgErr() - } + d.Next() // consume directive name + args := d.RemainingArgs() + if len(args) > 0 { + return d.ArgErr() + } - for d.NextBlock(0) { - switch d.Val() { - case "disable_openmetrics": - m.DisableOpenMetrics = true - default: - return d.Errf("unrecognized subdirective %q", d.Val()) - } + for d.NextBlock(0) { + switch d.Val() { + case "disable_openmetrics": + m.DisableOpenMetrics = true + default: + return d.Errf("unrecognized subdirective %q", d.Val()) } } return nil