@marcel91200 / caddy Mirror · read-only / commits / 36a6c7da
Fork 0

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