Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

caddyfile: Support for raw token values, improve map, expression #4643

Merged
merged 3 commits into from Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
63 changes: 63 additions & 0 deletions caddyconfig/caddyfile/dispenser.go
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"io"
"log"
"strconv"
"strings"
)

Expand Down Expand Up @@ -201,6 +202,43 @@ 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 {
mholt marked this conversation as resolved.
Show resolved Hide resolved
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{} {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good name choice, but TypedVal() also came to mind. Not sure which is better. I'm fine with this, but wanted to ask anyway to see what you thought.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Typed would be better if we supported returning somekind of struct, but I think Scalar is clearer that only scalars will be returned (for now)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, and that's what I was wondering too, if in the future we'd ever deserialize a string as a structured data type. But I can't see that happening in the near future nor do I have any idea why or what that would look like. So Scalar is fine... hopefully we don't have to change it.

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 {
mholt marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down Expand Up @@ -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
Expand All @@ -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.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 // enclosing quote character, if any
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(0)
}
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(0)
}
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
}
]
}
}
}
}
}
6 changes: 5 additions & 1 deletion modules/caddyhttp/celmatcher.go
Expand Up @@ -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
}
Expand Down