summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancis Lavoie <lavofr@gmail.com>2022-03-18 17:08:23 -0400
committerGitHub <noreply@github.com>2022-03-18 15:08:23 -0600
commitc5fffb4ac2631f0b41a8e13b62925b9dc8346cb9 (patch)
treec63bee8e93dd3b7b7544e18602a8606c3a38570d
parentdc4d147388547515f77447d594024386b732e7d4 (diff)
caddyfile: Support for raw token values; improve `map`, `expression` (#4643)
* caddyfile: Support for raw token values, improve `map`, `expression` * Applied code review comments * Rename RawVal to ValRaw Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
-rwxr-xr-xcaddyconfig/caddyfile/dispenser.go63
-rwxr-xr-xcaddyconfig/caddyfile/lexer.go12
-rw-r--r--caddytest/integration/caddyfile_adapt/expression_quotes.txt114
-rw-r--r--caddytest/integration/caddyfile_adapt/map_with_raw_types.txt107
-rw-r--r--modules/caddyhttp/celmatcher.go6
-rw-r--r--modules/caddyhttp/map/caddyfile.go21
6 files changed, 300 insertions, 23 deletions
diff --git a/caddyconfig/caddyfile/dispenser.go b/caddyconfig/caddyfile/dispenser.go
index 23f6ead..fbe71ad 100755
--- a/caddyconfig/caddyfile/dispenser.go
+++ b/caddyconfig/caddyfile/dispenser.go
@@ -19,6 +19,7 @@ import (
"fmt"
"io"
"log"
+ "strconv"
"strings"
)
@@ -201,6 +202,43 @@ func (d *Dispenser) Val() string {
return d.tokens[d.cursor].Text
}
+// ValRaw gets the raw text of the current token (including quotes).
+// If there is no token loaded, it returns empty string.
+func (d *Dispenser) ValRaw() string {
+ if d.cursor < 0 || d.cursor >= len(d.tokens) {
+ return ""
+ }
+ quote := d.tokens[d.cursor].wasQuoted
+ if quote > 0 {
+ return string(quote) + d.tokens[d.cursor].Text + string(quote) // string literal
+ }
+ return d.tokens[d.cursor].Text
+}
+
+// ScalarVal gets value of the current token, converted to the closest
+// scalar type. If there is no token loaded, it returns nil.
+func (d *Dispenser) ScalarVal() interface{} {
+ if d.cursor < 0 || d.cursor >= len(d.tokens) {
+ return nil
+ }
+ quote := d.tokens[d.cursor].wasQuoted
+ text := d.tokens[d.cursor].Text
+
+ if quote > 0 {
+ return text // string literal
+ }
+ if num, err := strconv.Atoi(text); err == nil {
+ return num
+ }
+ if num, err := strconv.ParseFloat(text, 64); err == nil {
+ return num
+ }
+ if bool, err := strconv.ParseBool(text); err == nil {
+ return bool
+ }
+ return text
+}
+
// Line gets the line number of the current token.
// If there is no token loaded, it returns 0.
func (d *Dispenser) Line() int {
@@ -249,6 +287,19 @@ func (d *Dispenser) AllArgs(targets ...*string) bool {
return true
}
+// CountRemainingArgs counts the amount of remaining arguments
+// (tokens on the same line) without consuming the tokens.
+func (d *Dispenser) CountRemainingArgs() int {
+ count := 0
+ for d.NextArg() {
+ count++
+ }
+ for i := 0; i < count; i++ {
+ d.Prev()
+ }
+ return count
+}
+
// RemainingArgs loads any more arguments (tokens on the same line)
// into a slice and returns them. Open curly brace tokens also indicate
// the end of arguments, and the curly brace is not included in
@@ -261,6 +312,18 @@ func (d *Dispenser) RemainingArgs() []string {
return args
}
+// RemainingArgsRaw loads any more arguments (tokens on the same line,
+// retaining quotes) into a slice and returns them. Open curly brace
+// tokens also indicate the end of arguments, and the curly brace is
+// not included in the return value nor is it loaded.
+func (d *Dispenser) RemainingArgsRaw() []string {
+ var args []string
+ for d.NextArg() {
+ args = append(args, d.ValRaw())
+ }
+ return args
+}
+
// NewFromNextSegment returns a new dispenser with a copy of
// the tokens from the current token until the end of the
// "directive" whether that be to the end of the line or
diff --git a/caddyconfig/caddyfile/lexer.go b/caddyconfig/caddyfile/lexer.go
index 968277f..4a23524 100755
--- a/caddyconfig/caddyfile/lexer.go
+++ b/caddyconfig/caddyfile/lexer.go
@@ -38,6 +38,7 @@ type (
File string
Line int
Text string
+ wasQuoted rune // enclosing quote character, if any
inSnippet bool
snippetName string
}
@@ -78,8 +79,9 @@ func (l *lexer) next() bool {
var val []rune
var comment, quoted, btQuoted, escaped bool
- makeToken := func() bool {
+ makeToken := func(quoted rune) bool {
l.token.Text = string(val)
+ l.token.wasQuoted = quoted
return true
}
@@ -87,7 +89,7 @@ func (l *lexer) next() bool {
ch, _, err := l.reader.ReadRune()
if err != nil {
if len(val) > 0 {
- return makeToken()
+ return makeToken(0)
}
if err == io.EOF {
return false
@@ -110,10 +112,10 @@ func (l *lexer) next() bool {
escaped = false
} else {
if quoted && ch == '"' {
- return makeToken()
+ return makeToken('"')
}
if btQuoted && ch == '`' {
- return makeToken()
+ return makeToken('`')
}
}
if ch == '\n' {
@@ -139,7 +141,7 @@ func (l *lexer) next() bool {
comment = false
}
if len(val) > 0 {
- return makeToken()
+ return makeToken(0)
}
continue
}
diff --git a/caddytest/integration/caddyfile_adapt/expression_quotes.txt b/caddytest/integration/caddyfile_adapt/expression_quotes.txt
new file mode 100644
index 0000000..f5f8983
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/expression_quotes.txt
@@ -0,0 +1,114 @@
+example.com
+
+@a expression {http.error.status_code} == 400
+abort @a
+
+@b expression {http.error.status_code} == "401"
+abort @b
+
+@c expression {http.error.status_code} == `402`
+abort @c
+
+@d expression "{http.error.status_code} == 403"
+abort @d
+
+@e expression `{http.error.status_code} == 404`
+abort @e
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "example.com"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "abort": true,
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} == 400"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "abort": true,
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} == \"401\""
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "abort": true,
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} == `402`"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "abort": true,
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} == 403"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "abort": true,
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} == 404"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt b/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt
new file mode 100644
index 0000000..54b2b60
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt
@@ -0,0 +1,107 @@
+example.com
+
+map {host} {my_placeholder} {magic_number} {
+ # Should output boolean "true" and an integer
+ example.com true 3
+
+ # Should output a string and null
+ foo.example.com "string value"
+
+ # Should output two strings (quoted int)
+ (.*)\.example.com "${1} subdomain" "5"
+
+ # Should output null and a string (quoted int)
+ ~.*\.net$ - `7`
+
+ # Should output a float and the string "false"
+ ~.*\.xyz$ 123.456 "false"
+
+ # Should output two strings, second being escaped quote
+ default "unknown domain" \"""
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "example.com"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "defaults": [
+ "unknown domain",
+ "\""
+ ],
+ "destinations": [
+ "{my_placeholder}",
+ "{magic_number}"
+ ],
+ "handler": "map",
+ "mappings": [
+ {
+ "input": "example.com",
+ "outputs": [
+ true,
+ 3
+ ]
+ },
+ {
+ "input": "foo.example.com",
+ "outputs": [
+ "string value",
+ null
+ ]
+ },
+ {
+ "input": "(.*)\\.example.com",
+ "outputs": [
+ "${1} subdomain",
+ "5"
+ ]
+ },
+ {
+ "input_regexp": ".*\\.net$",
+ "outputs": [
+ null,
+ "7"
+ ]
+ },
+ {
+ "input_regexp": ".*\\.xyz$",
+ "outputs": [
+ 123.456,
+ "false"
+ ]
+ }
+ ],
+ "source": "{http.request.host}"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go
index d7d55d8..0306f39 100644
--- a/modules/caddyhttp/celmatcher.go
+++ b/modules/caddyhttp/celmatcher.go
@@ -150,7 +150,11 @@ func (m MatchExpression) Match(r *http.Request) bool {
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
- m.Expr = strings.Join(d.RemainingArgs(), " ")
+ if d.CountRemainingArgs() > 1 {
+ m.Expr = strings.Join(d.RemainingArgsRaw(), " ")
+ } else {
+ m.Expr = d.Val()
+ }
}
return nil
}
diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go
index a7f809b..8394b21 100644
--- a/modules/caddyhttp/map/caddyfile.go
+++ b/modules/caddyhttp/map/caddyfile.go
@@ -15,7 +15,6 @@
package maphandler
import (
- "strconv"
"strings"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
@@ -75,11 +74,12 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// every other line maps one input to one or more outputs
in := h.Val()
var outs []interface{}
- for _, out := range h.RemainingArgs() {
- if out == "-" {
+ for h.NextArg() {
+ val := h.ScalarVal()
+ if val == "-" {
outs = append(outs, nil)
} else {
- outs = append(outs, specificType(out))
+ outs = append(outs, val)
}
}
@@ -108,16 +108,3 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
return handler, nil
}
-
-func specificType(v string) interface{} {
- if num, err := strconv.Atoi(v); err == nil {
- return num
- }
- if num, err := strconv.ParseFloat(v, 64); err == nil {
- return num
- }
- if bool, err := strconv.ParseBool(v); err == nil {
- return bool
- }
- return v
-}