1
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-16 21:56:40 -05:00
caddy/replacer_test.go
Bill Glover 36a6c7daf0
Rework Replacer loop to handle escaped braces (#3121)
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
2020-03-08 15:36:59 -06:00

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),
}
}