DotIgnoreIgnoreRule.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. using System;
  2. using System.IO;
  3. using System.Text.RegularExpressions;
  4. using MediaBrowser.Controller.Entities;
  5. using MediaBrowser.Controller.IO;
  6. using MediaBrowser.Controller.Resolvers;
  7. using MediaBrowser.Model.IO;
  8. namespace Emby.Server.Implementations.Library;
  9. /// <summary>
  10. /// Resolver rule class for ignoring files via .ignore.
  11. /// </summary>
  12. public class DotIgnoreIgnoreRule : IResolverIgnoreRule
  13. {
  14. private static readonly bool IsWindows = OperatingSystem.IsWindows();
  15. private static FileInfo? FindIgnoreFile(DirectoryInfo directory)
  16. {
  17. for (var current = directory; current is not null; current = current.Parent)
  18. {
  19. var ignorePath = Path.Join(current.FullName, ".ignore");
  20. if (File.Exists(ignorePath))
  21. {
  22. return new FileInfo(ignorePath);
  23. }
  24. }
  25. return null;
  26. }
  27. /// <inheritdoc />
  28. public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) => IsIgnored(fileInfo, parent);
  29. /// <summary>
  30. /// Checks whether or not the file is ignored.
  31. /// </summary>
  32. /// <param name="fileInfo">The file information.</param>
  33. /// <param name="parent">The parent BaseItem.</param>
  34. /// <returns>True if the file should be ignored.</returns>
  35. public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent)
  36. {
  37. var searchDirectory = fileInfo.IsDirectory
  38. ? new DirectoryInfo(fileInfo.FullName)
  39. : new DirectoryInfo(Path.GetDirectoryName(fileInfo.FullName) ?? string.Empty);
  40. if (string.IsNullOrEmpty(searchDirectory.FullName))
  41. {
  42. return false;
  43. }
  44. var ignoreFile = FindIgnoreFile(searchDirectory);
  45. if (ignoreFile is null)
  46. {
  47. return false;
  48. }
  49. // Fast path in case the ignore files isn't a symlink and is empty
  50. if (ignoreFile.LinkTarget is null && ignoreFile.Length == 0)
  51. {
  52. // Ignore directory if we just have the file
  53. return true;
  54. }
  55. var content = GetFileContent(ignoreFile);
  56. return string.IsNullOrWhiteSpace(content)
  57. || CheckIgnoreRules(fileInfo.FullName, content, fileInfo.IsDirectory);
  58. }
  59. private static bool CheckIgnoreRules(string path, string ignoreFileContent, bool isDirectory)
  60. {
  61. // If file has content, base ignoring off the content .gitignore-style rules
  62. var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  63. return CheckIgnoreRules(path, rules, isDirectory);
  64. }
  65. /// <summary>
  66. /// Checks whether a path should be ignored based on an array of ignore rules.
  67. /// </summary>
  68. /// <param name="path">The path to check.</param>
  69. /// <param name="rules">The array of ignore rules.</param>
  70. /// <param name="isDirectory">Whether the path is a directory.</param>
  71. /// <returns>True if the path should be ignored.</returns>
  72. internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory)
  73. => CheckIgnoreRules(path, rules, isDirectory, IsWindows);
  74. /// <summary>
  75. /// Checks whether a path should be ignored based on an array of ignore rules.
  76. /// </summary>
  77. /// <param name="path">The path to check.</param>
  78. /// <param name="rules">The array of ignore rules.</param>
  79. /// <param name="isDirectory">Whether the path is a directory.</param>
  80. /// <param name="normalizePath">Whether to normalize backslashes to forward slashes (for Windows paths).</param>
  81. /// <returns>True if the path should be ignored.</returns>
  82. internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory, bool normalizePath)
  83. {
  84. var ignore = new Ignore.Ignore();
  85. // Add each rule individually to catch and skip invalid patterns
  86. var validRulesAdded = 0;
  87. foreach (var rule in rules)
  88. {
  89. try
  90. {
  91. ignore.Add(rule);
  92. validRulesAdded++;
  93. }
  94. catch (RegexParseException)
  95. {
  96. // Ignore invalid patterns
  97. }
  98. }
  99. // If no valid rules were added, fall back to ignoring everything (like an empty .ignore file)
  100. if (validRulesAdded == 0)
  101. {
  102. return true;
  103. }
  104. // Mitigate the problem of the Ignore library not handling Windows paths correctly.
  105. // See https://github.com/jellyfin/jellyfin/issues/15484
  106. var pathToCheck = normalizePath ? path.NormalizePath('/') : path;
  107. // Add trailing slash for directories to match "folder/"
  108. if (isDirectory)
  109. {
  110. pathToCheck = string.Concat(pathToCheck.AsSpan().TrimEnd('/'), "/");
  111. }
  112. return ignore.IsIgnored(pathToCheck);
  113. }
  114. private static string GetFileContent(FileInfo ignoreFile)
  115. {
  116. ignoreFile = FileSystemHelper.ResolveLinkTarget(ignoreFile, returnFinalTarget: true) ?? ignoreFile;
  117. return ignoreFile.Exists
  118. ? File.ReadAllText(ignoreFile.FullName)
  119. : string.Empty;
  120. }
  121. }