Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 14:15 UTC
map: Coerce val to string, fix #4987
Also prevent infinite recursion, and enforce placeholder syntax.
Matthew Holt committed
Sep 1, 2022 at 21:15 UTC
73d4a8ba02292491fca289b324e89ef8c62fd435
5 files changed
+38
-35
caddyconfig/caddyfile/dispenser.go
+4
-4
@@ -146,15 +146,15 @@ func (d *Dispenser) NextLine() bool {
146
//
147
// Proper use of this method looks like this:
148
//
149
-// for nesting := d.Nesting(); d.NextBlock(nesting); {
150
-// }
149
+// for nesting := d.Nesting(); d.NextBlock(nesting); {
150
+// }
151
//
152
// However, in simple cases where it is known that the
153
// Dispenser is new and has not already traversed state
154
// by a loop over NextBlock(), this will do:
155
//
156
-// for d.NextBlock(0) {
157
-// }
156
+// for d.NextBlock(0) {
157
+// }
158
//
159
// As with other token parsing logic, a loop over
160
// NextBlock() should be contained within a loop over
modules/caddyhttp/map/caddyfile.go
+5
-5
@@ -27,10 +27,10 @@ func init() {
27
28
// parseCaddyfile sets up the map handler from Caddyfile tokens. Syntax:
29
//
30
-// map [<matcher>] <source> <destinations...> {
31
-// [~]<input> <outputs...>
32
-// default <defaults...>
33
-// }
30
+// map [<matcher>] <source> <destinations...> {
31
+// [~]<input> <outputs...>
32
+// default <defaults...>
33
+// }
34
//
35
// If the input value is prefixed with a tilde (~), then the input will be parsed as a
36
// regular expression.
@@ -76,7 +76,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
76
continue
77
}
78
79
- // every other line maps one input to one or more outputs
79
+ // every line maps an input value to one or more outputs
80
in := h.Val()
81
var outs []any
82
for h.NextArg() {
modules/caddyhttp/map/map.go
+20
-6
@@ -62,6 +62,9 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
62
// Provision sets up h.
63
func (h *Handler) Provision(_ caddy.Context) error {
64
for j, dest := range h.Destinations {
65
+ if strings.Count(dest, "{") != 1 || !strings.HasPrefix(dest, "{") {
66
+ return fmt.Errorf("destination must be a placeholder and only a placeholder")
67
+ }
68
h.Destinations[j] = strings.Trim(dest, "{}")
69
}
70
@@ -106,6 +109,16 @@ func (h *Handler) Validate() error {
109
}
110
seen[input] = i
111
112
+ // prevent infinite recursion
113
+ for _, out := range m.Outputs {
114
+ for _, dest := range h.Destinations {
115
+ if strings.Contains(caddy.ToString(out), dest) ||
116
+ strings.Contains(m.Input, dest) {
117
+ return fmt.Errorf("mapping %d requires value of {%s} to define value of {%s}: infinite recursion", i, dest, dest)
118
+ }
119
+ }
120
+ }
121
+
122
// ensure mappings have 1:1 output-to-destination correspondence
123
nOut := len(m.Outputs)
124
if nOut != nDest {
@@ -135,21 +148,22 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
148
if output == nil {
149
continue
150
}
151
+ outputStr := caddy.ToString(output)
152
+
153
+ // evaluate regular expression if configured
154
if m.re != nil {
155
var result []byte
156
matches := m.re.FindStringSubmatchIndex(input)
157
if matches == nil {
158
continue
159
}
144
- result = m.re.ExpandString(result, output.(string), input, matches)
160
+ result = m.re.ExpandString(result, outputStr, input, matches)
161
return string(result), true
162
}
163
+
164
+ // otherwise simple string comparison
165
if input == m.Input {
148
- if outputStr, ok := output.(string); ok {
149
- // NOTE: if the output has a placeholder that has the same key as the input, this is infinite recursion
150
- return repl.ReplaceAll(outputStr, ""), true
151
- }
152
- return output, true
166
+ return repl.ReplaceAll(outputStr, ""), true
167
}
168
}
169
modules/caddyhttp/templates/tplcontext.go
+2
-15
@@ -305,7 +305,7 @@ func (TemplateContext) funcStripHTML(s string) string {
305
// funcMarkdown renders the markdown body as HTML. The resulting
306
// HTML is NOT escaped so that it can be rendered as HTML.
307
func (TemplateContext) funcMarkdown(input any) (string, error) {
308
- inputStr := toString(input)
308
+ inputStr := caddy.ToString(input)
309
310
md := goldmark.New(
311
goldmark.WithExtensions(
@@ -341,7 +341,7 @@ func (TemplateContext) funcMarkdown(input any) (string, error) {
341
// and returns the separated key-value pairs and the body/content. input
342
// must be a "stringy" value.
343
func (TemplateContext) funcSplitFrontMatter(input any) (parsedMarkdownDoc, error) {
344
- meta, body, err := extractFrontMatter(toString(input))
344
+ meta, body, err := extractFrontMatter(caddy.ToString(input))
345
if err != nil {
346
return parsedMarkdownDoc{}, err
347
}
@@ -465,19 +465,6 @@ func (h WrappedHeader) Del(field string) string {
465
return ""
466
}
467
468
-func toString(input any) string {
469
- switch v := input.(type) {
470
- case string:
471
- return v
472
- case fmt.Stringer:
473
- return v.String()
474
- case error:
475
- return v.Error()
476
- default:
477
- return fmt.Sprintf("%v", input)
478
- }
479
-}
480
-
468
var bufPool = sync.Pool{
469
New: func() any {
470
return new(bytes.Buffer)
replacer.go
+7
-5
@@ -78,11 +78,11 @@ func (r *Replacer) Get(variable string) (any, bool) {
78
return nil, false
79
}
80
81
-// GetString is the same as Get, but coerces the value to a
82
-// string representation.
81
+// GetString is the same as Get, but coerces the value to a
82
+// string representation as efficiently as possible.
83
func (r *Replacer) GetString(variable string) (string, bool) {
84
s, found := r.Get(variable)
85
- return toString(s), found
85
+ return ToString(s), found
86
}
87
88
// Delete removes a variable with a static value
@@ -204,7 +204,7 @@ scan:
204
}
205
206
// convert val to a string as efficiently as possible
207
- valStr := toString(val)
207
+ valStr := ToString(val)
208
209
// write the value; if it's empty, either return
210
// an error or write a default value
@@ -230,7 +230,9 @@ scan:
230
return sb.String(), nil
231
}
232
233
-func toString(val any) string {
233
+// ToString returns val as a string, as efficiently as possible.
234
+// EXPERIMENTAL: may be changed or removed later.
235
+func ToString(val any) string {
236
switch v := val.(type) {
237
case nil:
238
return ""