diff --git a/ImageDescriber.Test/ImageScannerTests.cs b/ImageDescriber.Test/ImageScannerTests.cs index 816cfad..e593862 100644 --- a/ImageDescriber.Test/ImageScannerTests.cs +++ b/ImageDescriber.Test/ImageScannerTests.cs @@ -31,6 +31,54 @@ public void ScanForImagesFindsImageFiles() } } + [TestMethod] + public void ScanForImagesSkipsUnreadableSubdirectories() + { + if (OperatingSystem.IsWindows()) + { + Assert.Inconclusive("Unix file modes are used to make the folder unreadable."); + return; + } + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string lockedDir = Path.Combine(tempDir, "locked"); + Directory.CreateDirectory(lockedDir); + File.WriteAllBytes(Path.Combine(tempDir, "readable.jpg"), [0xFF, 0xD8]); + File.WriteAllBytes(Path.Combine(lockedDir, "hidden.jpg"), [0xFF, 0xD8]); + File.SetUnixFileMode(lockedDir, UnixFileMode.None); + + try + { + if (Directory.Exists(lockedDir) && CanList(lockedDir)) + { + Assert.Inconclusive("Running as a user that can read any folder, such as root."); + } + + IReadOnlyList results = ImageScanner.ScanForImages(tempDir.As()); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual("readable.jpg", Path.GetFileName(results[0].WeakString)); + } + finally + { + File.SetUnixFileMode(lockedDir, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + Directory.Delete(tempDir, true); + } + } + + private static bool CanList(string directory) + { + try + { + _ = Directory.GetFiles(directory); + return true; + } + catch (UnauthorizedAccessException) + { + return false; + } + } + [TestMethod] public void ScanForImagesIgnoresNonImageFiles() { diff --git a/ImageDescriber/ImageScanner.cs b/ImageDescriber/ImageScanner.cs index b9740de..da8adff 100644 --- a/ImageDescriber/ImageScanner.cs +++ b/ImageDescriber/ImageScanner.cs @@ -30,8 +30,16 @@ internal static IReadOnlyList ScanForImages(AbsoluteDirectoryP return []; } + // The SearchOption overload stops at the first folder it can't open, which on Windows is + // any drive or profile root, so skip unreadable folders and scan the rest of the tree. + EnumerationOptions options = new() + { + RecurseSubdirectories = true, + IgnoreInaccessible = true, + }; + List imageFiles = []; - foreach (string file in Directory.EnumerateFiles(path.WeakString, "*", SearchOption.AllDirectories)) + foreach (string file in Directory.EnumerateFiles(path.WeakString, "*", options)) { string ext = Path.GetExtension(file); if (string.IsNullOrEmpty(ext))