Files
Neil Twigg 208e82c53c Fix subject tree invalid handling of non-terminating full wildcard
A bug in `genParts` meant that the incorrect filter `foo.>.bar` could
match `bar` unexpectedly. This is now fixed by ensuring that the full
wildcard is handled as terminating in all cases.

Signed-off-by: Neil Twigg <[email protected]>
2025-11-27 13:37:00 +00:00

148 lines
4.2 KiB
Go

// Copyright 2023-2025 The NATS 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 stree
import (
"bytes"
)
// genParts will break a filter subject up into parts.
// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'.
// We do not care about other tokens per se, just parts that are separated by wildcards with an optional end fwc.
func genParts(filter []byte, parts [][]byte) [][]byte {
var start int
for i, e := 0, len(filter)-1; i < len(filter); i++ {
if filter[i] == tsep {
// See if next token is pwc. Either internal or end pwc.
if i < e && filter[i+1] == pwc && (i+2 <= e && filter[i+2] == tsep || i+1 == e) {
if i > start {
parts = append(parts, filter[start:i+1])
}
parts = append(parts, filter[i+1:i+2])
i++ // Skip pwc
if i+2 <= e {
i++ // Skip next tsep from next part too.
}
start = i + 1
} else if i < e && filter[i+1] == fwc && i+1 == e {
if i > start {
parts = append(parts, filter[start:i+1])
}
parts = append(parts, filter[i+1:i+2])
i++ // Skip fwc
start = i + 1
}
} else if filter[i] == pwc || filter[i] == fwc {
// Wildcard must be at the start or preceded by tsep.
if prev := i - 1; prev >= 0 && filter[prev] != tsep {
continue
}
// Wildcard must be at the end or followed by tsep.
if next := i + 1; next == e || next < e && filter[next] != tsep {
continue
}
// Full wildcard must be terminal.
if filter[i] == fwc && i < e {
break
}
// We start with a pwc or fwc.
parts = append(parts, filter[i:i+1])
if i+1 <= e {
i++ // Skip next tsep from next part too.
}
start = i + 1
}
}
if start < len(filter) {
// Check to see if we need to eat a leading tsep.
if filter[start] == tsep {
start++
}
parts = append(parts, filter[start:])
}
return parts
}
// Match our parts against a fragment, which could be prefix for nodes or a suffix for leafs.
func matchParts(parts [][]byte, frag []byte) ([][]byte, bool) {
lf := len(frag)
if lf == 0 {
return parts, true
}
var si int
lpi := len(parts) - 1
for i, part := range parts {
if si >= lf {
return parts[i:], true
}
lp := len(part)
// Check for pwc or fwc place holders.
if lp == 1 {
if part[0] == pwc {
index := bytes.IndexByte(frag[si:], tsep)
// We are trying to match pwc and did not find our tsep.
// Will need to move to next node from caller.
if index < 0 {
if i == lpi {
return nil, true
}
return parts[i:], true
}
si += index + 1
continue
} else if part[0] == fwc {
// If we are here we should be good.
return nil, true
}
}
end := min(si+lp, lf)
// If part is bigger then the remaining fragment, adjust to a portion on the part.
if si+lp > end {
// Frag is smaller then part itself.
part = part[:end-si]
}
if !bytes.Equal(part, frag[si:end]) {
return parts, false
}
// If we still have a portion of the fragment left, update and continue.
if end < lf {
si = end
continue
}
// If we matched a partial, do not move past current part
// but update the part to what was consumed. This allows upper layers to continue.
if end < si+lp {
if end >= lf {
// Create a copy before modifying. Reuse slice capacity available at the
// end of the parts slice, since this saves us additional allocations.
lp := len(parts)
parts = append(parts[lp:], parts[:lp]...)
parts[i] = parts[i][lf-si:]
} else {
i++
}
return parts[i:], true
}
if i == lpi {
return nil, true
}
// If we are here we are not the last part which means we have a wildcard
// gap, so we need to match anything up to next tsep.
si += len(part)
}
return parts, false
}