mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
36a6c7daf0
Fixes #3116 * Rework Replacer loop to ignore escaped braces * Add benchmark tests for replacer * Optimise handling of escaped braces * Handle escaped closing braces * Remove additional check for closing brace This commit removes the additional check for input in which the closing brace appears before the opening brace. This check has been removed for performance reasons as it is deemed an unlikely edge case. * Check for escaped closing braces in placeholder name
437 lines
9 KiB
Go
437 lines
9 KiB
Go
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package caddy
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
)
|
|
|
|
func TestReplacer(t *testing.T) {
|
|
type testCase struct {
|
|
input, expect, empty string
|
|
}
|
|
|
|
rep := testReplacer()
|
|
|
|
// ReplaceAll
|
|
for i, tc := range []testCase{
|
|
{
|
|
input: "{",
|
|
expect: "{",
|
|
},
|
|
{
|
|
input: `\{`,
|
|
expect: `{`,
|
|
},
|
|
{
|
|
input: "foo{",
|
|
expect: "foo{",
|
|
},
|
|
{
|
|
input: `foo\{`,
|
|
expect: `foo{`,
|
|
},
|
|
{
|
|
input: "foo{bar",
|
|
expect: "foo{bar",
|
|
},
|
|
{
|
|
input: `foo\{bar`,
|
|
expect: `foo{bar`,
|
|
},
|
|
{
|
|
input: "foo{bar}",
|
|
expect: "foo",
|
|
},
|
|
{
|
|
input: `foo\{bar\}`,
|
|
expect: `foo{bar}`,
|
|
},
|
|
{
|
|
input: "}",
|
|
expect: "}",
|
|
},
|
|
{
|
|
input: `\}`,
|
|
expect: `\}`,
|
|
},
|
|
{
|
|
input: "{}",
|
|
expect: "",
|
|
},
|
|
{
|
|
input: `\{\}`,
|
|
expect: `{}`,
|
|
},
|
|
{
|
|
input: `{"json": "object"}`,
|
|
expect: "",
|
|
},
|
|
{
|
|
input: `\{"json": "object"}`,
|
|
expect: `{"json": "object"}`,
|
|
},
|
|
{
|
|
input: `\{"json": "object"\}`,
|
|
expect: `{"json": "object"}`,
|
|
},
|
|
{
|
|
input: `\{"json": "object{bar}"\}`,
|
|
expect: `{"json": "object"}`,
|
|
},
|
|
{
|
|
input: `\{"json": \{"nested": "object"\}\}`,
|
|
expect: `{"json": {"nested": "object"}}`,
|
|
},
|
|
{
|
|
input: `\{"json": \{"nested": "{bar}"\}\}`,
|
|
expect: `{"json": {"nested": ""}}`,
|
|
},
|
|
{
|
|
input: `pre \{"json": \{"nested": "{bar}"\}\}`,
|
|
expect: `pre {"json": {"nested": ""}}`,
|
|
},
|
|
{
|
|
input: `\{"json": \{"nested": "{bar}"\}\} post`,
|
|
expect: `{"json": {"nested": ""}} post`,
|
|
},
|
|
{
|
|
input: `pre \{"json": \{"nested": "{bar}"\}\} post`,
|
|
expect: `pre {"json": {"nested": ""}} post`,
|
|
},
|
|
{
|
|
input: `{{`,
|
|
expect: "{{",
|
|
},
|
|
{
|
|
input: `{{}`,
|
|
expect: "",
|
|
},
|
|
{
|
|
input: `{"json": "object"\}`,
|
|
expect: "",
|
|
},
|
|
{
|
|
input: `{unknown}`,
|
|
empty: "-",
|
|
expect: "-",
|
|
},
|
|
{
|
|
input: `back\slashes`,
|
|
expect: `back\slashes`,
|
|
},
|
|
{
|
|
input: `double back\\slashes`,
|
|
expect: `double back\\slashes`,
|
|
},
|
|
{
|
|
input: `placeholder {with \{ brace} in name`,
|
|
expect: `placeholder in name`,
|
|
},
|
|
{
|
|
input: `placeholder {with \} brace} in name`,
|
|
expect: `placeholder in name`,
|
|
},
|
|
{
|
|
input: `placeholder {with \} \} braces} in name`,
|
|
expect: `placeholder in name`,
|
|
},
|
|
{
|
|
input: `\{'group':'default','max_age':3600,'endpoints':[\{'url':'https://some.domain.local/a/d/g'\}],'include_subdomains':true\}`,
|
|
expect: `{'group':'default','max_age':3600,'endpoints':[{'url':'https://some.domain.local/a/d/g'}],'include_subdomains':true}`,
|
|
},
|
|
} {
|
|
actual := rep.ReplaceAll(tc.input, tc.empty)
|
|
if actual != tc.expect {
|
|
t.Errorf("Test %d: '%s': expected '%s' but got '%s'",
|
|
i, tc.input, tc.expect, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkReplacer(b *testing.B) {
|
|
type testCase struct {
|
|
name, input, empty string
|
|
}
|
|
|
|
rep := testReplacer()
|
|
|
|
for _, bm := range []testCase{
|
|
{
|
|
name: "no placeholder",
|
|
input: `simple string`,
|
|
},
|
|
{
|
|
name: "placeholder",
|
|
input: `{"json": "object"}`,
|
|
},
|
|
{
|
|
name: "escaped placeholder",
|
|
input: `\{"json": \{"nested": "{bar}"\}\}`,
|
|
},
|
|
} {
|
|
b.Run(bm.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
rep.ReplaceAll(bm.input, bm.empty)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReplacerSet(t *testing.T) {
|
|
rep := testReplacer()
|
|
|
|
for _, tc := range []struct {
|
|
variable string
|
|
value string
|
|
}{
|
|
{
|
|
variable: "test1",
|
|
value: "val1",
|
|
},
|
|
{
|
|
variable: "asdf",
|
|
value: "123",
|
|
},
|
|
{
|
|
variable: "äöü",
|
|
value: "öö_äü",
|
|
},
|
|
{
|
|
variable: "with space",
|
|
value: "space value",
|
|
},
|
|
{
|
|
variable: "1",
|
|
value: "test-123",
|
|
},
|
|
{
|
|
variable: "mySuper_IP",
|
|
value: "1.2.3.4",
|
|
},
|
|
{
|
|
variable: "testEmpty",
|
|
value: "",
|
|
},
|
|
} {
|
|
rep.Set(tc.variable, tc.value)
|
|
|
|
// test if key is added
|
|
if val, ok := rep.static[tc.variable]; ok {
|
|
if val != tc.value {
|
|
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
|
|
}
|
|
} else {
|
|
t.Errorf("Expected existing key '%s' found nothing", tc.variable)
|
|
}
|
|
}
|
|
|
|
// test if all keys are still there (by length)
|
|
length := len(rep.static)
|
|
if len(rep.static) != 7 {
|
|
t.Errorf("Expected length '%v' got '%v'", 7, length)
|
|
}
|
|
}
|
|
|
|
func TestReplacerReplaceKnown(t *testing.T) {
|
|
rep := Replacer{
|
|
providers: []ReplacerFunc{
|
|
// split our possible vars to two functions (to test if both functions are called)
|
|
func(key string) (val string, ok bool) {
|
|
switch key {
|
|
case "test1":
|
|
return "val1", true
|
|
case "asdf":
|
|
return "123", true
|
|
case "äöü":
|
|
return "öö_äü", true
|
|
case "with space":
|
|
return "space value", true
|
|
default:
|
|
return "NOOO", false
|
|
}
|
|
},
|
|
func(key string) (val string, ok bool) {
|
|
switch key {
|
|
case "1":
|
|
return "test-123", true
|
|
case "mySuper_IP":
|
|
return "1.2.3.4", true
|
|
case "testEmpty":
|
|
return "", true
|
|
default:
|
|
return "NOOO", false
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
testInput string
|
|
expected string
|
|
}{
|
|
{
|
|
// test vars without space
|
|
testInput: "{test1}{asdf}{äöü}{1}{with space}{mySuper_IP}",
|
|
expected: "val1123öö_äütest-123space value1.2.3.4",
|
|
},
|
|
{
|
|
// test vars with space
|
|
testInput: "{test1} {asdf} {äöü} {1} {with space} {mySuper_IP} ",
|
|
expected: "val1 123 öö_äü test-123 space value 1.2.3.4 ",
|
|
},
|
|
{
|
|
// test with empty val
|
|
testInput: "{test1} {testEmpty} {asdf} {1} ",
|
|
expected: "val1 EMPTY 123 test-123 ",
|
|
},
|
|
{
|
|
// test vars with not finished placeholders
|
|
testInput: "{te{test1}{as{{df{1}",
|
|
expected: "{teval1{as{{dftest-123",
|
|
},
|
|
{
|
|
// test with non existing vars
|
|
testInput: "{test1} {nope} {1} ",
|
|
expected: "val1 {nope} test-123 ",
|
|
},
|
|
} {
|
|
actual := rep.ReplaceKnown(tc.testInput, "EMPTY")
|
|
|
|
// test if all are replaced as expected
|
|
if actual != tc.expected {
|
|
t.Errorf("Expected '%s' got '%s' for '%s'", tc.expected, actual, tc.testInput)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReplacerDelete(t *testing.T) {
|
|
rep := Replacer{
|
|
static: map[string]string{
|
|
"key1": "val1",
|
|
"key2": "val2",
|
|
"key3": "val3",
|
|
"key4": "val4",
|
|
},
|
|
}
|
|
|
|
startLen := len(rep.static)
|
|
|
|
toDel := []string{
|
|
"key2", "key4",
|
|
}
|
|
|
|
for _, key := range toDel {
|
|
rep.Delete(key)
|
|
|
|
// test if key is removed from static map
|
|
if _, ok := rep.static[key]; ok {
|
|
t.Errorf("Expected '%s' to be removed. It is still in static map.", key)
|
|
}
|
|
}
|
|
|
|
// check if static slice is smaller
|
|
expected := startLen - len(toDel)
|
|
actual := len(rep.static)
|
|
if len(rep.static) != expected {
|
|
t.Errorf("Expected length '%v' got length '%v'", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestReplacerMap(t *testing.T) {
|
|
rep := testReplacer()
|
|
|
|
for i, tc := range []ReplacerFunc{
|
|
func(key string) (val string, ok bool) {
|
|
return "", false
|
|
},
|
|
func(key string) (val string, ok bool) {
|
|
return "", false
|
|
},
|
|
} {
|
|
rep.Map(tc)
|
|
|
|
// test if function (which listens on specific key) is added by checking length
|
|
if len(rep.providers) == i+1 {
|
|
// check if the last function is the one we just added
|
|
pTc := fmt.Sprintf("%p", tc)
|
|
pRep := fmt.Sprintf("%p", rep.providers[i])
|
|
if pRep != pTc {
|
|
t.Errorf("Expected func pointer '%s' got '%s'", pTc, pRep)
|
|
}
|
|
} else {
|
|
t.Errorf("Expected providers length '%v' got length '%v'", i+1, len(rep.providers))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReplacerNew(t *testing.T) {
|
|
rep := NewReplacer()
|
|
|
|
if len(rep.providers) != 2 {
|
|
t.Errorf("Expected providers length '%v' got length '%v'", 2, len(rep.providers))
|
|
} else {
|
|
// test if default global replacements are added as the first provider
|
|
hostname, _ := os.Hostname()
|
|
os.Setenv("CADDY_REPLACER_TEST", "envtest")
|
|
defer os.Setenv("CADDY_REPLACER_TEST", "")
|
|
|
|
for _, tc := range []struct {
|
|
variable string
|
|
value string
|
|
}{
|
|
{
|
|
variable: "system.hostname",
|
|
value: hostname,
|
|
},
|
|
{
|
|
variable: "system.slash",
|
|
value: string(filepath.Separator),
|
|
},
|
|
{
|
|
variable: "system.os",
|
|
value: runtime.GOOS,
|
|
},
|
|
{
|
|
variable: "system.arch",
|
|
value: runtime.GOARCH,
|
|
},
|
|
{
|
|
variable: "env.CADDY_REPLACER_TEST",
|
|
value: "envtest",
|
|
},
|
|
} {
|
|
if val, ok := rep.providers[0](tc.variable); ok {
|
|
if val != tc.value {
|
|
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
|
|
}
|
|
} else {
|
|
t.Errorf("Expected key '%s' to be recognized by first provider", tc.variable)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func testReplacer() Replacer {
|
|
return Replacer{
|
|
providers: make([]ReplacerFunc, 0),
|
|
static: make(map[string]string),
|
|
}
|
|
}
|