diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs
index 80f7bbf69..d3ed4ba93 100644
--- a/Duplicati/Library/Utility/FilterExpression.cs
+++ b/Duplicati/Library/Utility/FilterExpression.cs
@@ -48,13 +48,23 @@ namespace Duplicati.Library.Utility
/// Represents a filter that can comprise multiple filter strings
///
public class FilterExpression : IFilter
- {
+ {
private struct FilterEntry
{
public readonly FilterType Type;
public readonly string Filter;
public readonly System.Text.RegularExpressions.Regex Regexp;
+ ///
+ /// The single wildcard character (DOS style)
+ ///
+ private const char SINGLE_WILDCARD = '?';
+ ///
+ /// The multiple wildcard character (DOS style)
+ ///
+ private const char MULTIPLE_WILDCARD = '*';
+
+
private static readonly System.Text.RegularExpressions.RegexOptions REGEXP_OPTIONS = Library.Utility.Utility.IsFSCaseSensitive ? System.Text.RegularExpressions.RegexOptions.Compiled : System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase;
public FilterEntry(string filter)
@@ -73,12 +83,92 @@ namespace Duplicati.Library.Utility
}
else
{
- this.Type = (filter.Contains("*") || filter.Contains("?")) ? FilterType.Wildcard : FilterType.Simple;
- this.Filter = filter;
+ this.Type = (filter.Contains(MULTIPLE_WILDCARD) || filter.Contains(SINGLE_WILDCARD)) ? FilterType.Wildcard : FilterType.Simple;
+ this.Filter = Utility.IsFSCaseSensitive ? filter.ToUpper() : filter;
this.Regexp = new System.Text.RegularExpressions.Regex(Library.Utility.Utility.ConvertGlobbingToRegExp(filter), REGEXP_OPTIONS);
}
}
+ ///
+ /// Tests whether specified string can be matched agains provided pattern string. Pattern may contain single- and multiple-replacing
+ /// wildcard characters.
+ ///
+ /// String which is matched against the pattern.
+ /// Pattern against which string is matched.
+ /// true if matches the string ; otherwise false.
+ /// From: http://www.c-sharpcorner.com/uploadfile/b81385/efficient-string-matching-algorithm-with-use-of-wildcard-characters/
+ private static bool IsWildcardMatch(string input, string pattern)
+ {
+ int[] inputPosStack = new int[(input.Length + 1) * (pattern.Length + 1)]; // Stack containing input positions that should be tested for further matching
+ int[] patternPosStack = new int[inputPosStack.Length]; // Stack containing pattern positions that should be tested for further matching
+ int stackPos = -1; // Points to last occupied entry in stack; -1 indicates that stack is empty
+ bool[,] pointTested = new bool[input.Length + 1, pattern.Length + 1]; // Each true value indicates that input position vs. pattern position has been tested
+ int inputPos = 0; // Position in input matched up to the first multiple wildcard in pattern
+ int patternPos = 0; // Position in pattern matched up to the first multiple wildcard in pattern
+ // Match beginning of the string until first multiple wildcard in pattern
+ while (inputPos < input.Length && patternPos < pattern.Length && pattern[patternPos] != MULTIPLE_WILDCARD && (input[inputPos] == pattern[patternPos] || pattern[patternPos] == SINGLE_WILDCARD))
+ {
+ inputPos++;
+ patternPos++;
+ }
+
+
+ // Push this position to stack if it points to end of pattern or to a general wildcard
+ if (patternPos == pattern.Length || pattern[patternPos] == MULTIPLE_WILDCARD)
+ {
+ pointTested[inputPos, patternPos] = true;
+ inputPosStack[++stackPos] = inputPos;
+ patternPosStack[stackPos] = patternPos;
+ }
+ bool matched = false;
+ // Repeat matching until either string is matched against the pattern or no more parts remain on stack to test
+ while (stackPos >= 0 && !matched)
+ {
+ inputPos = inputPosStack[stackPos]; // Pop input and pattern positions from stack
+ patternPos = patternPosStack[stackPos--]; // Matching will succeed if rest of the input string matches rest of the pattern
+
+ // Modified from original version to match zero or more characters
+ //if (inputPos == input.Length && patternPos == pattern.Length)
+
+ if (inputPos == input.Length && (patternPos == pattern.Length || (patternPos == pattern.Length - 1 && pattern[patternPos] == MULTIPLE_WILDCARD)))
+ matched = true; // Reached end of both pattern and input string, hence matching is successful
+ else
+ {
+ // First character in next pattern block is guaranteed to be multiple wildcard
+ // So skip it and search for all matches in value string until next multiple wildcard character is reached in pattern
+ for(int curInputStart = inputPos; curInputStart < input.Length; curInputStart++)
+ {
+ int curInputPos = curInputStart;
+ int curPatternPos = patternPos + 1;
+ if (curPatternPos == pattern.Length)
+ { // Pattern ends with multiple wildcard, hence rest of the input string is matched with that character
+ curInputPos = input.Length;
+ }
+ else
+ {
+ while (curInputPos < input.Length && curPatternPos < pattern.Length && pattern[curPatternPos] != MULTIPLE_WILDCARD &&
+ (input[curInputPos] == pattern[curPatternPos] || pattern[curPatternPos] == SINGLE_WILDCARD))
+ {
+ curInputPos++;
+ curPatternPos++;
+ }
+ }
+ // If we have reached next multiple wildcard character in pattern without breaking the matching sequence, then we have another candidate for full match
+ // This candidate should be pushed to stack for further processing
+ // At the same time, pair (input position, pattern position) will be marked as tested, so that it will not be pushed to stack later again
+ if (((curPatternPos == pattern.Length && curInputPos == input.Length) || (curPatternPos < pattern.Length && pattern[curPatternPos] == MULTIPLE_WILDCARD))
+ && !pointTested[curInputPos, curPatternPos])
+ {
+ pointTested[curInputPos, curPatternPos] = true;
+ inputPosStack[++stackPos] = curInputPos;
+ patternPosStack[stackPos] = curPatternPos;
+ }
+ }
+ }
+ }
+ return matched;
+ }
+
///
/// Gets a value indicating if the filter matches the path
///
@@ -90,6 +180,7 @@ namespace Duplicati.Library.Utility
case FilterType.Simple:
return string.Equals(this.Filter, path, Library.Utility.Utility.ClientFilenameStringComparision);
case FilterType.Wildcard:
+ return IsWildcardMatch(Utility.IsFSCaseSensitive ? path.ToUpper() : path, this.Filter);
case FilterType.Regexp:
var m = this.Regexp.Match(path);
return m.Success && m.Length == path.Length;