mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-25 09:40:13 +08:00
map: Coerce val to string, fix #4987
Also prevent infinite recursion, and enforce placeholder syntax.
This commit is contained in:
parent
7d5108d132
commit
73d4a8ba02
|
@ -146,15 +146,15 @@ func (d *Dispenser) NextLine() bool {
|
||||||
//
|
//
|
||||||
// Proper use of this method looks like this:
|
// Proper use of this method looks like this:
|
||||||
//
|
//
|
||||||
// for nesting := d.Nesting(); d.NextBlock(nesting); {
|
// for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// However, in simple cases where it is known that the
|
// However, in simple cases where it is known that the
|
||||||
// Dispenser is new and has not already traversed state
|
// Dispenser is new and has not already traversed state
|
||||||
// by a loop over NextBlock(), this will do:
|
// by a loop over NextBlock(), this will do:
|
||||||
//
|
//
|
||||||
// for d.NextBlock(0) {
|
// for d.NextBlock(0) {
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// As with other token parsing logic, a loop over
|
// As with other token parsing logic, a loop over
|
||||||
// NextBlock() should be contained within a loop over
|
// NextBlock() should be contained within a loop over
|
||||||
|
|
|
@ -27,10 +27,10 @@ func init() {
|
||||||
|
|
||||||
// parseCaddyfile sets up the map handler from Caddyfile tokens. Syntax:
|
// parseCaddyfile sets up the map handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// map [<matcher>] <source> <destinations...> {
|
// map [<matcher>] <source> <destinations...> {
|
||||||
// [~]<input> <outputs...>
|
// [~]<input> <outputs...>
|
||||||
// default <defaults...>
|
// default <defaults...>
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// If the input value is prefixed with a tilde (~), then the input will be parsed as a
|
// If the input value is prefixed with a tilde (~), then the input will be parsed as a
|
||||||
// regular expression.
|
// regular expression.
|
||||||
|
@ -76,7 +76,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// every other line maps one input to one or more outputs
|
// every line maps an input value to one or more outputs
|
||||||
in := h.Val()
|
in := h.Val()
|
||||||
var outs []any
|
var outs []any
|
||||||
for h.NextArg() {
|
for h.NextArg() {
|
||||||
|
|
|
@ -62,6 +62,9 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
|
||||||
// Provision sets up h.
|
// Provision sets up h.
|
||||||
func (h *Handler) Provision(_ caddy.Context) error {
|
func (h *Handler) Provision(_ caddy.Context) error {
|
||||||
for j, dest := range h.Destinations {
|
for j, dest := range h.Destinations {
|
||||||
|
if strings.Count(dest, "{") != 1 || !strings.HasPrefix(dest, "{") {
|
||||||
|
return fmt.Errorf("destination must be a placeholder and only a placeholder")
|
||||||
|
}
|
||||||
h.Destinations[j] = strings.Trim(dest, "{}")
|
h.Destinations[j] = strings.Trim(dest, "{}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +109,16 @@ func (h *Handler) Validate() error {
|
||||||
}
|
}
|
||||||
seen[input] = i
|
seen[input] = i
|
||||||
|
|
||||||
|
// prevent infinite recursion
|
||||||
|
for _, out := range m.Outputs {
|
||||||
|
for _, dest := range h.Destinations {
|
||||||
|
if strings.Contains(caddy.ToString(out), dest) ||
|
||||||
|
strings.Contains(m.Input, dest) {
|
||||||
|
return fmt.Errorf("mapping %d requires value of {%s} to define value of {%s}: infinite recursion", i, dest, dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ensure mappings have 1:1 output-to-destination correspondence
|
// ensure mappings have 1:1 output-to-destination correspondence
|
||||||
nOut := len(m.Outputs)
|
nOut := len(m.Outputs)
|
||||||
if nOut != nDest {
|
if nOut != nDest {
|
||||||
|
@ -135,21 +148,22 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
|
||||||
if output == nil {
|
if output == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
outputStr := caddy.ToString(output)
|
||||||
|
|
||||||
|
// evaluate regular expression if configured
|
||||||
if m.re != nil {
|
if m.re != nil {
|
||||||
var result []byte
|
var result []byte
|
||||||
matches := m.re.FindStringSubmatchIndex(input)
|
matches := m.re.FindStringSubmatchIndex(input)
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = m.re.ExpandString(result, output.(string), input, matches)
|
result = m.re.ExpandString(result, outputStr, input, matches)
|
||||||
return string(result), true
|
return string(result), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// otherwise simple string comparison
|
||||||
if input == m.Input {
|
if input == m.Input {
|
||||||
if outputStr, ok := output.(string); ok {
|
return repl.ReplaceAll(outputStr, ""), true
|
||||||
// NOTE: if the output has a placeholder that has the same key as the input, this is infinite recursion
|
|
||||||
return repl.ReplaceAll(outputStr, ""), true
|
|
||||||
}
|
|
||||||
return output, true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -305,7 +305,7 @@ func (TemplateContext) funcStripHTML(s string) string {
|
||||||
// funcMarkdown renders the markdown body as HTML. The resulting
|
// funcMarkdown renders the markdown body as HTML. The resulting
|
||||||
// HTML is NOT escaped so that it can be rendered as HTML.
|
// HTML is NOT escaped so that it can be rendered as HTML.
|
||||||
func (TemplateContext) funcMarkdown(input any) (string, error) {
|
func (TemplateContext) funcMarkdown(input any) (string, error) {
|
||||||
inputStr := toString(input)
|
inputStr := caddy.ToString(input)
|
||||||
|
|
||||||
md := goldmark.New(
|
md := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
|
@ -341,7 +341,7 @@ func (TemplateContext) funcMarkdown(input any) (string, error) {
|
||||||
// and returns the separated key-value pairs and the body/content. input
|
// and returns the separated key-value pairs and the body/content. input
|
||||||
// must be a "stringy" value.
|
// must be a "stringy" value.
|
||||||
func (TemplateContext) funcSplitFrontMatter(input any) (parsedMarkdownDoc, error) {
|
func (TemplateContext) funcSplitFrontMatter(input any) (parsedMarkdownDoc, error) {
|
||||||
meta, body, err := extractFrontMatter(toString(input))
|
meta, body, err := extractFrontMatter(caddy.ToString(input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return parsedMarkdownDoc{}, err
|
return parsedMarkdownDoc{}, err
|
||||||
}
|
}
|
||||||
|
@ -465,19 +465,6 @@ func (h WrappedHeader) Del(field string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func toString(input any) string {
|
|
||||||
switch v := input.(type) {
|
|
||||||
case string:
|
|
||||||
return v
|
|
||||||
case fmt.Stringer:
|
|
||||||
return v.String()
|
|
||||||
case error:
|
|
||||||
return v.Error()
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%v", input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bufPool = sync.Pool{
|
var bufPool = sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
return new(bytes.Buffer)
|
return new(bytes.Buffer)
|
||||||
|
|
12
replacer.go
12
replacer.go
|
@ -78,11 +78,11 @@ func (r *Replacer) Get(variable string) (any, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString is the same as Get, but coerces the value to a
|
// GetString is the same as Get, but coerces the value to a
|
||||||
// string representation.
|
// string representation as efficiently as possible.
|
||||||
func (r *Replacer) GetString(variable string) (string, bool) {
|
func (r *Replacer) GetString(variable string) (string, bool) {
|
||||||
s, found := r.Get(variable)
|
s, found := r.Get(variable)
|
||||||
return toString(s), found
|
return ToString(s), found
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes a variable with a static value
|
// Delete removes a variable with a static value
|
||||||
|
@ -204,7 +204,7 @@ scan:
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert val to a string as efficiently as possible
|
// convert val to a string as efficiently as possible
|
||||||
valStr := toString(val)
|
valStr := ToString(val)
|
||||||
|
|
||||||
// write the value; if it's empty, either return
|
// write the value; if it's empty, either return
|
||||||
// an error or write a default value
|
// an error or write a default value
|
||||||
|
@ -230,7 +230,9 @@ scan:
|
||||||
return sb.String(), nil
|
return sb.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toString(val any) string {
|
// ToString returns val as a string, as efficiently as possible.
|
||||||
|
// EXPERIMENTAL: may be changed or removed later.
|
||||||
|
func ToString(val any) string {
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return ""
|
return ""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user