#nullable enable 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 string str, string 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)); } string srch = "[" + attribute + "="; int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase); if (start != -1) { start += srch.Length; int end = str.IndexOf(']', start); return str.Substring(start, end - start); } // for imdbid we also accept pattern matching if (string.Equals(attribute, "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) || (!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; } } }