1
0
mirror of https://github.com/MetaCubeX/mihomo.git synced 2025-09-20 04:25:59 +08:00

fix: ? in DOMAIN-WILDCARD should match exactly one character

https://github.com/MetaCubeX/mihomo/issues/2204
This commit is contained in:
wwqgtxx
2025-08-11 15:29:50 +08:00
parent d7999a32d3
commit dc52c38179
2 changed files with 36 additions and 62 deletions

View File

@ -1,3 +1,12 @@
// Package wildcard modified IGLOU-EU/go-wildcard to support:
//
// `*` matches zero or more characters
// `?` matches exactly one character
//
// The original go-wildcard library used `.` to match exactly one character, and `?` to match zero or one character.
// `.` is a valid delimiter in domain name matching and should not be used as a wildcard.
// The `?` matching logic strictly matches only one character in most scenarios.
// So, the `?` matching logic in the original go-wildcard library has been removed and its wildcard `.` has been replaced with `?`.
package wildcard package wildcard
// copy and modified from https://github.com/IGLOU-EU/go-wildcard/tree/ce22b7af48e487517a492d3727d9386492043e21 // copy and modified from https://github.com/IGLOU-EU/go-wildcard/tree/ce22b7af48e487517a492d3727d9386492043e21
@ -16,12 +25,10 @@ func Match(pattern, s string) bool {
} }
func matchByString(pattern, s string) bool { func matchByString(pattern, s string) bool {
var lastErotemeCluster byte var patternIndex, sIndex, lastStar int
var patternIndex, sIndex, lastStar, lastEroteme int
patternLen := len(pattern) patternLen := len(pattern)
sLen := len(s) sLen := len(s)
star := -1 star := -1
eroteme := -1
Loop: Loop:
if sIndex >= sLen { if sIndex >= sLen {
@ -38,14 +45,8 @@ Loop:
return false return false
} }
switch pattern[patternIndex] { switch pattern[patternIndex] {
// Removed dot matching as it conflicts with dot in domains.
// case '.':
// It matches any single character. So, we don't need to check anything.
case '?': case '?':
// '?' matches one character. Store its position and match exactly one character in the string. // It matches any single character. So, we don't need to check anything.
eroteme = patternIndex
lastEroteme = sIndex
lastErotemeCluster = byte(s[sIndex])
case '*': case '*':
// '*' matches zero or more characters. Store its position and increment the pattern index. // '*' matches zero or more characters. Store its position and increment the pattern index.
star = patternIndex star = patternIndex
@ -53,15 +54,8 @@ Loop:
patternIndex++ patternIndex++
goto Loop goto Loop
default: default:
// If the characters don't match, check if there was a previous '?' or '*' to backtrack. // If the characters don't match, check if there was a previous '*' to backtrack.
if pattern[patternIndex] != s[sIndex] { if pattern[patternIndex] != s[sIndex] {
if eroteme != -1 {
patternIndex = eroteme + 1
sIndex = lastEroteme
eroteme = -1
goto Loop
}
if star != -1 { if star != -1 {
patternIndex = star + 1 patternIndex = star + 1
lastStar++ lastStar++
@ -71,29 +65,18 @@ Loop:
return false return false
} }
// If the characters match, check if it was not the same to validate the eroteme.
if eroteme != -1 && lastErotemeCluster != byte(s[sIndex]) {
eroteme = -1
}
} }
patternIndex++ patternIndex++
sIndex++ sIndex++
goto Loop goto Loop
// Check if the remaining pattern characters are '*' or '?', which can match the end of the string. // Check if the remaining pattern characters are '*', which can match the end of the string.
checkPattern: checkPattern:
if patternIndex < patternLen { if patternIndex < patternLen {
if pattern[patternIndex] == '*' { if pattern[patternIndex] == '*' {
patternIndex++ patternIndex++
goto checkPattern goto checkPattern
} else if pattern[patternIndex] == '?' {
if sIndex >= sLen {
sIndex--
}
patternIndex++
goto checkPattern
} }
} }

View File

@ -25,31 +25,17 @@ func TestMatch(t *testing.T) {
{"", "", true}, {"", "", true},
{"", "*", true}, {"", "*", true},
{"", "**", true}, {"", "**", true},
{"", "?", true}, {"", "?", false},
{"", "??", true}, {"", "?*", false},
{"", "?*", true}, {"", "*?", false},
{"", "*?", true},
{"", ".", false},
{"", ".?", false},
{"", "?.", false},
{"", ".*", false},
{"", "*.", false},
{"", "*.?", false},
{"", "?.*", false},
{"a", "", false}, {"a", "", false},
{"a", "a", true}, {"a", "a", true},
{"a", "*", true}, {"a", "*", true},
{"a", "**", true}, {"a", "**", true},
{"a", "?", true}, {"a", "?", true},
{"a", "??", true}, {"a", "?*", true},
{"a", ".", false}, {"a", "*?", true},
{"a", ".?", false},
{"a", "?.", false},
{"a", ".*", false},
{"a", "*.", false},
{"a", "*.?", false},
{"a", "?.*", false},
{"match the exact string", "match the exact string", true}, {"match the exact string", "match the exact string", true},
{"do not match a different string", "this is a different string", false}, {"do not match a different string", "this is a different string", false},
@ -68,22 +54,27 @@ func TestMatch(t *testing.T) {
{"match a string with a ?", "match ? string with a ?", true}, {"match a string with a ?", "match ? string with a ?", true},
{"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", true}, {"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", true},
{"match a string with two ?", "match a string with two ??", true}, {"match a string with two ?", "match a ??ring with two ?", true},
{"match a optional char with a ?", "match a optional? char with a ?", true}, {"do not match a string with extra ?", "do not match a string with extra ??", false},
{"match a optional char with a ?", "match a optional? char with a ?", true},
{"do not match a string with extra and a ?", "do not match ? string with extra and a ? like this", false},
{"do not match a string with a .", "do not match . string with a .", false}, {"abc.edf.hjg", "abc.edf.hjg", true},
{"do not match a string with a . at the beginning", "do not .atch a string with a . at the beginning", false}, {"abc.edf.hjg", "ab.cedf.hjg", false},
{"do not match a string with two .", "do not match a ..ring with two .", false}, {"abc.edf.hjg", "abc.edfh.jg", false},
{"do not match a string with extra .", "do not match a string with extra ..", false}, {"abc.edf.hjg", "abc.edf.hjq", false},
{"A big brown fox jumps over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", false}, {"abc.edf.hjg", "abc.*.hjg", true},
{"A big brown fox fails to jump over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", false}, {"abc.edf.hjg", "abc.*.hjq", false},
{"abc.edf.hjg", "abc*hjg", true},
{"abc.edf.hjg", "abc*hjq", false},
{"abc.edf.hjg", "a*g", true},
{"abc.edf.hjg", "a*q", false},
{"domain a.b.c", "domain a.b.c", true}, {"abc.edf.hjg", "ab?.edf.hjg", true},
{"domain adb.c", "domain a.b.c", false}, {"abc.edf.hjg", "?b?.edf.hjg", true},
{"aaaa", "a*a", true}, {"abc.edf.hjg", "??c.edf.hjg", true},
{"abc.edf.hjg", "a??.edf.hjg", true},
{"abc.edf.hjg", "ab??.edf.hjg", false},
{"abc.edf.hjg", "??.edf.hjg", false},
} }
for i, c := range cases { for i, c := range cases {