File: System\Addin\Hosting\Utils.cs
Project: ndp\fx\src\AddIn\AddIn\System.AddIn.csproj (System.AddIn)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class:  Utils
**
** Purpose: Random functionality that is useful for the 
**    Add-In model
**
===========================================================*/
using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
using System.Text;
using System.Diagnostics;
using System.AddIn.MiniReflection;
using System.Diagnostics.Contracts;
 
namespace System.AddIn.Hosting
{
    [Serializable]
    internal static class Utils
    {
        // This method requires that the types passed in are from the same CLR 
        // loader context.  The V2 loader defines 4 loader contexts, and only 2 of
        // them will exist in the CLR V3.  A type from an assembly loaded in one 
        // loader context is not assignable to the same type in a different loader
        // context.  This applies for normal assignment, but also shows up in 
        // Reflection, as getting different System.Type instances.  This method
        // wants to be relatively fast, so instead of doing string comparisons on
        // type names, we leverage Reflection's guarantees about singleton Type
        // instances for assemblies loaded in the same loader context.  That's why
        // the loop below can do a reference equality test.
        internal static bool HasCustomAttribute(Type attributeType, Type inspectType)
        {
            return GetCustomAttributeData(attributeType, inspectType) != null;
        }
 
        
        internal static CustomAttributeData GetCustomAttributeData(Type attributeType, Type inspectType)
        {
            // Spec#: Should this be a DebugRequires or RequiresExpensive?
            System.Diagnostics.Contracts.Contract.Requires(typeof(Attribute).IsAssignableFrom(attributeType));
            // The following precondition isn't strictly sufficient for the V2 CLR
            // because we define 4 loader contexts and expose only one boolean 
            // predicate for testing which loader context an assembly is loaded in.
            // But it is still very useful - our types must be in the same loader context.
            // 
            // Removed because sometimes we look for attributes on a type's Base type which may be Object
            // System.Diagnostics.Contracts.Contract.Requires(attributeType.Assembly.ReflectionOnly == inspectType.Assembly.ReflectionOnly);
 
            foreach (CustomAttributeData ca in CustomAttributeData.GetCustomAttributes(inspectType))
            {
                if (Object.ReferenceEquals(ca.Constructor.DeclaringType, attributeType))
                    return ca;
            }
            return null;
        }
 
        /*
        internal static bool PublicKeyTokensEqual(String token1, byte[] token2)
        {
            if (token2 == null || token2.Length == 0)
                return token1 == "null";
            if (token1 == "null")
                return false;
            Contract.Assert(token1.Length == 2 * token2.Length, "Lengths didn't match");
            for (int i = 0; i < token2.Length; i++)
            {
                int firstPart = (token1[2 * i] <= '9') ? token1[2 * i] - '0' : token1[2 * i] - 'a' + 10;
                int secondPart = (token1[2 * i + 1] <= '9') ? token1[2 * i + 1] - '0' : token1[2 * i + 1] - 'a' + 10;
                byte b1 = (byte)((firstPart << 4) + secondPart);
                if (b1 != token2[i])
                    return false;
            }
            return true;
        }
         */
 
        /*
        internal static bool PublicKeyTokensEqual(byte[] token1, byte[] token2)
        {
            if (token2 == null || token2.Length == 0)
                return token1 == null || token1.Length == 0;
            if (token1 == null)
                return false;
            System.Diagnostics.Contracts.Contract.Assert(token1.Length == token2.Length, "Lengths didn't match");
            for (int i = 0; i < token1.Length; i++)
                if (token1[i] != token2[i])
                    return false;
            return true;
        }
        */
 
        internal static bool PublicKeyMatches(AssemblyName a1, AssemblyName a2)
        {
            byte[] key = a2.GetPublicKey();
            return PublicKeyMatches(a1, key);
        }
 
        internal static bool PublicKeyMatches(System.Reflection.AssemblyName a1, byte[] publicKeyOrToken)
        {
            if (publicKeyOrToken == null)
                return a1.GetPublicKey() == null;
            byte[] publicKey = a1.GetPublicKey();
            if (publicKey != null && publicKeyOrToken.Length == publicKey.Length)
            {
                for (int i = 0; i < publicKey.Length; i++)
                    if (publicKey[i] != publicKeyOrToken[i])
                        return false;
                return true;
            }
            byte[] publicKeyToken = a1.GetPublicKeyToken();
            if (publicKeyOrToken.Length == publicKeyToken.Length)
            {
                for (int i = 0; i < publicKeyToken.Length; i++)
                    if (publicKeyToken[i] != publicKeyOrToken[i])
                        return false;
                return true;
            }
            return false;
        }
 
        internal static String PublicKeyToString(byte[] key)
        {
            if (key == null || key.Length == 0)
                return "null";
 
            StringBuilder sb = new StringBuilder(key.Length);
            foreach (byte b in key)
            {
                sb.Append(b.ToString("x2", System.Globalization.CultureInfo.InvariantCulture));
            }
            return sb.ToString();
        }
 
        // You must have already normalized the paths!
        internal static String MakeRelativePath(String path, String root)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(path));
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(root));
            if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase))
                throw new ArgumentException(Res.MakeRelativePathArgs);
            System.Diagnostics.Contracts.Contract.Requires(String.Equals(path, Path.GetFullPath(path)));
            System.Diagnostics.Contracts.Contract.Requires(String.Equals(root, Path.GetFullPath(root)));
            System.Diagnostics.Contracts.Contract.Ensures(!Path.IsPathRooted(System.Diagnostics.Contracts.Contract.Result<String>()));
            System.Diagnostics.Contracts.Contract.EndContractBlock();
 
            int skip = 0;
            char lastChar = root[root.Length - 1];
            if (lastChar != Path.DirectorySeparatorChar && lastChar != Path.AltDirectorySeparatorChar)
                skip++;
 
            String relPath = path.Substring(root.Length + skip);
            return relPath;
        }
 
        // Pass in fully qualified names.  We've factored out our assembly names
        // comparisons so that we can handle policy if necessary, and also deal with
        // potentially mal-formed assembly refs, or assembly refs that include
        // processor architecture, etc.  (I haven't implemented that, but it could be done.)
        internal static bool AssemblyRefEqualsDef(String assemblyRef, String assemblyDef)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(assemblyRef));
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(assemblyDef));
 
            return String.Equals(assemblyRef, assemblyDef);
        }
 
        // Pass in fully qualified names.  We've factored out our assembly names
        // comparisons so that we can handle policy if necessary.
        internal static bool AssemblyDefEqualsDef(String assemblyDef1, String assemblyDef2)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(assemblyDef1));
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(assemblyDef2));
 
            return String.Equals(assemblyDef1, assemblyDef2);
        }
 
        // Pass in fully qualified type names.  We've factored out our assembly names
        // comparisons so that we can handle policy if necessary.
        internal static bool FullTypeNameDefEqualsDef(String typeAndAssemblyName1, String typeAndAssemblyName2)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(typeAndAssemblyName1));
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(typeAndAssemblyName2));
 
            return String.Equals(typeAndAssemblyName1, typeAndAssemblyName2);
        }
 
        // If you're calling this method, you suspect that you've already loaded this
        // assembly, but you need to upgrade the assembly from the LoadFrom loader
        // context to the default loader context.  However, we'll have to call it
        // for an unbound set of assemblies.
        internal static Assembly FindLoadedAssemblyRef(String assemblyRef)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(assemblyRef));
 
            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) {
                if (Utils.AssemblyRefEqualsDef(assemblyRef, a.FullName)) {
                    //Console.WriteLine("FindLoadedAssemblyRef found its target (probably in the LoadFrom context).  Returning, in hopes of upgrading to default loader context.  Code base: {0}", a.CodeBase);
                    return a;
                }
            }
            return null;
        }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Justification="LoadFrom was designed for addins")]
        internal static Assembly LoadAssemblyFrom(List<String> dirsToLookIn, String assemblyRef)
        {
            int firstComma = assemblyRef.IndexOf(',');
            if (firstComma == -1)
                return null;
            String simpleName = assemblyRef.Substring(0, firstComma);
 
            List<String> potentialFileNames = new List<string>(dirsToLookIn.Count * 2);
            foreach (String path in dirsToLookIn)
            {
                String simpleFileName = Path.Combine(path, simpleName);
                String dllName = simpleFileName + ".dll";
                if (File.Exists(dllName))
                    potentialFileNames.Add(dllName);
                else if (File.Exists(simpleFileName + ".exe"))
                    potentialFileNames.Add(simpleFileName + ".exe");
            }
 
            foreach (String fileName in potentialFileNames)
            {
                try
                {
                    Assembly assembly = Assembly.LoadFrom(fileName);
                    // We should at least be comparing the public key token
                    // for the two assemblies here.  The version numbers may 
                    // potentially be different, dependent on publisher policy.
                    if (Utils.AssemblyRefEqualsDef(assemblyRef, assembly.FullName))
                    {
                        return assembly;
                    }
                }
                catch (BadImageFormatException)
                {
                }
            }
            return null;
        }
 
        // If they have full trust, give a good error message.  Otherwise, prevent information disclosure.
        [SecuritySafeCritical]
        internal static bool HasFullTrust()
        {
            try
            {
                new PermissionSet(PermissionState.Unrestricted).Demand();
                return true;
            }
            catch(SecurityException)
            {
                return false;
            }
        }
 
        //Utility method to assert permission and unload the appdomain
        [System.Security.SecurityCritical]
        [SecurityPermission(SecurityAction.Assert, ControlAppDomain = true)]
        internal static void UnloadAppDomain(AppDomain domain)
        {
            AppDomain.Unload(domain);
        }
    }
}