File: ManifestUtil\Util.cs
Web Access
Project: ..\..\..\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.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using Microsoft.Win32;
 
#nullable disable
 
namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities
{
    internal static class Util
    {
        internal static readonly string Schema = Environment.GetEnvironmentVariable("VSPSCHEMA");
        internal static readonly bool logging = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("VSPLOG"));
        internal static readonly string logPath = GetLogPath();
        private static readonly char[] s_fileNameInvalidChars = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
        private static StreamWriter s_logFileWriter;
#if !RUNTIME_TYPE_NETCORE
        // Major, Minor, Build and Revision of CLR v2.0
        private static readonly int[] s_clrVersion2 = { 2, 0, 50727, 0 };
#else
        // Major, Minor, Build and Revision of CLR v4.0
        private static readonly int[] s_clrVersion4 = { 4, 0, 30319, 0 };
#endif
 
        #region " Platform <-> ProcessorArchitecture mapping "
        // Note: These two arrays are parallel and must correspond to one another.
        private static readonly string[] s_platforms =
        {
            "AnyCPU",
            "x86",
            "x64",
            "Itanium",
            "arm",
            "arm64",
        };
        private static readonly string[] s_processorArchitectures =
        {
            "msil",
            "x86",
            "amd64",
            "ia64",
            "arm",
            "arm64",
        };
        #endregion
 
        public static string ByteArrayToHex(Byte[] a)
        {
            if (a == null)
            {
                return null;
            }
 
            StringBuilder s = new StringBuilder(a.Length);
            foreach (Byte b in a)
            {
                s.Append(b.ToString("X02", CultureInfo.InvariantCulture));
            }
 
            return s.ToString();
        }
 
        public static string ByteArrayToString(Byte[] a)
        {
            if (a == null)
            {
                return null;
            }
 
            StringBuilder s = new StringBuilder(a.Length);
            foreach (Byte b in a)
            {
                s.Append(Convert.ToChar(b));
            }
 
            return s.ToString();
        }
 
        public static int CopyStream(Stream input, Stream output)
        {
            const int bufferSize = 0x4000;
            byte[] buffer = new byte[bufferSize];
            int bytesCopied = 0;
            int bytesRead;
            do
            {
                bytesRead = input.Read(buffer, 0, bufferSize);
                output.Write(buffer, 0, bytesRead);
                bytesCopied += bytesRead;
            } while (bytesRead > 0);
            output.Flush();
            input.Position = 0;
            output.Position = 0;
            return bytesCopied;
        }
 
        public static string FilterNonprintableChars(string value)
        {
            StringBuilder sb = new StringBuilder(value);
            int i = 0;
            while (i < sb.Length)
            {
                if (sb[i] < ' ')
                {
                    sb.Remove(i, 1);
                }
                else
                {
                    ++i;
                }
            }
 
            return sb.ToString();
        }
 
        public static string GetAssemblyPath()
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        }
 
        public static string GetClrVersion()
        {
            Version v = Environment.Version;
#if RUNTIME_TYPE_NETCORE
            // This is a version of ClickOnce .NET FX target runtime, which cannot be obtained in .NET (Core) process.
            // Set to .NET FX v4 runtime as the ony one supported for manifest generation in .NET (Core) process.
            v = new Version(s_clrVersion4[0], s_clrVersion4[1], s_clrVersion4[2], s_clrVersion4[3]);
#else
            v = new Version(v.Major, v.Minor, v.Build, 0);
#endif
            return v.ToString();
        }
 
        /// <summary>
        /// Return a CLRVersion from a given target framework version.
        /// </summary>
        /// <param name="targetFrameworkVersion"></param>
        /// <returns></returns>
        public static string GetClrVersion(string targetFrameworkVersion)
        {
            if (string.IsNullOrEmpty(targetFrameworkVersion))
            {
                return GetClrVersion();
            }
 
            Version clrVersion;
#if RUNTIME_TYPE_NETCORE
            // This is a version of ClickOnce .NET FX target runtime, which cannot be obtained in .NET (Core) process.
            // Set to .NET FX v4 runtime as the ony one supported for manifest generation in .NET (Core) process.
            Version currentVersion = new Version(s_clrVersion4[0], s_clrVersion4[1], s_clrVersion4[2], s_clrVersion4[3]);
#else
            Version currentVersion = Environment.Version;
#endif
            Version frameworkVersion = GetTargetFrameworkVersion(targetFrameworkVersion);
 
            // for FX 4.0 or above use the current version.
            if (frameworkVersion != null && (frameworkVersion.Major >= currentVersion.Major))
            {
                clrVersion = new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build, 0);
            }
            else
            {
#if RUNTIME_TYPE_NETCORE
                // Set to .NET FX v4 runtime as the ony one supported for manifest generation in .NET (Core) process.
                clrVersion = new Version(s_clrVersion4[0], s_clrVersion4[1], s_clrVersion4[2], s_clrVersion4[3]);
#else
                clrVersion = new Version(s_clrVersion2[0], s_clrVersion2[1], s_clrVersion2[2], s_clrVersion2[3]);
#endif
            }
            return clrVersion.ToString();
        }
 
        /// <summary>
        /// Gets a Version object corresponding to the given target framework version string.
        /// </summary>
        public static Version GetTargetFrameworkVersion(string targetFramework)
        {
            Version frameworkVersion = null;
            if (!String.IsNullOrEmpty(targetFramework))
            {
                if (targetFramework.StartsWith("v", StringComparison.OrdinalIgnoreCase))
                {
                    Version.TryParse(targetFramework.Substring(1), out frameworkVersion);
                }
                else
                {
                    Version.TryParse(targetFramework, out frameworkVersion);
                }
            }
            return frameworkVersion;
        }
 
        public static string GetEmbeddedResourceString(string name)
        {
            Stream s = GetEmbeddedResourceStream(name);
            using StreamReader r = new StreamReader(s);
            return r.ReadToEnd();
        }
 
        public static Stream GetEmbeddedResourceStream(string name)
        {
            Assembly a = Assembly.GetExecutingAssembly();
            Stream s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(Util).Namespace, name));
            Debug.Assert(s != null, String.Format(CultureInfo.CurrentCulture, "EmbeddedResource '{0}' not found", name));
            return s;
        }
 
        public static void GetFileInfo(string path, out string hash, out long length)
        {
            GetFileInfoImpl(path, null, out hash, out length);
        }
 
        public static void GetFileInfo(string path, string targetFrameworkVersion, out string hash, out long length)
        {
            GetFileInfoImpl(path, targetFrameworkVersion, out hash, out length);
        }
 
        [SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = ".NET 4.0 and earlier versions cannot parse SHA-2.")]
        private static void GetFileInfoImpl(string path, string targetFrameWorkVersion, out string hash, out long length)
        {
            FileInfo fi = new FileInfo(path);
            length = fi.Length;
 
            Stream s = null;
            HashAlgorithm hashAlg = null;
            try
            {
                s = fi.OpenRead();
 
                if (string.IsNullOrEmpty(targetFrameWorkVersion) || CompareFrameworkVersions(targetFrameWorkVersion, Constants.TargetFrameworkVersion40) <= 0)
                {
#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
                    // codeql[cs/weak-crypto] .NET 4.0 and earlier versions cannot parse SHA-2. Newer Frameworks use SHA256. https://devdiv.visualstudio.com/DevDiv/_workitems/edit/139025
                    hashAlg = SHA1.Create(
#if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES
                        "System.Security.Cryptography.SHA1CryptoServiceProvider"
#endif
                        );
#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter
                }
                else
                {
#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
                    hashAlg = SHA256.Create(
#if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES
                        "System.Security.Cryptography.SHA256CryptoServiceProvider"
#endif
                        );
#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter
                }
                byte[] hashBytes = hashAlg.ComputeHash(s);
                hash = Convert.ToBase64String(hashBytes);
            }
            finally
            {
                s?.Close();
                hashAlg?.Dispose();
            }
        }
 
        private static string GetLogPath()
        {
            if (!logging)
            {
                return null;
            }
 
            string logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Microsoft\VisualStudio\8.0\VSPLOG");
            if (!FileSystems.Default.DirectoryExists(logPath))
            {
                Directory.CreateDirectory(logPath);
            }
 
            return logPath;
        }
 
        [SupportedOSPlatform("windows")]
        public static string GetRegisteredOrganization()
        {
            RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", false);
            if (key != null)
            {
                string org = (string)key.GetValue("RegisteredOrganization");
                org = org?.Trim();
                if (!String.IsNullOrEmpty(org))
                {
                    return org;
                }
            }
            return null;
        }
 
        public static bool IsValidAssemblyName(string value)
        {
            return IsValidFileName(value);
        }
 
        public static bool IsValidCulture(string value)
        {
            if (String.Equals(value, "neutral", StringComparison.OrdinalIgnoreCase))
            {
                return true; // "neutral" is valid in a manifest but not in CultureInfo class
            }
 
            if (String.Equals(value, "*", StringComparison.OrdinalIgnoreCase))
            {
                return true; // "*" is same as "neutral"
            }
 
            CultureInfo culture;
            try
            {
                culture = new CultureInfo(value);
            }
            catch (ArgumentException)
            {
                return false;
            }
            return true;
        }
 
        public static bool IsValidFileName(string value)
        {
            return value.IndexOfAny(s_fileNameInvalidChars) < 0;
        }
 
        public static bool IsValidVersion(string value, int octets)
        {
            Version version;
            try
            {
                version = new Version(value);
            }
            catch (FormatException)
            {
                return false;
            }
            catch (ArgumentOutOfRangeException)
            {
                return false;
            }
            catch (ArgumentNullException)
            {
                return false;
            }
            catch (ArgumentException)
            {
                return false;
            }
            catch (OverflowException)
            {
                return false;
            }
 
            if (octets >= 1 && version.Major < 0)
            {
                return false;
            }
 
            if (octets >= 2 && version.Minor < 0)
            {
                return false;
            }
 
            if (octets >= 3 && version.Build < 0)
            {
                return false;
            }
 
            if (octets >= 4 && version.Revision < 0)
            {
                return false;
            }
 
            return true;
        }
 
        internal static bool IsValidFrameworkVersion(string value)
        {
            if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
            {
                return IsValidVersion(value.Substring(1), 2);
            }
 
            return IsValidVersion(value, 2);
        }
 
        public static string PlatformToProcessorArchitecture(string platform)
        {
            for (int i = 0; i < s_platforms.Length; ++i)
            {
                if (String.Equals(platform, s_platforms[i], StringComparison.OrdinalIgnoreCase))
                {
                    return s_processorArchitectures[i];
                }
            }
 
            return null;
        }
 
        private static ITaskItem[] RemoveDuplicateItems(ITaskItem[] items)
        {
            if (items == null)
            {
                return null;
            }
 
            if (items.Length <= 1)
            {
                return items;
            }
 
            var list = new Dictionary<string, ITaskItem>();
            foreach (ITaskItem item in items)
            {
                if (String.IsNullOrEmpty(item.ItemSpec))
                {
                    continue;
                }
 
                string key;
                var id = new AssemblyIdentity(item.ItemSpec);
                if (id.IsStrongName)
                {
                    key = id.GetFullName(AssemblyIdentity.FullNameFlags.All);
                }
                else
                {
                    key = Path.GetFullPath(item.ItemSpec).ToUpperInvariant();
                }
 
                if (!list.ContainsKey(key))
                {
                    list.Add(key, item);
                }
            }
 
            return list.Values.ToArray();
        }
 
        public static ITaskItem[] SortItems(ITaskItem[] items)
        {
            ITaskItem[] outputItems = RemoveDuplicateItems(items);
            if (outputItems != null)
            {
                Array.Sort(outputItems, s_itemComparer);
            }
 
            return outputItems;
        }
 
        public static void WriteFile(string path, string s)
        {
            using (StreamWriter w = new StreamWriter(path))
            {
                w.Write(s);
            }
        }
 
        public static void WriteFile(string path, Stream s)
        {
            using StreamReader r = new StreamReader(s);
            WriteFile(path, r.ReadToEnd());
        }
 
        public static void WriteLog(string text)
        {
            if (!logging)
            {
                return;
            }
 
            if (s_logFileWriter == null)
            {
                try
                {
                    s_logFileWriter = new StreamWriter(Path.Combine(logPath, "Microsoft.Build.Tasks.log"), false);
                }
                catch (UnauthorizedAccessException)
                {
                    return;
                }
                catch (ArgumentException)
                {
                    return;
                }
                catch (IOException)
                {
                    return;
                }
                catch (SecurityException)
                {
                    return;
                }
            }
 
            s_logFileWriter.WriteLine(text);
            s_logFileWriter.Flush();
        }
 
        public static void WriteLogFile(string filename, Stream s)
        {
            if (!logging)
            {
                return;
            }
 
            string path = Path.Combine(logPath, filename);
            using var r = new StreamReader(s, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, 1024, leaveOpen: true);
            string text = r.ReadToEnd();
            try
            {
                WriteFile(path, text);
            }
            catch (UnauthorizedAccessException)
            {
            }
            catch (ArgumentException)
            {
            }
            catch (IOException)
            {
            }
            catch (SecurityException)
            {
            }
 
            s.Position = 0;
        }
 
        public static void WriteLogFile(string filename, string s)
        {
            if (!logging)
            {
                return;
            }
 
            string path = Path.Combine(logPath, filename);
            try
            {
                WriteFile(path, s);
            }
            catch (UnauthorizedAccessException)
            {
                return;
            }
            catch (ArgumentException)
            {
                return;
            }
            catch (IOException)
            {
                return;
            }
            catch (SecurityException)
            {
                return;
            }
        }
 
        public static void WriteLogFile(string filename, System.Xml.XmlElement element)
        {
            if (!logging)
            {
                return;
            }
 
            WriteLogFile(filename, element.OuterXml);
        }
 
        public static string WriteTempFile(Stream s)
        {
            // May throw IO-related exceptions
            string path = FileUtilities.GetTemporaryFileName();
 
            WriteFile(path, s);
            return path;
        }
 
        public static string WriteTempFile(string s)
        {
            // May throw IO-related exceptions
            string path = FileUtilities.GetTemporaryFileName();
 
            WriteFile(path, s);
            return path;
        }
 
        #region ItemComparer
        private static readonly ItemComparer s_itemComparer = new ItemComparer();
        private class ItemComparer : IComparer
        {
            int IComparer.Compare(object obj1, object obj2)
            {
                if (obj1 == null || obj2 == null)
                {
                    Debug.Fail("Comparing null objects");
                    return 0;
                }
                if (!(obj1 is ITaskItem) || !(obj2 is ITaskItem))
                {
                    Debug.Fail("Comparing objects that are not ITaskItem");
                    return 0;
                }
                ITaskItem item1 = obj1 as ITaskItem;
                ITaskItem item2 = obj2 as ITaskItem;
                if (item1.ItemSpec == null || item2.ItemSpec == null)
                {
                    Debug.Fail("Objects do not have a ItemSpec");
                    return 0;
                }
                return String.Compare(item1.ItemSpec, item2.ItemSpec, StringComparison.Ordinal);
            }
        }
        #endregion
 
        public static Version ConvertFrameworkVersionToString(string version)
        {
            if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase))
            {
                return new Version(version.Substring(1));
            }
            return new Version(version);
        }
 
        public static int CompareFrameworkVersions(string versionA, string versionB)
        {
            Version version1 = ConvertFrameworkVersionToString(versionA);
            Version version2 = ConvertFrameworkVersionToString(versionB);
            return version1.CompareTo(version2);
        }
    }
}