mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-26 10:13:39 +08:00
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>
This commit is contained in:
parent
dc4d147388
commit
c5fffb4ac2
|
@ -19,6 +19,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -201,6 +202,43 @@ func (d *Dispenser) Val() string {
|
||||||
return d.tokens[d.cursor].Text
|
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.
|
// Line gets the line number of the current token.
|
||||||
// If there is no token loaded, it returns 0.
|
// If there is no token loaded, it returns 0.
|
||||||
func (d *Dispenser) Line() int {
|
func (d *Dispenser) Line() int {
|
||||||
|
@ -249,6 +287,19 @@ func (d *Dispenser) AllArgs(targets ...*string) bool {
|
||||||
return true
|
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)
|
// RemainingArgs loads any more arguments (tokens on the same line)
|
||||||
// into a slice and returns them. Open curly brace tokens also indicate
|
// 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 end of arguments, and the curly brace is not included in
|
||||||
|
@ -261,6 +312,18 @@ func (d *Dispenser) RemainingArgs() []string {
|
||||||
return args
|
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
|
// NewFromNextSegment returns a new dispenser with a copy of
|
||||||
// the tokens from the current token until the end of the
|
// the tokens from the current token until the end of the
|
||||||
// "directive" whether that be to the end of the line or
|
// "directive" whether that be to the end of the line or
|
||||||
|
|
|
@ -38,6 +38,7 @@ type (
|
||||||
File string
|
File string
|
||||||
Line int
|
Line int
|
||||||
Text string
|
Text string
|
||||||
|
wasQuoted rune // enclosing quote character, if any
|
||||||
inSnippet bool
|
inSnippet bool
|
||||||
snippetName string
|
snippetName string
|
||||||
}
|
}
|
||||||
|
@ -78,8 +79,9 @@ func (l *lexer) next() bool {
|
||||||
var val []rune
|
var val []rune
|
||||||
var comment, quoted, btQuoted, escaped bool
|
var comment, quoted, btQuoted, escaped bool
|
||||||
|
|
||||||
makeToken := func() bool {
|
makeToken := func(quoted rune) bool {
|
||||||
l.token.Text = string(val)
|
l.token.Text = string(val)
|
||||||
|
l.token.wasQuoted = quoted
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ func (l *lexer) next() bool {
|
||||||
ch, _, err := l.reader.ReadRune()
|
ch, _, err := l.reader.ReadRune()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
return makeToken()
|
return makeToken(0)
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return false
|
return false
|
||||||
|
@ -110,10 +112,10 @@ func (l *lexer) next() bool {
|
||||||
escaped = false
|
escaped = false
|
||||||
} else {
|
} else {
|
||||||
if quoted && ch == '"' {
|
if quoted && ch == '"' {
|
||||||
return makeToken()
|
return makeToken('"')
|
||||||
}
|
}
|
||||||
if btQuoted && ch == '`' {
|
if btQuoted && ch == '`' {
|
||||||
return makeToken()
|
return makeToken('`')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
|
@ -139,7 +141,7 @@ func (l *lexer) next() bool {
|
||||||
comment = false
|
comment = false
|
||||||
}
|
}
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
return makeToken()
|
return makeToken(0)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
114
caddytest/integration/caddyfile_adapt/expression_quotes.txt
Normal file
114
caddytest/integration/caddyfile_adapt/expression_quotes.txt
Normal file
|
@ -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
caddytest/integration/caddyfile_adapt/map_with_raw_types.txt
Normal file
107
caddytest/integration/caddyfile_adapt/map_with_raw_types.txt
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -150,7 +150,11 @@ func (m MatchExpression) Match(r *http.Request) bool {
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
for d.Next() {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
package maphandler
|
package maphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"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
|
// every other line maps one input to one or more outputs
|
||||||
in := h.Val()
|
in := h.Val()
|
||||||
var outs []interface{}
|
var outs []interface{}
|
||||||
for _, out := range h.RemainingArgs() {
|
for h.NextArg() {
|
||||||
if out == "-" {
|
val := h.ScalarVal()
|
||||||
|
if val == "-" {
|
||||||
outs = append(outs, nil)
|
outs = append(outs, nil)
|
||||||
} else {
|
} 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
|
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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user