Fix Bug #3122: Fix 'sync_list' globbing rule application (#3123)

* Add check for 'globbing' and 'wildcard' rules, that the number of segments before the first wildcard character need to match before the actual rule can be applied
* Only calculate ruleSegments and pathSegments once
* Use same calculation method to count path segments for applicability globbing rule as this is more consistent
This commit is contained in:
abraunegg 2025-02-23 05:35:41 +11:00 committed by GitHub
commit 8ecdbab20f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -335,7 +335,17 @@ class ClientSideFiltering {
// wildcard (*) rules are below if we get there, if this rule does not contain a wildcard
if ((to!string(syncListRuleEntry[0]) == "/") && (!canFind(syncListRuleEntry, wildcard))) {
// attempt to perform an exact segment match
if (exactMatchRuleSegmentsToPathSegments(syncListRuleEntry, path)) {
// split both paths by '/' to create segment arrays
string[] ruleSegments = syncListRuleEntry.strip.split("/").filter!(s => !s.empty).array;
string[] pathSegments = path.strip.split("/").filter!(s => !s.empty).array;
// Print rule and input segments for validation during debug
if (debugLogging) {
addLogEntry("Rule Segments: " ~ to!string(ruleSegments), ["debug"]);
addLogEntry("Path Segments: " ~ to!string(pathSegments), ["debug"]);
}
if (exactMatchRuleSegmentsToPathSegments(ruleSegments, pathSegments)) {
// EXACT PATH MATCH
if (debugLogging) {addLogEntry("Exact path match with 'sync_list' rule entry", ["debug"]);}
@ -359,19 +369,18 @@ class ClientSideFiltering {
} else {
// NOT an EXACT MATCH, so check the very first path segment
// - This is so that paths in 'sync_list' as specified as /some path/another path/ actually get included|excluded correctly
if (matchFirstSegmentToPathFirstSegment(syncListRuleEntry, path)) {
if (matchFirstSegmentToPathFirstSegment(ruleSegments, pathSegments)) {
// PARENT ROOT MATCH
if (debugLogging) {addLogEntry("Parent root path match with 'sync_list' rule entry", ["debug"]);}
// Does the 'rest' of the input path match?
// We only need to do this step if the input path has more and 1 segment (the parent folder)
auto inputSegments = path.strip.split("/").filter!(s => !s.empty).array;
if (count(inputSegments) > 1) {
if (count(pathSegments) > 1) {
// More segments to check, so do a parental path match
if (matchRuleSegmentsToPathSegments(syncListRuleEntry, path)) {
if (matchRuleSegmentsToPathSegments(ruleSegments, pathSegments)) {
// PARENTAL PATH MATCH
if (debugLogging) {addLogEntry("Parental path match with 'sync_list' rule entry", ["debug"]);}
// What sort of rule was this?
if (!thisIsAnExcludeRule) {
// Include Rule
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: parental path match", ["debug"]);}
@ -481,63 +490,78 @@ class ClientSideFiltering {
}
// Does the 'sync_list' rule contain a wildcard (*) or globbing (**) reference anywhere in the rule?
// EXCLUSION
// !/Programming/Projects/Android/**/build/*
// INCLUSION
// /Programming/Projects/Android/**/build/*
if (canFind(syncListRuleEntry, wildcard)) {
// reset the applicable flag
wildcardRuleMatched = false;
// sync_list rule contains some sort of wildcard sequence
if (thisIsAnExcludeRule) {
if (debugLogging) {addLogEntry("wildcard (* or **) exclusion rule: !" ~ syncListRuleEntry, ["debug"]);}
} else {
if (debugLogging) {addLogEntry("wildcard (* or **) inclusion rule: " ~ syncListRuleEntry, ["debug"]);}
}
// Is this a globbing rule (**) or just a single wildcard (*) entries
if (canFind(syncListRuleEntry, globbing)) {
// globbing (**) rule processing
if (matchPathAgainstRule(path, syncListRuleEntry)) {
// set the applicable flag
wildcardRuleMatched = true;
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: globbing pattern match", ["debug"]);}
}
// Does this 'wildcard' rule even apply to this path?
auto wildcardDepth = firstWildcardDepth(syncListRuleEntry);
auto pathSegments = count(path.strip.split("/").filter!(s => !s.empty).array);
// are there enough path segments for this wildcard rule to apply?
if (pathSegments < wildcardDepth) {
// there are not enough path segments up to the first wildcard character for this rule to even be applicable
if (debugLogging) {addLogEntry("- This sync list wildcard rule should not be evaluated as the wildcard appears beyond the current input path", ["debug"]);}
} else {
// wildcard (*) rule processing
// create regex from 'syncListRuleEntry'
auto allowedMask = regex(createRegexCompatiblePath(syncListRuleEntry));
if (matchAll(path, allowedMask)) {
// set the applicable flag
wildcardRuleMatched = true;
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard pattern match", ["debug"]);}
// path segments are enough for this wildcard rule to potentially apply
// sync_list rule contains some sort of wildcard sequence
if (thisIsAnExcludeRule) {
if (debugLogging) {addLogEntry("wildcard (* or **) exclusion rule: !" ~ syncListRuleEntry, ["debug"]);}
} else {
// matchAll no match ... try another way just to be sure
if (debugLogging) {addLogEntry("wildcard (* or **) inclusion rule: " ~ syncListRuleEntry, ["debug"]);}
}
// Is this a globbing rule (**) or just a single wildcard (*) entries
if (canFind(syncListRuleEntry, globbing)) {
// globbing (**) rule processing
if (matchPathAgainstRule(path, syncListRuleEntry)) {
// set the applicable flag
wildcardRuleMatched = true;
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard pattern match using segment matching", ["debug"]);}
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: globbing pattern match using segment matching", ["debug"]);}
}
} else {
// wildcard (*) rule processing
// create regex from 'syncListRuleEntry'
auto allowedMask = regex(createRegexCompatiblePath(syncListRuleEntry));
if (matchAll(path, allowedMask)) {
// set the applicable flag
wildcardRuleMatched = true;
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard pattern match", ["debug"]);}
} else {
// matchAll no match ... try another way just to be sure
if (matchPathAgainstRule(path, syncListRuleEntry)) {
// set the applicable flag
wildcardRuleMatched = true;
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard pattern match using segment matching", ["debug"]);}
}
}
}
}
// Was the rule matched?
if (wildcardRuleMatched) {
// Is this an exclude rule?
if (thisIsAnExcludeRule) {
// Yes exclude rule
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard|globbing rule matched and must be excluded", ["debug"]);}
excludeWildcardMatched = true;
exclude = true;
finalResult = true;
} else {
// include rule
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard|globbing pattern matched and must be included", ["debug"]);}
finalResult = false;
excludeWildcardMatched = false;
// Was the rule matched?
if (wildcardRuleMatched) {
// Is this an exclude rule?
if (thisIsAnExcludeRule) {
// Yes exclude rule
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard|globbing rule matched and must be excluded", ["debug"]);}
excludeWildcardMatched = true;
exclude = true;
finalResult = true;
} else {
// include rule
if (debugLogging) {addLogEntry("Evaluation against 'sync_list' rule result: wildcard|globbing pattern matched and must be included", ["debug"]);}
finalResult = false;
excludeWildcardMatched = false;
}
}
}
}
}
// debug logging post 'sync_list' rule evaluations
if (debugLogging) {
// Rule evaluation complete
addLogEntry("------------------------------------------------------------------------", ["debug"]);
@ -564,6 +588,18 @@ class ClientSideFiltering {
return finalResult;
}
// Calculate wildcard character depth in path
int firstWildcardDepth(string syncListRuleEntry) {
int depth = 0;
foreach (segment; pathSplitter(syncListRuleEntry))
{
if (segment.canFind("*")) // Check for wildcard characters
return depth;
depth++;
}
return depth; // No wildcard found should be '0'
}
// Create a wildcard regex compatible string based on the sync list rule
string createRegexCompatiblePath(string regexCompatiblePath) {
regexCompatiblePath = regexCompatiblePath.replace(".", "\\."); // Escape the dot (.) if present
@ -625,18 +661,9 @@ class ClientSideFiltering {
return j == ruleSegments.length || (j == ruleSegments.length - 1 && ruleSegments[j] == "**") || lastSegmentMatchesRule;
}
bool exactMatchRuleSegmentsToPathSegments(string rulePath, string inputPath) {
bool exactMatchRuleSegmentsToPathSegments(string[] ruleSegments, string[] inputSegments) {
if (debugLogging) {addLogEntry("Running exactMatchRuleSegmentsToPathSegments()", ["debug"]);}
// Split both paths by '/'
auto ruleSegments = rulePath.strip.split("/").filter!(s => !s.empty).array;
auto inputSegments = inputPath.strip.split("/").filter!(s => !s.empty).array;
// Print rule and input segments for validation
if (debugLogging) {
addLogEntry("Rule Segments: " ~ to!string(ruleSegments), ["debug"]);
addLogEntry("Input Segments: " ~ to!string(inputSegments), ["debug"]);
}
// If rule has more segments than input, or input has more segments than rule, no match is possible
if ((ruleSegments.length > inputSegments.length) || ( inputSegments.length > ruleSegments.length)) {
return false;
@ -655,18 +682,9 @@ class ClientSideFiltering {
return true;
}
bool matchRuleSegmentsToPathSegments(string rulePath, string inputPath) {
bool matchRuleSegmentsToPathSegments(string[] ruleSegments, string[] inputSegments) {
if (debugLogging) {addLogEntry("Running matchRuleSegmentsToPathSegments()", ["debug"]);}
// Split both paths by '/'
auto ruleSegments = rulePath.strip.split("/").filter!(s => !s.empty).array;
auto inputSegments = inputPath.strip.split("/").filter!(s => !s.empty).array;
// Print rule and input segments for validation
if (debugLogging) {
addLogEntry("Rule Segments: " ~ to!string(ruleSegments), ["debug"]);
addLogEntry("Input Segments: " ~ to!string(inputSegments), ["debug"]);
}
// If rule has more segments than input, no match is possible
if (ruleSegments.length > inputSegments.length) {
return false;
@ -676,18 +694,9 @@ class ClientSideFiltering {
return equal(ruleSegments, inputSegments[0 .. ruleSegments.length]);
}
bool matchFirstSegmentToPathFirstSegment(string rulePath, string inputPath) {
bool matchFirstSegmentToPathFirstSegment(string[] ruleSegments, string[] inputSegments) {
if (debugLogging) {addLogEntry("Running matchFirstSegmentToPathFirstSegment()", ["debug"]);}
// Split both paths by '/'
auto ruleSegments = rulePath.strip.split("/").filter!(s => !s.empty).array;
auto inputSegments = inputPath.strip.split("/").filter!(s => !s.empty).array;
// Print rule and input segments for validation
if (debugLogging) {
addLogEntry("Rule Segments: " ~ to!string(ruleSegments), ["debug"]);
addLogEntry("Input Segments: " ~ to!string(inputSegments), ["debug"]);
}
// Check that both segments are not empty
if (ruleSegments.length == 0 || inputSegments.length == 0) {
return false; // Return false if either segment array is empty