Просмотр исходного кода

Backport pull request #15746 from jellyfin/release-10.11.z

Skip invalid ignore rules

Original-merge: 6e60634c9f078cc01e343b07a0a6b2a5c230478c

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
Shadowghost 18 часов назад
Родитель
Сommit
b9cf26db2f

+ 46 - 2
Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs

@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+using System.Text.RegularExpressions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Resolvers;
@@ -70,12 +71,55 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
     {
         // If file has content, base ignoring off the content .gitignore-style rules
         var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+        return CheckIgnoreRules(path, rules, isDirectory);
+    }
+
+    /// <summary>
+    /// Checks whether a path should be ignored based on an array of ignore rules.
+    /// </summary>
+    /// <param name="path">The path to check.</param>
+    /// <param name="rules">The array of ignore rules.</param>
+    /// <param name="isDirectory">Whether the path is a directory.</param>
+    /// <returns>True if the path should be ignored.</returns>
+    internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory)
+        => CheckIgnoreRules(path, rules, isDirectory, IsWindows);
+
+    /// <summary>
+    /// Checks whether a path should be ignored based on an array of ignore rules.
+    /// </summary>
+    /// <param name="path">The path to check.</param>
+    /// <param name="rules">The array of ignore rules.</param>
+    /// <param name="isDirectory">Whether the path is a directory.</param>
+    /// <param name="normalizePath">Whether to normalize backslashes to forward slashes (for Windows paths).</param>
+    /// <returns>True if the path should be ignored.</returns>
+    internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory, bool normalizePath)
+    {
         var ignore = new Ignore.Ignore();
-        ignore.Add(rules);
+
+        // Add each rule individually to catch and skip invalid patterns
+        var validRulesAdded = 0;
+        foreach (var rule in rules)
+        {
+            try
+            {
+                ignore.Add(rule);
+                validRulesAdded++;
+            }
+            catch (RegexParseException)
+            {
+                // Ignore invalid patterns
+            }
+        }
+
+        // If no valid rules were added, fall back to ignoring everything (like an empty .ignore file)
+        if (validRulesAdded == 0)
+        {
+            return true;
+        }
 
          // Mitigate the problem of the Ignore library not handling Windows paths correctly.
          // See https://github.com/jellyfin/jellyfin/issues/15484
-        var pathToCheck = IsWindows ? path.NormalizePath('/') : path;
+        var pathToCheck = normalizePath ? path.NormalizePath('/') : path;
 
         // Add trailing slash for directories to match "folder/"
         if (isDirectory)

+ 69 - 18
tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs

@@ -1,30 +1,81 @@
+using Emby.Server.Implementations.Library;
 using Xunit;
 
 namespace Jellyfin.Server.Implementations.Tests.Library;
 
 public class DotIgnoreIgnoreRuleTest
 {
-    [Fact]
-    public void Test()
+    private static readonly string[] _rule1 = ["SPs"];
+    private static readonly string[] _rule2 = ["SPs", "!thebestshot.mkv"];
+    private static readonly string[] _rule3 = ["*.txt", @"{\colortbl;\red255\green255\blue255;}", "videos/", @"\invalid\escape\sequence", "*.mkv"];
+    private static readonly string[] _rule4 = [@"{\colortbl;\red255\green255\blue255;}", @"\invalid\escape\sequence"];
+
+    public static TheoryData<string[], string, bool, bool> CheckIgnoreRulesTestData =>
+        new()
+        {
+            // Basic pattern matching
+            { _rule1, "f:/cd/sps/ffffff.mkv", false, true },
+            { _rule1, "cd/sps/ffffff.mkv", false, true },
+            { _rule1, "/cd/sps/ffffff.mkv", false, true },
+
+            // Negate pattern
+            { _rule2, "f:/cd/sps/ffffff.mkv", false, true },
+            { _rule2, "cd/sps/ffffff.mkv", false, true },
+            { _rule2, "/cd/sps/ffffff.mkv", false, true },
+            { _rule2, "f:/cd/sps/thebestshot.mkv", false, false },
+            { _rule2, "cd/sps/thebestshot.mkv", false, false },
+            { _rule2, "/cd/sps/thebestshot.mkv", false, false },
+
+            // Mixed valid and invalid patterns - skips invalid, applies valid
+            { _rule3, "test.txt", false, true },
+            { _rule3, "videos/movie.mp4", false, true },
+            { _rule3, "movie.mkv", false, true },
+            { _rule3, "test.mp3", false, false },
+
+            // Only invalid patterns - falls back to ignore all
+            { _rule4, "any-file.txt", false, true },
+            { _rule4, "any/path/to/file.mkv", false, true },
+        };
+
+    public static TheoryData<string[], string, bool, bool> WindowsPathNormalizationTestData =>
+        new()
+        {
+            // Windows paths with backslashes - should match when normalizePath is true
+            { _rule1, @"C:\cd\sps\ffffff.mkv", false, true },
+            { _rule1, @"D:\media\sps\movie.mkv", false, true },
+            { _rule1, @"\\server\share\sps\file.mkv", false, true },
+
+            // Negate pattern with Windows paths
+            { _rule2, @"C:\cd\sps\ffffff.mkv", false, true },
+            { _rule2, @"C:\cd\sps\thebestshot.mkv", false, false },
+
+            // Directory matching with Windows paths
+            { _rule3, @"C:\videos\movie.mp4", false, true },
+            { _rule3, @"D:\documents\test.txt", false, true },
+            { _rule3, @"E:\music\song.mp3", false, false },
+        };
+
+    [Theory]
+    [MemberData(nameof(CheckIgnoreRulesTestData))]
+    public void CheckIgnoreRules_ReturnsExpectedResult(string[] rules, string path, bool isDirectory, bool expectedIgnored)
+    {
+        Assert.Equal(expectedIgnored, DotIgnoreIgnoreRule.CheckIgnoreRules(path, rules, isDirectory));
+    }
+
+    [Theory]
+    [MemberData(nameof(WindowsPathNormalizationTestData))]
+    public void CheckIgnoreRules_WithWindowsPaths_NormalizesBackslashes(string[] rules, string path, bool isDirectory, bool expectedIgnored)
     {
-        var ignore = new Ignore.Ignore();
-        ignore.Add("SPs");
-        Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
-        Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
-        Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
+        // With normalizePath=true, backslashes should be converted to forward slashes
+        Assert.Equal(expectedIgnored, DotIgnoreIgnoreRule.CheckIgnoreRules(path, rules, isDirectory, normalizePath: true));
     }
 
-    [Fact]
-    public void TestNegatePattern()
+    [Theory]
+    [InlineData(@"C:\cd\sps\ffffff.mkv")]
+    [InlineData(@"D:\media\sps\movie.mkv")]
+    public void CheckIgnoreRules_WithWindowsPaths_WithoutNormalization_DoesNotMatch(string path)
     {
-        var ignore = new Ignore.Ignore();
-        ignore.Add("SPs");
-        ignore.Add("!thebestshot.mkv");
-        Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
-        Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
-        Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
-        Assert.True(!ignore.IsIgnored("f:/cd/sps/thebestshot.mkv"));
-        Assert.True(!ignore.IsIgnored("cd/sps/thebestshot.mkv"));
-        Assert.True(!ignore.IsIgnored("/cd/sps/thebestshot.mkv"));
+        // Without normalization, Windows paths with backslashes won't match patterns expecting forward slashes
+        Assert.False(DotIgnoreIgnoreRule.CheckIgnoreRules(path, _rule1, isDirectory: false, normalizePath: false));
     }
 }