using System; using System.Diagnostics.CodeAnalysis; using MediaBrowser.Common.Providers; namespace Emby.Server.Implementations.Library { /// /// Class providing extension methods for working with paths. /// public static class PathExtensions { /// /// Gets the attribute value. /// /// The STR. /// The attrib. /// System.String. /// or is empty. public static string? GetAttributeValue(this ReadOnlySpan str, ReadOnlySpan attribute) { if (str.Length == 0) { throw new ArgumentException("String can't be empty.", nameof(str)); } if (attribute.Length == 0) { throw new ArgumentException("String can't be empty.", nameof(attribute)); } var openBracketIndex = str.IndexOf('['); var attributeIndex = str.IndexOf(attribute); var closingBracketIndex = str.IndexOf(']'); while (openBracketIndex < attributeIndex && attributeIndex < closingBracketIndex) { if (openBracketIndex + 1 == attributeIndex && str[attributeIndex + attribute.Length] == '=') { return str[(attributeIndex + attribute.Length + 1)..closingBracketIndex].Trim().ToString(); } str = str[(closingBracketIndex + 1)..]; openBracketIndex = str.IndexOf('['); attributeIndex = str.IndexOf(attribute); closingBracketIndex = str.IndexOf(']'); } // for imdbid we also accept pattern matching if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase)) { var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId); return match ? imdbId.ToString() : null; } return null; } /// /// Replaces a sub path with another sub path and normalizes the final path. /// /// The original path. /// The original sub path. /// The new sub path. /// The result of the sub path replacement. /// The path after replacing the sub path. /// , or is empty. public static bool TryReplaceSubPath( [NotNullWhen(true)] this string? path, [NotNullWhen(true)] string? subPath, [NotNullWhen(true)] string? newSubPath, [NotNullWhen(true)] out string? newPath) { newPath = null; if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(subPath) || string.IsNullOrEmpty(newSubPath) || subPath.Length > path.Length) { return false; } char oldDirectorySeparatorChar; char newDirectorySeparatorChar; // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162 // The reasoning behind this is that a forward slash likely means it's a Linux path and // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much). if (newSubPath.Contains('/', StringComparison.Ordinal)) { oldDirectorySeparatorChar = '\\'; newDirectorySeparatorChar = '/'; } else { oldDirectorySeparatorChar = '/'; newDirectorySeparatorChar = '\\'; } path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results // when the sub path matches a similar but in-complete subpath var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar; if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)) { return false; } if (path.Length > subPath.Length && !oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar) { return false; } var newSubPathTrimmed = newSubPath.AsSpan().TrimEnd(newDirectorySeparatorChar); // Ensure that the path with the old subpath removed starts with a leading dir separator int idx = oldSubPathEndsWithSeparator ? subPath.Length - 1 : subPath.Length; newPath = string.Concat(newSubPathTrimmed, path.AsSpan(idx)); return true; } } }