1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddyfile
16
+
17
+import (
18
+ "regexp"
19
+ "strconv"
20
+ "strings"
21
+
22
+ "github.com/caddyserver/caddy/v2"
23
+ "go.uber.org/zap"
24
+)
25
+
26
+// parseVariadic determines if the token is a variadic placeholder,
27
+// and if so, determines the index range (start/end) of args to use.
28
+// Returns a boolean signaling whether a variadic placeholder was found,
29
+// and the start and end indices.
30
+func parseVariadic(token Token, argCount int) (bool, int, int) {
31
+ if !strings.HasPrefix(token.Text, "{args[") {
32
+ return false, 0, 0
33
+ }
34
+ if !strings.HasSuffix(token.Text, "]}") {
35
+ return false, 0, 0
36
+ }
37
+
38
+ argRange := strings.TrimSuffix(strings.TrimPrefix(token.Text, "{args["), "]}")
39
+ if argRange == "" {
40
+ caddy.Log().Named("caddyfile").Warn(
41
+ "Placeholder "+token.Text+" cannot have an empty index",
42
+ zap.String("file", token.File+":"+strconv.Itoa(token.Line)))
43
+ return false, 0, 0
44
+ }
45
+
46
+ start, end, found := strings.Cut(argRange, ":")
47
+
48
+ // If no ":" delimiter is found, this is not a variadic.
49
+ // The replacer will pick this up.
50
+ if !found {
51
+ return false, 0, 0
52
+ }
53
+
54
+ var (
55
+ startIndex = 0
56
+ endIndex = argCount
57
+ err error
58
+ )
59
+ if start != "" {
60
+ startIndex, err = strconv.Atoi(start)
61
+ if err != nil {
62
+ caddy.Log().Named("caddyfile").Warn(
63
+ "Variadic placeholder "+token.Text+" has an invalid start index",
64
+ zap.String("file", token.File+":"+strconv.Itoa(token.Line)))
65
+ return false, 0, 0
66
+ }
67
+ }
68
+ if end != "" {
69
+ endIndex, err = strconv.Atoi(end)
70
+ if err != nil {
71
+ caddy.Log().Named("caddyfile").Warn(
72
+ "Variadic placeholder "+token.Text+" has an invalid end index",
73
+ zap.String("file", token.File+":"+strconv.Itoa(token.Line)))
74
+ return false, 0, 0
75
+ }
76
+ }
77
+
78
+ // bound check
79
+ if startIndex < 0 || startIndex > endIndex || endIndex > argCount {
80
+ caddy.Log().Named("caddyfile").Warn(
81
+ "Variadic placeholder "+token.Text+" indices are out of bounds, only "+strconv.Itoa(argCount)+" argument(s) exist",
82
+ zap.String("file", token.File+":"+strconv.Itoa(token.Line)))
83
+ return false, 0, 0
84
+ }
85
+ return true, startIndex, endIndex
86
+}
87
+
88
+// makeArgsReplacer prepares a Replacer which can replace
89
+// non-variadic args placeholders in imported tokens.
90
+func makeArgsReplacer(args []string) *caddy.Replacer {
91
+ repl := caddy.NewEmptyReplacer()
92
+ repl.Map(func(key string) (any, bool) {
93
+ // TODO: Remove the deprecated {args.*} placeholder
94
+ // support at some point in the future
95
+ if matches := argsRegexpIndexDeprecated.FindStringSubmatch(key); len(matches) > 0 {
96
+ value, err := strconv.Atoi(matches[1])
97
+ if err != nil {
98
+ caddy.Log().Named("caddyfile").Warn(
99
+ "Placeholder {args." + matches[1] + "} has an invalid index")
100
+ return nil, false
101
+ }
102
+ if value >= len(args) {
103
+ caddy.Log().Named("caddyfile").Warn(
104
+ "Placeholder {args." + matches[1] + "} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist")
105
+ return nil, false
106
+ }
107
+ caddy.Log().Named("caddyfile").Warn(
108
+ "Placeholder {args." + matches[1] + "} deprecated, use {args[" + matches[1] + "]} instead")
109
+ return args[value], true
110
+ }
111
+
112
+ // Handle args[*] form
113
+ if matches := argsRegexpIndex.FindStringSubmatch(key); len(matches) > 0 {
114
+ if strings.Contains(matches[1], ":") {
115
+ caddy.Log().Named("caddyfile").Warn(
116
+ "Variadic placeholder {args[" + matches[1] + "]} must be a token on its own")
117
+ return nil, false
118
+ }
119
+ value, err := strconv.Atoi(matches[1])
120
+ if err != nil {
121
+ caddy.Log().Named("caddyfile").Warn(
122
+ "Placeholder {args[" + matches[1] + "]} has an invalid index")
123
+ return nil, false
124
+ }
125
+ if value >= len(args) {
126
+ caddy.Log().Named("caddyfile").Warn(
127
+ "Placeholder {args[" + matches[1] + "]} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist")
128
+ return nil, false
129
+ }
130
+ return args[value], true
131
+ }
132
+
133
+ // Not an args placeholder, ignore
134
+ return nil, false
135
+ })
136
+ return repl
137
+}
138
+
139
+var (
140
+ argsRegexpIndexDeprecated = regexp.MustCompile(`args\.(.+)`)
141
+ argsRegexpIndex = regexp.MustCompile(`args\[(.+)]`)
142
+)