Skip to content

Commit b7be6de

Browse files
committed
added escaping colon to avoid confusions between wildcards and and non-wildcards
1 parent 507b158 commit b7be6de

File tree

2 files changed

+62
-16
lines changed

2 files changed

+62
-16
lines changed

router/inbuilt/internal/radix/tree.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,9 @@ func (n *Node[T]) appendPredecessor(node *Node[T]) {
209209
n.predecessors = append(n.predecessors, node)
210210
}
211211

212-
func IsDynamicTemplate(str string) bool {
213-
return strings.IndexByte(str, ':') != -1
212+
func IsDynamicTemplate(path string) bool {
213+
segs := splitPath(path)
214+
return len(segs) > 1 || segs[0].IsWildcard
214215
}
215216

216217
func truncCommon(segs []pathSegment, length int) []pathSegment {
@@ -237,15 +238,26 @@ type pathSegment struct {
237238
Value string
238239
}
239240

240-
func splitPath(str string) (result []pathSegment) {
241+
func splitPath(str string) (path []pathSegment) {
242+
offset := 0
243+
241244
for len(str) > 0 {
242-
colon := strings.IndexByte(str, ':')
245+
colon := strings.IndexByte(str[offset:], ':')
243246
if colon == -1 {
244-
result = append(result, pathSegment{false, false, str})
247+
path = append(path, pathSegment{false, false, str})
245248
break
246249
}
247250

248-
result = append(result, pathSegment{false, false, str[:colon]})
251+
colon += offset
252+
253+
if colon > 0 && str[colon-1] == '\\' {
254+
// escaped colon: isn't a wildcard, skip
255+
str = str[:colon-1] + str[colon:]
256+
offset = colon
257+
continue
258+
}
259+
260+
path = append(path, pathSegment{false, false, str[:colon]})
249261
str = str[colon+1:]
250262

251263
boundary := strings.IndexByte(str, '/')
@@ -256,17 +268,17 @@ func splitPath(str string) (result []pathSegment) {
256268
wildcard := str[:boundary]
257269
greedy := false
258270
if strings.HasSuffix(wildcard, "...") {
259-
wildcard = wildcard[:len(wildcard)-3]
271+
wildcard = wildcard[:len(wildcard)-len("...")]
260272
greedy = true
261273
}
262274

263-
result = append(result, pathSegment{true, greedy, wildcard})
275+
path = append(path, pathSegment{true, greedy, wildcard})
264276
if boundary < len(str) {
265277
boundary++
266278
}
267279

268280
str = str[boundary:]
269281
}
270282

271-
return result
283+
return path
272284
}

router/inbuilt/internal/radix/tree_test.go

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ func TestTree(t *testing.T) {
141141
require.Equal(t, "wow", wildcards.Value("id"))
142142
})
143143

144+
test := func(t *testing.T, tree *Node[int], path string, value int, wKey, wVal string) {
145+
w := kv.New()
146+
val, found := tree.Lookup(path, w)
147+
require.True(t, found)
148+
require.Equal(t, value, val)
149+
require.Equal(t, wVal, w.Value(wKey))
150+
}
151+
144152
t.Run("dynamic in the middle", func(t *testing.T) {
145153
tree := New[int]()
146154
require.NoError(t, tree.Insert("/user/:id", 1))
@@ -220,14 +228,40 @@ func TestTree(t *testing.T) {
220228
test(t, tree, "/prefix42", 1, "path", "42")
221229
test(t, tree, "/prefixnowhere/like/this", 1, "path", "nowhere/like/this")
222230
})
223-
}
224231

225-
func test(t *testing.T, tree *Node[int], path string, value int, wKey, wVal string) {
226-
w := kv.New()
227-
val, found := tree.Lookup(path, w)
228-
require.True(t, found)
229-
require.Equal(t, value, val)
230-
require.Equal(t, wVal, w.Value(wKey))
232+
t.Run("escape wildcard", func(t *testing.T) {
233+
t.Run("static leftmost", func(t *testing.T) {
234+
tree := New[int]()
235+
require.NoError(t, tree.Insert("\\:hello/\\:world", 1))
236+
value, found := tree.Lookup(":hello/:world", nil)
237+
require.True(t, found)
238+
require.Equal(t, 1, value)
239+
})
240+
241+
t.Run("static rightmost", func(t *testing.T) {
242+
tree := New[int]()
243+
require.NoError(t, tree.Insert("/\\:hello", 1))
244+
value, found := tree.Lookup("/:hello", nil)
245+
require.True(t, found)
246+
require.Equal(t, 1, value)
247+
})
248+
249+
t.Run("dynamic", func(t *testing.T) {
250+
tree := New[int]()
251+
require.NoError(t, tree.Insert("/\\:hello/:name", 1))
252+
wildcards := kv.New()
253+
value, found := tree.Lookup("/:hello/Pavlo", wildcards)
254+
require.True(t, found)
255+
require.Equal(t, 1, value)
256+
require.Equal(t, "Pavlo", wildcards.Value("name"))
257+
})
258+
})
259+
260+
t.Run("path classifier", func(t *testing.T) {
261+
require.False(t, IsDynamicTemplate("\\:hello/\\:world"))
262+
require.False(t, IsDynamicTemplate("/\\:hello"))
263+
require.True(t, IsDynamicTemplate("/\\:hello/:name"))
264+
})
231265
}
232266

233267
// isn't used anymore. Left just in case the tree needs to be debugged.

0 commit comments

Comments
 (0)