| File: ManifestUtil\PathUtil.cs | Web Access |
| Project: src\msbuild\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.IO; #if !NET using System.Linq; #else using System.Text; #endif using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities { internal static class PathUtil { public static string CanonicalizePath(string path) { if (!String.IsNullOrEmpty(path)) { path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); path = path.TrimEnd(Path.DirectorySeparatorChar); } return path; } public static string CanonicalizeUrl(string url) { Uri uri = new Uri(url); return uri.AbsoluteUri; } // REVIEW: Can we use System.Uri segments instead public static string[] GetPathSegments(string path) { path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); path = path.TrimEnd(Path.DirectorySeparatorChar); return path.Split(MSBuildConstants.DirectorySeparatorChar); } // Resolves the path, and if path is a url also canonicalizes it. public static string Format(string path, AbsolutePath baseDirectory) { if (String.IsNullOrEmpty(path)) { return path; } string resolvedPath = Resolve(path, baseDirectory); Uri u = new Uri(resolvedPath); // // GB18030: Uri class does not correctly encode chars in the PUA range for implicit // file paths (paths without explicit scheme): // https://github.com/dotnet/runtime/issues/89538 // Workaround is to use UriBuilder with the file scheme specified explicitly to // correctly encode the PUA chars. // if (Uri.UriSchemeFile.Equals(u.Scheme, StringComparison.OrdinalIgnoreCase) && !IsAsciiString(resolvedPath)) { UriBuilder builder = new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = string.Empty, Path = resolvedPath, }; u = builder.Uri; } return u.AbsoluteUri; } public static bool IsAssembly(string path) { if (String.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } if (String.Equals(Path.GetExtension(path), ".application", StringComparison.Ordinal)) { return true; } if (String.Equals(Path.GetExtension(path), ".manifest", StringComparison.Ordinal)) { return true; } if (!IsProgramFile(path)) { return false; // optimization, don't want to sniff every every kind of file -- just dll's or exe's } if (IsManagedAssembly(path)) { return true; } if (IsNativeAssembly(path)) { return true; } return false; } // This function must be kept in sync with \dd\vb\publish\design\baseprovider\pathutil.vb public static bool IsDataFile(string path) { return path.EndsWith(".mdf", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".mdb", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".ldf", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".sdf", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".xml", StringComparison.OrdinalIgnoreCase) ; } public static bool IsEqualPath(string path1, string path2) { return String.Compare(CanonicalizePath(path1), CanonicalizePath(path2), true, System.Globalization.CultureInfo.CurrentCulture) == 0; } public static bool IsLocalPath(string path) { Uri u = new Uri(path, UriKind.RelativeOrAbsolute); if (!u.IsAbsoluteUri) { return true; } return String.IsNullOrEmpty(u.Host); } public static bool IsManagedAssembly(string path) { if (String.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } using (MetadataReader r = MetadataReader.Create(path)) { return r != null; } } public static bool IsNativeAssembly(string path) { if (String.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } if (String.Equals(Path.GetExtension(path), ".manifest", StringComparison.Ordinal)) { return true; } return EmbeddedManifestReader.Read(path) != null; } public static bool IsPEFile(string path) { byte[] buffer = new byte[2]; using (Stream s = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { s.ReadExactly(buffer, 0, 2); } // if first two bytes are "MZ" then we're looking at an .exe or a .dll not a .manifest return (buffer[0] == 0x4D) && (buffer[1] == 0x5A); } public static bool IsProgramFile(string path) { return path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ; } public static bool IsUncPath(string path) { Uri u; if (!Uri.TryCreate(path, UriKind.Absolute, out u) || u == null) { return false; } return u.IsUnc; } public static bool IsUrl(string path) { if (!Uri.TryCreate(path, UriKind.Absolute, out Uri u) || u == null) { return false; } return !u.IsUnc && !String.IsNullOrEmpty(u.Host); } // If path is a url and starts with "localhost", resolves to machine name. // If path is a relative path, resolves to a full path against <paramref name="baseDirectory"/>. public static string Resolve(string path, AbsolutePath baseDirectory) { if (String.IsNullOrEmpty(path)) { return path; } if (IsUncPath(path)) { return path; // if it's UNC then do nothing } if (IsUrl(path)) // if it's a URL then need to check for "localhost"... { // Replace "localhost" with the actual machine name... const string localHost = "localhost"; var u = new Uri(path); if (String.Equals(u.Host, localHost, StringComparison.OrdinalIgnoreCase)) { // Unfortunately Uri.Host is read-only, so we need to reconstruct it manually... int i = path.IndexOf(localHost, StringComparison.OrdinalIgnoreCase); return i >= 0 ? #if NET $"{path.AsSpan(0, i)}{Environment.MachineName.ToLowerInvariant()}{path.AsSpan(i + localHost.Length)}" : #else $"{path.Substring(0, i)}{Environment.MachineName.ToLowerInvariant()}{path.Substring(i + localHost.Length)}" : #endif path; } return path; } // If not UNC or URL then treat as a local disk path. Root the (possibly relative) // path against the supplied base directory rather than the process current directory // so the resolution is deterministic under multithreaded task execution, and return // its canonical (fully qualified, normalized) form. FormatUrl rejects whitespace-only // inputs on Windows before reaching this method, since AbsolutePath would otherwise // silently trim trailing whitespace and absolutize "baseDirectory\ " to baseDirectory. return new AbsolutePath(path, baseDirectory).GetCanonicalForm(); } private static bool IsAsciiString(string str) => #if NET Ascii.IsValid(str); #else str.All(c => c <= 127); #endif } }