fix: prevent incorrect binding

This commit is contained in:
Brendan Le Glaunec
2026-01-28 20:25:33 +01:00
parent 77a2eac262
commit a867a606b2
2 changed files with 43 additions and 4 deletions
+31 -4
View File
@@ -60,7 +60,7 @@ func findChannelIncrement(route string) (incrementalRoute, bool) {
} }
pos += index pos += index
start, end, ok := firstNumberAfter(route, pos+len(pattern)) start, end, ok := firstNumberAfterKey(route, pos+len(pattern))
if ok { if ok {
num, width, parseOK := parseNumber(route, start, end) num, width, parseOK := parseNumber(route, start, end)
if parseOK { if parseOK {
@@ -131,23 +131,41 @@ func parseNumber(route string, start, end int) (int, int, bool) {
return num, len(value), true return num, len(value), true
} }
// firstNumberAfter returns the first numeric token after a given index. // firstNumberAfterKey returns the first numeric token after a keyword, limited to
func firstNumberAfter(route string, after int) (start, end int, ok bool) { // the current token and requiring an '=' delimiter (query param or path segment).
func firstNumberAfterKey(route string, after int) (start, end int, ok bool) {
if after < 0 { if after < 0 {
after = 0 after = 0
} }
tokenEnd := len(route)
for i := after; i < len(route); i++ { for i := after; i < len(route); i++ {
if isTokenDelimiter(route[i]) {
tokenEnd = i
break
}
}
relEq := strings.IndexByte(route[after:tokenEnd], '=')
searchStart := after
if relEq != -1 {
searchStart = after + relEq + 1
}
for i := searchStart; i < tokenEnd; i++ {
if !isDigit(route[i]) { if !isDigit(route[i]) {
if relEq == -1 {
break
}
continue continue
} }
end := i + 1 end := i + 1
for end < len(route) && isDigit(route[end]) { for end < tokenEnd && isDigit(route[end]) {
end++ end++
} }
return i, end, true return i, end, true
} }
return 0, 0, false return 0, 0, false
} }
@@ -163,3 +181,12 @@ func buildIncrementedRoute(match incrementalRoute, number int) string {
func isDigit(b byte) bool { func isDigit(b byte) bool {
return b >= '0' && b <= '9' return b >= '0' && b <= '9'
} }
func isTokenDelimiter(b byte) bool {
switch b {
case '&', '/', '?', '#':
return true
default:
return false
}
}
+12
View File
@@ -66,6 +66,18 @@ func TestDetectIncrementalRoute_OverflowAtEndFallsBack(t *testing.T) {
assert.Equal(t, "/bar999999999999999999999999999999", match.suffix) assert.Equal(t, "/bar999999999999999999999999999999", match.suffix)
} }
func TestDetectIncrementalRoute_ChannelKeywordShouldNotBindAcrossParams(t *testing.T) {
// The channel keyword should not bind to digits in other query parameters.
route := "/path?channelname=foo&version=12"
match, ok := detectIncrementalRoute(route)
require.True(t, ok)
assert.False(t, match.isChannel)
assert.Equal(t, 12, match.number)
assert.Equal(t, "/path?channelname=foo&version=", match.prefix)
assert.Equal(t, "", match.suffix)
}
func TestBuildIncrementedRoute_ZeroPadding(t *testing.T) { func TestBuildIncrementedRoute_ZeroPadding(t *testing.T) {
match := incrementalRoute{ match := incrementalRoute{
prefix: "/channel", prefix: "/channel",