// Copyright 2015 Matthew Holt and The Caddy Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package caddyfile import ( "regexp" "strconv" "strings" "go.uber.org/zap" "github.com/caddyserver/caddy/v2" ) // parseVariadic determines if the token is a variadic placeholder, // and if so, determines the index range (start/end) of args to use. // Returns a boolean signaling whether a variadic placeholder was found, // and the start and end indices. func parseVariadic(token Token, argCount int) (bool, int, int) { if !strings.HasPrefix(token.Text, "{args[") { return false, 0, 0 } if !strings.HasSuffix(token.Text, "]}") { return false, 0, 0 } argRange := strings.TrimSuffix(strings.TrimPrefix(token.Text, "{args["), "]}") if argRange == "" { caddy.Log().Named("caddyfile").Warn( "Placeholder "+token.Text+" cannot have an empty index", zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports)) return false, 0, 0 } start, end, found := strings.Cut(argRange, ":") // If no ":" delimiter is found, this is not a variadic. // The replacer will pick this up. if !found { return false, 0, 0 } // A valid token may contain several placeholders, and // they may be separated by ":". It's not variadic. // https://github.com/caddyserver/caddy/issues/5716 if strings.Contains(start, "}") || strings.Contains(end, "{") { return false, 0, 0 } var ( startIndex = 0 endIndex = argCount err error ) if start != "" { startIndex, err = strconv.Atoi(start) if err != nil { caddy.Log().Named("caddyfile").Warn( "Variadic placeholder "+token.Text+" has an invalid start index", zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports)) return false, 0, 0 } } if end != "" { endIndex, err = strconv.Atoi(end) if err != nil { caddy.Log().Named("caddyfile").Warn( "Variadic placeholder "+token.Text+" has an invalid end index", zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports)) return false, 0, 0 } } // bound check if startIndex < 0 || startIndex > endIndex || endIndex > argCount { caddy.Log().Named("caddyfile").Warn( "Variadic placeholder "+token.Text+" indices are out of bounds, only "+strconv.Itoa(argCount)+" argument(s) exist", zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports)) return false, 0, 0 } return true, startIndex, endIndex } // makeArgsReplacer prepares a Replacer which can replace // non-variadic args placeholders in imported tokens. func makeArgsReplacer(args []string) *caddy.Replacer { repl := caddy.NewEmptyReplacer() repl.Map(func(key string) (any, bool) { // TODO: Remove the deprecated {args.*} placeholder // support at some point in the future if matches := argsRegexpIndexDeprecated.FindStringSubmatch(key); len(matches) > 0 { // What's matched may be a substring of the key if matches[0] != key { return nil, false } value, err := strconv.Atoi(matches[1]) if err != nil { caddy.Log().Named("caddyfile").Warn( "Placeholder {args." + matches[1] + "} has an invalid index") return nil, false } if value >= len(args) { caddy.Log().Named("caddyfile").Warn( "Placeholder {args." + matches[1] + "} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist") return nil, false } caddy.Log().Named("caddyfile").Warn( "Placeholder {args." + matches[1] + "} deprecated, use {args[" + matches[1] + "]} instead") return args[value], true } // Handle args[*] form if matches := argsRegexpIndex.FindStringSubmatch(key); len(matches) > 0 { // What's matched may be a substring of the key if matches[0] != key { return nil, false } if strings.Contains(matches[1], ":") { caddy.Log().Named("caddyfile").Warn( "Variadic placeholder {args[" + matches[1] + "]} must be a token on its own") return nil, false } value, err := strconv.Atoi(matches[1]) if err != nil { caddy.Log().Named("caddyfile").Warn( "Placeholder {args[" + matches[1] + "]} has an invalid index") return nil, false } if value >= len(args) { caddy.Log().Named("caddyfile").Warn( "Placeholder {args[" + matches[1] + "]} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist") return nil, false } return args[value], true } // Not an args placeholder, ignore return nil, false }) return repl } var ( argsRegexpIndexDeprecated = regexp.MustCompile(`args\.(.+)`) argsRegexpIndex = regexp.MustCompile(`args\[(.+)]`) )