diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs
index cb2f4e346c..8645399a0b 100644
--- a/src/Umbraco.Core/IO/ShadowFileSystem.cs
+++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs
@@ -218,11 +218,11 @@ namespace Umbraco.Core.IO
var normPath = NormPath(path);
var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray();
var files = filter != null ? _fs.GetFiles(path, filter) : _fs.GetFiles(path);
- var regexFilter = FilterToRegex(filter);
+ var wildcard = filter == null ? null : new WildcardExpression(filter);
return files
.Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir)
.Select(kvp => kvp.Key))
- .Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist && FilterByRegex(kvp.Key, regexFilter)).Select(kvp => kvp.Key))
+ .Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist && (wildcard == null || wildcard.IsMatch(kvp.Key))).Select(kvp => kvp.Key))
.Distinct();
}
@@ -326,32 +326,65 @@ namespace Umbraco.Core.IO
_sfs.AddFile(path, physicalPath, overrideIfExists, copy);
Nodes[normPath] = new ShadowNode(false, false);
}
-
- ///
- /// Helper function for filtering keys by Regex if a filter is specified.
- ///
- ///
- ///
- ///
- internal static bool FilterByRegex(string input, string regexFilter)
- {
- if (regexFilter == null) return true;
- return regexFilter != string.Empty && Regex.IsMatch(input, regexFilter);
- }
- ///
- /// Transforms a filter pattern into a Regex pattern
- ///
- ///
- ///
- ///
- /// Appending '$' only if not containing wildcard is stupid and broken.
- /// It is however what seems to be what they're doing in .NET so we need to match the functionality.
- ///
- internal static string FilterToRegex(string pattern)
+ // copied from System.Web.Util.Wildcard internal
+ internal class WildcardExpression
{
- if (pattern == null) return null;
- return "^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + (pattern.Contains("*") ? string.Empty : "$");
+ private readonly string _pattern;
+ private readonly bool _caseInsensitive;
+ private Regex _regex;
+
+ private static Regex metaRegex = new Regex("[\\+\\{\\\\\\[\\|\\(\\)\\.\\^\\$]");
+ private static Regex questRegex = new Regex("\\?");
+ private static Regex starRegex = new Regex("\\*");
+ private static Regex commaRegex = new Regex(",");
+ private static Regex slashRegex = new Regex("(?=/)");
+ private static Regex backslashRegex = new Regex("(?=[\\\\:])");
+
+ public WildcardExpression(string pattern, bool caseInsensitive = true)
+ {
+ _pattern = pattern;
+ _caseInsensitive = caseInsensitive;
+ }
+
+ private void EnsureRegex(string pattern)
+ {
+ if (_regex != null) return;
+
+ var options = RegexOptions.None;
+
+ // match right-to-left (for speed) if the pattern starts with a *
+
+ if (pattern.Length > 0 && pattern[0] == '*')
+ options = RegexOptions.RightToLeft | RegexOptions.Singleline;
+ else
+ options = RegexOptions.Singleline;
+
+ // case insensitivity
+
+ if (_caseInsensitive)
+ options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
+
+ // Remove regex metacharacters
+
+ pattern = metaRegex.Replace(pattern, "\\$0");
+
+ // Replace wildcard metacharacters with regex codes
+
+ pattern = questRegex.Replace(pattern, ".");
+ pattern = starRegex.Replace(pattern, ".*");
+ pattern = commaRegex.Replace(pattern, "\\z|\\A");
+
+ // anchor the pattern at beginning and end, and return the regex
+
+ _regex = new Regex("\\A" + pattern + "\\z", options);
+ }
+
+ public bool IsMatch(string input)
+ {
+ EnsureRegex(_pattern);
+ return _regex.IsMatch(input);
+ }
}
}
}
diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs
index 03cc9364ce..80110698b5 100644
--- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs
+++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs
@@ -805,101 +805,6 @@ namespace Umbraco.Tests.IO
Assert.AreEqual(3, sfsFiles.Length);
}
- [Test]
- [Ignore("Does not work on all environments, Directory.GetFiles is broken.")]
- public void ShadowGetFilesUsingWildcardAndSingleCharacterFilter()
- {
- // Arrange
- var path = IOHelper.MapPath("FileSysTests");
- Directory.CreateDirectory(path);
- Directory.CreateDirectory(path + "/ShadowTests");
- Directory.CreateDirectory(path + "/ShadowSystem");
-
- var fs = new PhysicalFileSystem(path + "/ShadowTests/", "ignore");
- var sfs = new PhysicalFileSystem(path + "/ShadowSystem/", "ignore");
- var ss = new ShadowFileSystem(fs, sfs);
-
- // Act
- File.WriteAllText(path + "/ShadowTests/f2.txt", "foo");
- File.WriteAllText(path + "/ShadowTests/f2.doc", "foo");
- File.WriteAllText(path + "/ShadowTests/f2.docx", "foo");
- using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
- ss.AddFile("f1.txt", ms);
- using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
- ss.AddFile("f1.doc", ms);
- using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
- ss.AddFile("f1.docx", ms);
-
- // Assert
- // ensure we get 6 files from the shadow
- var getFiles = ss.GetFiles(string.Empty);
- Assert.AreEqual(6, getFiles.Count());
- var getFilesWithWildcardSinglecharFilter = ss.GetFiles(string.Empty, "*.d?c");
-
- Assert.AreEqual(4, getFilesWithWildcardSinglecharFilter.Count());
- var getFilesWithWildcardSinglecharFilter2 = ss.GetFiles(string.Empty, "*.d?cx");
- Assert.AreEqual(2, getFilesWithWildcardSinglecharFilter2.Count());
-
- var fsFiles = fs.GetFiles(string.Empty).ToArray();
- Assert.AreEqual(3, fsFiles.Length);
- var sfsFiles = sfs.GetFiles(string.Empty).ToArray();
- Assert.AreEqual(3, sfsFiles.Length);
- }
-
- [Test]
- [Ignore("Does not work on all environments, Directory.GetFiles is broken.")]
- public void ShadowFileSystemFilterIsAsBrokenAsRealFileSystemFilter()
- {
- // Arrange
- var path = IOHelper.MapPath("FileSysTests");
- Directory.CreateDirectory(path);
- Directory.CreateDirectory(path + "/filter");
- // create files on disk and create a "fake" list of files to verify filters against
- File.WriteAllText(path + "/filter/f1.txt", "foo");
- File.WriteAllText(path + "/filter/f1.doc", "foo");
- File.WriteAllText(path + "/filter/f1.docx", "foo");
- var files = new string[]
- {
- "f1.txt",
- "f1.doc",
- "f1.docx",
- };
- var filter1 = "";
- var filter2 = "*";
- var filter3 = "*.doc";
- var filter4 = "*.d?c";
- var filter5 = "f1.doc";
- var filter6 = "f1.d?c";
- var filter7 = "**.d?c";
- var filter8 = "f*.doc";
-
- // Act & Assert
- var result1Disk = Directory.GetFiles(path + "/filter/", filter1).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result1Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter1))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result1Disk, result1Fake);
- var result2Disk = Directory.GetFiles(path + "/filter/", filter2).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result2Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter2))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result2Disk, result2Fake);
- var result3Disk = Directory.GetFiles(path + "/filter/", filter3).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result3Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter3))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result3Disk, result3Fake);
- var result4Disk = Directory.GetFiles(path + "/filter/", filter4).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result4Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter4))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result4Disk, result4Fake);
- var result5Disk = Directory.GetFiles(path + "/filter/", filter5).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result5Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter5))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result5Disk, result5Fake);
- var result6Disk = Directory.GetFiles(path + "/filter/", filter6).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result6Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter6))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result6Disk, result6Fake);
- var result7Disk = Directory.GetFiles(path + "/filter/", filter7).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result7Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter7))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result7Disk, result7Fake);
- var result8Disk = Directory.GetFiles(path + "/filter/", filter8).Select(Path.GetFileName).OrderBy(x => x).ToArray();
- var result8Fake = files.Where(x => ShadowFileSystem.FilterByRegex(x, ShadowFileSystem.FilterToRegex(filter8))).OrderBy(x => x).ToArray();
- Assert.AreEqual(result8Disk, result8Fake);
- }
-
///
/// Returns the full paths of the files on the disk.
/// Note that this will be the *actual* path of the file, meaning a file existing on the initialized FS