Split cookie names on both _ and - separators
splitTokens previously picked a single separator (_ winning over -). Cookies like __Secure-1PSID were split on _ only, leaving "Secure-1PSID" as one token that falsely passed looksVariable, producing the bogus ___* heuristic pattern. The function now splits by _ first, then sub-splits each non-UUID part by -, tracking per-gap separators. heuristicTemplate trims leading/trailing empty tokens before analysis so that prefix underscores are preserved in the output but do not pollute the variable detection. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -349,22 +349,48 @@ func findMergeGroups(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func heuristicTemplate(name string) (string, bool) {
|
func heuristicTemplate(name string) (string, bool) {
|
||||||
tokens, sep := splitTokens(name)
|
tokens, seps := splitTokens(name)
|
||||||
if sep == 0 {
|
if len(seps) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim leading empty tokens (e.g. "__Secure-..." yields ["", "", ...]).
|
||||||
|
var prefix string
|
||||||
|
for len(tokens) > 1 && tokens[0] == "" {
|
||||||
|
prefix += string(seps[0])
|
||||||
|
tokens = tokens[1:]
|
||||||
|
seps = seps[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim trailing empty tokens.
|
||||||
|
var suffix string
|
||||||
|
for len(tokens) > 1 && tokens[len(tokens)-1] == "" {
|
||||||
|
suffix = string(seps[len(seps)-1]) + suffix
|
||||||
|
tokens = tokens[:len(tokens)-1]
|
||||||
|
seps = seps[:len(seps)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(seps) == 0 {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
s := string(sep)
|
|
||||||
changed := false
|
changed := false
|
||||||
var result []string
|
var resultTokens []string
|
||||||
for _, t := range tokens {
|
var resultSeps []byte
|
||||||
|
for i, t := range tokens {
|
||||||
if looksVariable(t) {
|
if looksVariable(t) {
|
||||||
changed = true
|
changed = true
|
||||||
if len(result) == 0 || result[len(result)-1] != "*" {
|
if len(resultTokens) == 0 || resultTokens[len(resultTokens)-1] != "*" {
|
||||||
result = append(result, "*")
|
if i > 0 {
|
||||||
|
resultSeps = append(resultSeps, seps[i-1])
|
||||||
|
}
|
||||||
|
resultTokens = append(resultTokens, "*")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = append(result, t)
|
if i > 0 {
|
||||||
|
resultSeps = append(resultSeps, seps[i-1])
|
||||||
|
}
|
||||||
|
resultTokens = append(resultTokens, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +398,7 @@ func heuristicTemplate(name string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(result, s), true
|
return prefix + joinTokens(resultTokens, resultSeps) + suffix, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func templateCandidates(name string) []string {
|
func templateCandidates(name string) []string {
|
||||||
@@ -384,11 +410,12 @@ func templateCandidates(name string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens, sep := splitTokens(name)
|
tokens, seps := splitTokens(name)
|
||||||
if len(tokens) >= 3 && sep != 0 {
|
if len(tokens) >= 3 && len(seps) > 0 {
|
||||||
s := string(sep)
|
|
||||||
for pos := 1; pos < len(tokens)-1; pos++ {
|
for pos := 1; pos < len(tokens)-1; pos++ {
|
||||||
tmpl := strings.Join(tokens[:pos], s) + s + "*" + s + strings.Join(tokens[pos+1:], s)
|
left := joinTokens(tokens[:pos], seps[:pos-1])
|
||||||
|
right := joinTokens(tokens[pos+1:], seps[pos+1:])
|
||||||
|
tmpl := left + string(seps[pos-1]) + "*" + string(seps[pos]) + right
|
||||||
candidates = append(candidates, tmpl)
|
candidates = append(candidates, tmpl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,14 +489,45 @@ func isUUIDShape(s string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitTokens(name string) ([]string, byte) {
|
func splitTokens(name string) ([]string, []byte) {
|
||||||
if found := strings.Contains(name, "_"); found {
|
underscoreParts := strings.Split(name, "_")
|
||||||
return strings.Split(name, "_"), '_'
|
|
||||||
|
var tokens []string
|
||||||
|
var seps []byte
|
||||||
|
|
||||||
|
for i, part := range underscoreParts {
|
||||||
|
if i > 0 {
|
||||||
|
seps = append(seps, '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
if isUUIDShape(part) || !strings.Contains(part, "-") {
|
||||||
|
tokens = append(tokens, part)
|
||||||
|
} else {
|
||||||
|
for j, sub := range strings.Split(part, "-") {
|
||||||
|
if j > 0 {
|
||||||
|
seps = append(seps, '-')
|
||||||
|
}
|
||||||
|
tokens = append(tokens, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if found := strings.Contains(name, "-"); found {
|
|
||||||
return strings.Split(name, "-"), '-'
|
if len(seps) == 0 {
|
||||||
|
return []string{name}, nil
|
||||||
}
|
}
|
||||||
return []string{name}, 0
|
|
||||||
|
return tokens, seps
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinTokens(tokens []string, seps []byte) string {
|
||||||
|
var b strings.Builder
|
||||||
|
for i, t := range tokens {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteByte(seps[i-1])
|
||||||
|
}
|
||||||
|
b.WriteString(t)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func globMatch(pattern, name string) bool {
|
func globMatch(pattern, name string) bool {
|
||||||
|
|||||||
@@ -233,6 +233,11 @@ func TestHeuristicTemplate(t *testing.T) {
|
|||||||
template: "c15t-consent-*",
|
template: "c15t-consent-*",
|
||||||
changed: true,
|
changed: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "leading underscores with dash not variable",
|
||||||
|
input: "__Secure-1PSID",
|
||||||
|
changed: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -456,31 +461,43 @@ func TestSplitTokens(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
tokens []string
|
tokens []string
|
||||||
sep byte
|
seps []byte
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "underscore separator",
|
name: "underscore separator",
|
||||||
input: "ph_phc_abc",
|
input: "ph_phc_abc",
|
||||||
tokens: []string{"ph", "phc", "abc"},
|
tokens: []string{"ph", "phc", "abc"},
|
||||||
sep: '_',
|
seps: []byte{'_', '_'},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dash separator",
|
name: "dash separator",
|
||||||
input: "c15t-consent-abc",
|
input: "c15t-consent-abc",
|
||||||
tokens: []string{"c15t", "consent", "abc"},
|
tokens: []string{"c15t", "consent", "abc"},
|
||||||
sep: '-',
|
seps: []byte{'-', '-'},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no separator",
|
name: "no separator",
|
||||||
input: "PHPSESSID",
|
input: "PHPSESSID",
|
||||||
tokens: []string{"PHPSESSID"},
|
tokens: []string{"PHPSESSID"},
|
||||||
sep: 0,
|
seps: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "underscore takes priority over dash",
|
name: "mixed separators split both",
|
||||||
input: "foo_bar-baz",
|
input: "foo_bar-baz",
|
||||||
tokens: []string{"foo", "bar-baz"},
|
tokens: []string{"foo", "bar", "baz"},
|
||||||
sep: '_',
|
seps: []byte{'_', '-'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "leading underscores with dash",
|
||||||
|
input: "__Secure-1PSID",
|
||||||
|
tokens: []string{"", "", "Secure", "1PSID"},
|
||||||
|
seps: []byte{'_', '_', '-'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UUID preserved as single token",
|
||||||
|
input: "session_550e8400-e29b-41d4-a716-446655440000_data",
|
||||||
|
tokens: []string{"session", "550e8400-e29b-41d4-a716-446655440000", "data"},
|
||||||
|
seps: []byte{'_', '_'},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,9 +506,9 @@ func TestSplitTokens(t *testing.T) {
|
|||||||
tt.name,
|
tt.name,
|
||||||
func(t *testing.T) {
|
func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tokens, sep := splitTokens(tt.input)
|
tokens, seps := splitTokens(tt.input)
|
||||||
assert.Equal(t, tt.tokens, tokens)
|
assert.Equal(t, tt.tokens, tokens)
|
||||||
assert.Equal(t, tt.sep, sep)
|
assert.Equal(t, tt.seps, seps)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -879,6 +896,21 @@ func TestFindMergeGroups(t *testing.T) {
|
|||||||
assert.Len(t, statGroup, 3)
|
assert.Len(t, statGroup, 3)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"secure prefix cookies do not produce heuristic glob",
|
||||||
|
func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
patterns := coredata.TrackerPatterns{
|
||||||
|
makePattern("__Secure-1PSID", &oneYear),
|
||||||
|
makePattern("__Secure-1PSIDTS", &oneYear),
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := findMergeGroups(patterns, 3)
|
||||||
|
assert.Empty(t, groups)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDurationBucket(t *testing.T) {
|
func TestDurationBucket(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user