Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
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
Bill Glover committed
Mar 8, 2020 at 21:36 UTC
36a6c7daf0f45353efe860e254aa148b7574b04e
2 files changed
+127
-1
replacer.go
+14
-1
@@ -125,6 +125,14 @@ func (r *Replacer) replace(input, empty string,
125
// iterate the input to find each placeholder
126
var lastWriteCursor int
127
for i := 0; i < len(input); i++ {
128
+
129
+ // check for escaped braces
130
+ if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) {
131
+ sb.WriteString(input[lastWriteCursor : i-1])
132
+ lastWriteCursor = i
133
+ continue
134
+ }
135
+
136
if input[i] != phOpen {
137
continue
138
}
@@ -135,6 +143,11 @@ func (r *Replacer) replace(input, empty string,
143
continue
144
}
145
146
+ // if necessary look for the first closing brace that is not escaped
147
+ for end > 0 && end < len(input)-1 && input[end-1] == phEscape {
148
+ end = strings.Index(input[end+1:], string(phClose)) + end + 1
149
+ }
150
+
151
// write the substring from the last cursor to this point
152
sb.WriteString(input[lastWriteCursor:i])
153
@@ -237,4 +250,4 @@ var nowFunc = time.Now
250
// ReplacerCtxKey is the context key for a replacer.
251
const ReplacerCtxKey CtxKey = "replacer"
252
240
-const phOpen, phClose = '{', '}'
253
+const phOpen, phClose, phEscape = '{', '}', '\\'
replacer_test.go
+113
@@ -35,30 +35,86 @@ func TestReplacer(t *testing.T) {
35
input: "{",
36
expect: "{",
37
},
38
+ {
39
+ input: `\{`,
40
+ expect: `{`,
41
+ },
42
{
43
input: "foo{",
44
expect: "foo{",
45
},
46
+ {
47
+ input: `foo\{`,
48
+ expect: `foo{`,
49
+ },
50
{
51
input: "foo{bar",
52
expect: "foo{bar",
53
},
54
+ {
55
+ input: `foo\{bar`,
56
+ expect: `foo{bar`,
57
+ },
58
{
59
input: "foo{bar}",
60
expect: "foo",
61
},
62
+ {
63
+ input: `foo\{bar\}`,
64
+ expect: `foo{bar}`,
65
+ },
66
{
67
input: "}",
68
expect: "}",
69
},
70
+ {
71
+ input: `\}`,
72
+ expect: `\}`,
73
+ },
74
{
75
input: "{}",
76
expect: "",
77
},
78
+ {
79
+ input: `\{\}`,
80
+ expect: `{}`,
81
+ },
82
{
83
input: `{"json": "object"}`,
84
expect: "",
85
},
86
+ {
87
+ input: `\{"json": "object"}`,
88
+ expect: `{"json": "object"}`,
89
+ },
90
+ {
91
+ input: `\{"json": "object"\}`,
92
+ expect: `{"json": "object"}`,
93
+ },
94
+ {
95
+ input: `\{"json": "object{bar}"\}`,
96
+ expect: `{"json": "object"}`,
97
+ },
98
+ {
99
+ input: `\{"json": \{"nested": "object"\}\}`,
100
+ expect: `{"json": {"nested": "object"}}`,
101
+ },
102
+ {
103
+ input: `\{"json": \{"nested": "{bar}"\}\}`,
104
+ expect: `{"json": {"nested": ""}}`,
105
+ },
106
+ {
107
+ input: `pre \{"json": \{"nested": "{bar}"\}\}`,
108
+ expect: `pre {"json": {"nested": ""}}`,
109
+ },
110
+ {
111
+ input: `\{"json": \{"nested": "{bar}"\}\} post`,
112
+ expect: `{"json": {"nested": ""}} post`,
113
+ },
114
+ {
115
+ input: `pre \{"json": \{"nested": "{bar}"\}\} post`,
116
+ expect: `pre {"json": {"nested": ""}} post`,
117
+ },
118
{
119
input: `{{`,
120
expect: "{{",
@@ -67,11 +123,39 @@ func TestReplacer(t *testing.T) {
123
input: `{{}`,
124
expect: "",
125
},
126
+ {
127
+ input: `{"json": "object"\}`,
128
+ expect: "",
129
+ },
130
{
131
input: `{unknown}`,
132
empty: "-",
133
expect: "-",
134
},
135
+ {
136
+ input: `back\slashes`,
137
+ expect: `back\slashes`,
138
+ },
139
+ {
140
+ input: `double back\\slashes`,
141
+ expect: `double back\\slashes`,
142
+ },
143
+ {
144
+ input: `placeholder {with \{ brace} in name`,
145
+ expect: `placeholder in name`,
146
+ },
147
+ {
148
+ input: `placeholder {with \} brace} in name`,
149
+ expect: `placeholder in name`,
150
+ },
151
+ {
152
+ input: `placeholder {with \} \} braces} in name`,
153
+ expect: `placeholder in name`,
154
+ },
155
+ {
156
+ input: `\{'group':'default','max_age':3600,'endpoints':[\{'url':'https://some.domain.local/a/d/g'\}],'include_subdomains':true\}`,
157
+ expect: `{'group':'default','max_age':3600,'endpoints':[{'url':'https://some.domain.local/a/d/g'}],'include_subdomains':true}`,
158
+ },
159
} {
160
actual := rep.ReplaceAll(tc.input, tc.empty)
161
if actual != tc.expect {
@@ -81,6 +165,35 @@ func TestReplacer(t *testing.T) {
165
}
166
}
167
168
+func BenchmarkReplacer(b *testing.B) {
169
+ type testCase struct {
170
+ name, input, empty string
171
+ }
172
+
173
+ rep := testReplacer()
174
+
175
+ for _, bm := range []testCase{
176
+ {
177
+ name: "no placeholder",
178
+ input: `simple string`,
179
+ },
180
+ {
181
+ name: "placeholder",
182
+ input: `{"json": "object"}`,
183
+ },
184
+ {
185
+ name: "escaped placeholder",
186
+ input: `\{"json": \{"nested": "{bar}"\}\}`,
187
+ },
188
+ } {
189
+ b.Run(bm.name, func(b *testing.B) {
190
+ for i := 0; i < b.N; i++ {
191
+ rep.ReplaceAll(bm.input, bm.empty)
192
+ }
193
+ })
194
+ }
195
+}
196
+
197
func TestReplacerSet(t *testing.T) {
198
rep := testReplacer()
199