From 88a1ad04aa26a14c3399f5c3297b74a262cd793f Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 14 Feb 2017 14:57:04 +0100 Subject: [PATCH] ensuring filter is applied to the shadow filesystem when using GetFiles. added tests for various filters. --- src/Umbraco.Core/IO/ShadowFileSystem.cs | 31 ++- src/Umbraco.Tests/IO/ShadowFileSystemTests.cs | 237 +++++++++++++++++- 2 files changed, 265 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index faf3678495..bce8b8861b 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; namespace Umbraco.Core.IO { @@ -209,10 +210,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); 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).Select(kvp => kvp.Key)) + .Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist && FilterByRegex(kvp.Key, regexFilter)).Select(kvp => kvp.Key)) .Distinct(); } @@ -285,5 +287,32 @@ namespace Umbraco.Core.IO if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path."); return _sfs.GetSize(path); } + + /// + /// 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) + { + if (pattern == null) return null; + return "^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + (pattern.Contains("*") ? string.Empty : "$"); + } } } diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 85a2ae3345..ac4a25385d 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -650,8 +650,6 @@ namespace Umbraco.Tests.IO // ensure we get 2 files from the shadow var getFiles = ss.GetFiles(string.Empty); Assert.AreEqual(2, getFiles.Count()); - var getFilesWithFilter = ss.GetFiles(string.Empty, "*"); - Assert.AreEqual(2, getFilesWithFilter.Count()); var fsFiles = fs.GetFiles(string.Empty).ToArray(); Assert.AreEqual(1, fsFiles.Length); @@ -659,6 +657,241 @@ namespace Umbraco.Tests.IO Assert.AreEqual(1, sfsFiles.Length); } + /// + /// Check that GetFiles using the filter function with empty string will return expected results + /// + [Test] + public void ShadowGetFilesUsingEmptyFilter() + { + // 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"); + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) + ss.AddFile("f1.txt", ms); + + // Assert + // ensure we get 2 files from the shadow + var getFiles = ss.GetFiles(string.Empty); + Assert.AreEqual(2, getFiles.Count()); + // ensure we get 0 files when using a empty filter + var getFilesWithEmptyFilter = ss.GetFiles(string.Empty, ""); + Assert.AreEqual(0, getFilesWithEmptyFilter.Count()); + + var fsFiles = fs.GetFiles(string.Empty).ToArray(); + Assert.AreEqual(1, fsFiles.Length); + var sfsFiles = sfs.GetFiles(string.Empty).ToArray(); + Assert.AreEqual(1, sfsFiles.Length); + } + + /// + /// Check that GetFiles using the filter function with null will return expected results + /// + [Test] + public void ShadowGetFilesUsingNullFilter() + { + // 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"); + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) + ss.AddFile("f1.txt", ms); + + // Assert + // ensure we get 2 files from the shadow + var getFiles = ss.GetFiles(string.Empty); + Assert.AreEqual(2, getFiles.Count()); + // ensure we get 2 files when using null in filter parameter + var getFilesWithNullFilter = ss.GetFiles(string.Empty, null); + Assert.AreEqual(2, getFilesWithNullFilter.Count()); + + var fsFiles = fs.GetFiles(string.Empty).ToArray(); + Assert.AreEqual(1, fsFiles.Length); + var sfsFiles = sfs.GetFiles(string.Empty).ToArray(); + Assert.AreEqual(1, sfsFiles.Length); + } + + [Test] + public void ShadowGetFilesUsingWildcardFilter() + { + // 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"); + 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); + + // Assert + // ensure we get 4 files from the shadow + var getFiles = ss.GetFiles(string.Empty); + Assert.AreEqual(4, getFiles.Count()); + // ensure we get only 2 of 4 files from the shadow when using filter + var getFilesWithWildcardFilter = ss.GetFiles(string.Empty, "*.doc"); + Assert.AreEqual(2, getFilesWithWildcardFilter.Count()); + + var fsFiles = fs.GetFiles(string.Empty).ToArray(); + Assert.AreEqual(2, fsFiles.Length); + var sfsFiles = sfs.GetFiles(string.Empty).ToArray(); + Assert.AreEqual(2, sfsFiles.Length); + } + + [Test] + public void ShadowGetFilesUsingSingleCharacterFilter() + { + // 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()); + // ensure we get only 2 of 6 files from the shadow when using filter on shadow + var getFilesWithWildcardSinglecharFilter = ss.GetFiles(string.Empty, "f1.d?c"); + Assert.AreEqual(1, getFilesWithWildcardSinglecharFilter.Count()); + // ensure we get only 2 of 6 files from the shadow when using filter on disk + var getFilesWithWildcardSinglecharFilter2 = ss.GetFiles(string.Empty, "f2.d?c"); + Assert.AreEqual(1, 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] + 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] + 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"; + + // 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); + } + /// /// 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