mirror of
https://github.com/hanzoai/pubsub.git
synced 2026-10-30 15:06:26 +00:00
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]>
148 lines
4.2 KiB
Go
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
|
|
}
|