Skip to content

Commit

Permalink
caddyfile: Support for raw token values, improve map, expression
Browse files Browse the repository at this point in the history
  • Loading branch information
francislavoie committed Mar 18, 2022
1 parent 93c99f6 commit 7a548b8
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 7 deletions.
25 changes: 25 additions & 0 deletions caddyconfig/caddyfile/dispenser.go
Expand Up @@ -201,6 +201,19 @@ func (d *Dispenser) Val() string {
return d.tokens[d.cursor].Text
}

// RawVal gets the raw text of the current token (including quotes).
// If there is no token loaded, it returns empty string.
func (d *Dispenser) RawVal() 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)
}
return d.tokens[d.cursor].Text
}

// Line gets the line number of the current token.
// If there is no token loaded, it returns 0.
func (d *Dispenser) Line() int {
Expand Down Expand Up @@ -261,6 +274,18 @@ func (d *Dispenser) RemainingArgs() []string {
return args
}

// RawRemainingArgs 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) RawRemainingArgs() []string {
var args []string
for d.NextArg() {
args = append(args, d.RawVal())
}
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
Expand Down
12 changes: 7 additions & 5 deletions caddyconfig/caddyfile/lexer.go
Expand Up @@ -38,6 +38,7 @@ type (
File string
Line int
Text string
wasQuoted rune
inSnippet bool
snippetName string
}
Expand Down Expand Up @@ -78,16 +79,17 @@ 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
}

for {
ch, _, err := l.reader.ReadRune()
if err != nil {
if len(val) > 0 {
return makeToken()
return makeToken(-1)
}
if err == io.EOF {
return false
Expand All @@ -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' {
Expand All @@ -139,7 +141,7 @@ func (l *lexer) next() bool {
comment = false
}
if len(val) > 0 {
return makeToken()
return makeToken(-1)
}
continue
}
Expand Down
114 changes: 114 additions & 0 deletions 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
}
]
}
}
}
}
}
107 changes: 107 additions & 0 deletions 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
}
]
}
}
}
}
}
11 changes: 10 additions & 1 deletion modules/caddyhttp/celmatcher.go
Expand Up @@ -150,8 +150,17 @@ 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(), " ")
m.Expr = strings.Join(d.RawRemainingArgs(), " ")
}

// Deal with the raw tokens potentially having been wrapped in quotes
if strings.HasPrefix(m.Expr, "`") && strings.HasSuffix(m.Expr, "`") {
m.Expr = strings.Trim(m.Expr, "`")
}
if strings.HasPrefix(m.Expr, "\"") && strings.HasSuffix(m.Expr, "\"") {
m.Expr = strings.Trim(m.Expr, "\"")
}

return nil
}

Expand Down
12 changes: 11 additions & 1 deletion modules/caddyhttp/map/caddyfile.go
Expand Up @@ -75,7 +75,7 @@ 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() {
for _, out := range h.RawRemainingArgs() {
if out == "-" {
outs = append(outs, nil)
} else {
Expand Down Expand Up @@ -109,7 +109,17 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
return handler, nil
}

// specificType parses the token string and casts it to
// the appropriate scalar type. Supports " and ` quoted
// strings, integers, floats, bool (true/false), and
// anything else is returned as a string.
func specificType(v string) interface{} {
if strings.HasPrefix(v, "\"") {
return strings.Trim(v, "\"")
}
if strings.HasPrefix(v, "`") {
return strings.Trim(v, "`")
}
if num, err := strconv.Atoi(v); err == nil {
return num
}
Expand Down

0 comments on commit 7a548b8

Please sign in to comment.