File: System\Addin\Hosting\InspectionWorker.cs
Project: ndp\fx\src\AddIn\AddIn\System.AddIn.csproj (System.AddIn)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.AddIn.MiniReflection;
using System.AddIn.Pipeline;
using System.AddIn;
using System.Diagnostics.Contracts;
using TypeInfo=System.AddIn.MiniReflection.TypeInfo;
 
namespace System.AddIn.Hosting
{
 
    [Serializable]
    internal struct InspectionResults
    {
        internal List<PipelineComponent> Components;
        internal Collection<String> Warnings;
    }
 
    internal sealed class InspectionWorker : MarshalByRefObject
    {
        private String _assemblyFileName;
        private String _pipelineRootDirectory;  
        private PipelineComponentType _currentComponentType;
        private static Assembly SystemAddInInReflectionOnlyContext;
        private static Assembly SystemAddInContractsInReflectionOnlyContext;
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification="Initialization needs to be done in this order")]
        static InspectionWorker()
        {
            // Since we're using Reflection-only LoadFrom, we must pre-load 
            // dependent assemblies, such as System.AddIn.dll (this assembly).
 
            SystemAddInInReflectionOnlyContext = Assembly.ReflectionOnlyLoad(typeof(AddInStore).Assembly.FullName);
            SystemAddInContractsInReflectionOnlyContext = Assembly.ReflectionOnlyLoad(typeof(System.AddIn.Contract.IContract).Assembly.FullName);
            PipelineComponent.SetTypesFromReflectionLoaderContext(SystemAddInInReflectionOnlyContext, SystemAddInContractsInReflectionOnlyContext);
        }
 
        // Soon, this assembly resolve event won't be used during inspection.
        // But we'll need this exact same code during activation.
        internal Assembly ResolveAssembly(Object sender, ResolveEventArgs args)
        {
            String assemblyRef = args.Name;
            // I need to look in both the directory structure on disk for add-in
            // pipeline components, as well as in the GAC for assemblies like 
            // System.dll, which I haven't pre-loaded into the ReflectionOnly 
            // loader context.  
            // LoadFrom respects publisher policy, but ReflectionOnlyLoadFrom doesn't,
            // but that's OK.
            // Do I have to look in the GAC _first_ to mimic behavior
            // at runtime, which would respect policy when calling LoadFrom?
            // The problem with that is I'll be looking in the GAC for assemblies
            // that won't exist some portion of the time, meaning we have
            // to throw and ---- FileNotFoundExceptions.  The debugging experience
            // and performance are horrible.
 
            String simpleName = assemblyRef.Substring(0, assemblyRef.IndexOf(','));
            if (String.Equals(simpleName, "System.AddIn"))
                return SystemAddInInReflectionOnlyContext;
            if (String.Equals(simpleName, "System.AddIn.Contract"))
                return SystemAddInContractsInReflectionOnlyContext;
            String rootDir = Path.GetDirectoryName(Path.GetDirectoryName(_assemblyFileName));
            if (_currentComponentType == PipelineComponentType.AddIn)
                rootDir = Path.GetDirectoryName(rootDir);
 
            List<String> dirsToLookIn = new List<String>();
            switch(_currentComponentType)
            {
                case PipelineComponentType.HostAdapter:
                    // Look in contract directory.  For loading the HAV,
                    // we can't do that at discovery time since we don't know
                    // which directory contains the HAV.  This is why we're
                    // writing our own metadata parser in managed code.  
                    // At activation time, the HAV should already be loaded 
                    // within the host's AppDomain.
                    dirsToLookIn.Add(Path.Combine(rootDir, AddInStore.ContractsDirName));
                    break;
 
                case PipelineComponentType.Contract:
                    break;
 
                case PipelineComponentType.AddInAdapter:
                    // Look in contract directory and addin base directory.
                    dirsToLookIn.Add(Path.Combine(rootDir, AddInStore.ContractsDirName));
                    dirsToLookIn.Add(Path.Combine(rootDir, AddInStore.AddInBasesDirName));
                    break;
 
                case PipelineComponentType.AddInBase:
                    // look for other assemblies in the same folder
                    dirsToLookIn.Add(Path.Combine(rootDir, AddInStore.AddInBasesDirName));
                    // @
 
 
 
 
                    break;
 
                    /*
                case PipelineComponentType.AddIn:
                    // Look in both the add-in's directory and the add-in base's
                    // directory.  We do the first by setting the app base for the AppDomain,
                    // but we may not be able to do that if the user created the appdomain.
                    dirsToLookIn.Add(Path.Combine(rootDir, AddInBasesDirName));
                    dirsToLookIn.Add(Path.GetDirectoryName(_assemblyFileName));
                    break;
                    */
 
                default:
                    System.Diagnostics.Contracts.Contract.Assert(false, "Fell through switch in the inspection assembly resolve event!");
                    break;
            }
 
            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 a = Assembly.ReflectionOnlyLoadFrom(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, a.FullName))
                        return a;
                }
                catch (BadImageFormatException)
                {
                }
            }
 
            // Look in the GAC.  It may not be there, so we need to catch 
            // FileNotFoundException.
            // As an optimization, don't probe in the GAC if the public
            // key token is null, because by definition the assembly 
            // can't be in the GAC.
            if (!assemblyRef.Contains("PublicKeyToken=null"))
            {
                try
                {
                        return Assembly.ReflectionOnlyLoad(assemblyRef);
                }
                catch (FileNotFoundException) {}
                
                try
                {
                    // As a final effort, look in the GAC after appying loader policy. 
                    // This lets us resolves references for assemblies built against an older version of the framework.
                    return Assembly.ReflectionOnlyLoad(AppDomain.CurrentDomain.ApplyPolicy(assemblyRef));
                }
                catch (FileNotFoundException) {}
            }
            
            //Console.WriteLine("Couldn't resolve assembly {0} while loading a {1}", simpleName, _currentComponentType);
            return null;
        }
 
        // Note that Inspect sets state around for the assembly resolve event in a way that isn't currently threadsafe.
        // <SecurityKernel Critical="True" Ring="0">
        // <SatisfiesLinkDemand Name="AppDomain.add_ReflectionOnlyAssemblyResolve(System.ResolveEventHandler):System.Void" />
        // <SatisfiesLinkDemand Name="AppDomain.remove_ReflectionOnlyAssemblyResolve(System.ResolveEventHandler):System.Void" />
        // </SecurityKernel>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling"), 
         System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed"),
         System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"),
         System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
        [System.Security.SecuritySafeCritical]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2128:SecurityTransparentCodeShouldNotAssert", Justification = "This is a SecurityRules.Level1 assembly, in which this rule is being incorrectly applied")]
        internal InspectionResults Inspect(PipelineComponentType componentType, string assemblyFileName, string pipelineRootDirectory)
        {
            System.Diagnostics.Contracts.Contract.Requires(assemblyFileName != null);
            System.Diagnostics.Contracts.Contract.Requires(pipelineRootDirectory != null);
 
            _assemblyFileName = assemblyFileName;
            _pipelineRootDirectory = pipelineRootDirectory;
 
            // Set up the assembly resolve event.
            _currentComponentType = componentType;
            ResolveEventHandler assemblyResolver = new ResolveEventHandler(ResolveAssembly);
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += assemblyResolver;
 
            InspectionResults retval = new InspectionResults();
            retval.Components = new List<PipelineComponent>();
            retval.Warnings = new Collection<String>();
            Type[] publicTypes;
            String assemblyName = null;
 
            // Need to assert again here because we are in a new appdomain
            FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.PathDiscovery |
                    FileIOPermissionAccess.Read, _pipelineRootDirectory);
            permission.Assert();
 
            try
            {
                // We want to load the assembly WITHOUT REGARD OF PUBLISHER POLICY.
                // If the directory structure contains v1.0 of a component and v1.1
                // exists in the GAC and is a security fix to v1.0, we still want to
                // inspect v1.0.  (The reason is we have other parts of the 
                // pipeline that were likely compiled against v1.0, not v1.1, and
                // we do type comparisons by comparing the fully qualified assembly 
                // name.)  LoadFrom unfortunately respects policy.  Assembly's
                // ReflectionOnlyLoad(byte[]) doesn't.  ReflectionOnlyLoadFrom(String)
                // does respect policy if you've set DEVPATH, but only as a bug.
                // We don't think setting DEVPATH is interesting.
                Assembly a = Assembly.ReflectionOnlyLoadFrom(_assemblyFileName);
                publicTypes = a.GetTypes();
                assemblyName = a.FullName;
            }
            catch (FileNotFoundException fnf)
            {
                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, Res.AssemblyLoadFileNotFound, fnf.Message, fnf.FileName));
                return retval;
            }
            catch (Exception e)
            {
                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, Res.AssemblyLoadThrew, e.GetType().Name, e.Message, _assemblyFileName));
                return retval;
            }
            PipelineComponent component = null;
 
            String relativeFileName = Utils.MakeRelativePath(_assemblyFileName, _pipelineRootDirectory);
            Type lastType = null;
            try
            {
                // Iterate over public types, looking for the appropriate custom attributes.
                foreach (Type type in publicTypes)
                {
                    component = null;
                    lastType = type;
                    switch (componentType)
                    {
                        case PipelineComponentType.Contract:
                            if (!Utils.HasCustomAttribute(PipelineComponent.ContractAttributeInReflectionLoaderContext, type))
                                continue;
 
                            component = new ContractComponent(new TypeInfo(type), relativeFileName);
                            break;
 
                        case PipelineComponentType.AddInAdapter:
                            if (!Utils.HasCustomAttribute(PipelineComponent.AddInAdapterAttributeInReflectionLoaderContext, type))
                                continue;
 
                            component = new AddInAdapter(new TypeInfo(type), relativeFileName);
                            break;
 
                        case PipelineComponentType.AddInBase:
                            if (Utils.HasCustomAttribute(PipelineComponent.AddInAttributeInReflectionLoaderContext, type))
                                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, Res.AddInInAddInViewFolder, type.Name, _assemblyFileName));
 
                            if (!Utils.HasCustomAttribute(PipelineComponent.AddInBaseAttributeInReflectionLoaderContext, type))
                                continue;
 
                            TypeInfo[] activatableAs = null;
                            CustomAttributeData cad = Utils.GetCustomAttributeData(PipelineComponent.AddInBaseAttributeInReflectionLoaderContext, type);
                            foreach(CustomAttributeNamedArgument cana in cad.NamedArguments)
                            {
                                if (cana.MemberInfo.Name == "ActivatableAs")
                                {
                                    CustomAttributeTypedArgument arg = cana.TypedValue;
                                    ReadOnlyCollection<CustomAttributeTypedArgument> types = (ReadOnlyCollection<CustomAttributeTypedArgument>)arg.Value;
                                    activatableAs = new TypeInfo[types.Count];
                                    int i = 0;
                                    foreach (CustomAttributeTypedArgument subArg in types)
                                    {
                                        activatableAs[i++] = new TypeInfo((Type)subArg.Value);
                                    }
                                }
                            }
 
                            component = new AddInBase(new TypeInfo(type), activatableAs, relativeFileName, assemblyName);
 
                            break;
 
                        default:
                            System.Diagnostics.Contracts.Contract.Assert(false, "Fell through switch - unrecognized componentType in InspectionWorker.Inspect");
                            break;
                    }  // switch
 
                    // If we found a component, make sure it satisfies all of its constraints, and give our
                    // PipelineComponents a chance to initialize state.
                    if (component != null)
                    {
                        if (component.Validate(type, retval.Warnings))
                            retval.Components.Add(component);
                    }
                } // foreach type in the assembly
            } // try
            catch (FileNotFoundException fnf)
            {
                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, Res.AssemblyLoadFileNotFound, fnf.Message, fnf.FileName));
                return retval;
            }
            catch (NotImplementedException)
            {
                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, Res.NotImplementedFeatureBadCtorParamOrAssembly, 
                    _assemblyFileName, (lastType == null) ? "" : lastType.FullName));
                return retval;
            }
            catch (Exception e)
            {
                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, Res.InspectingAssemblyThrew, e.GetType().Name, e.Message, _assemblyFileName));
                return retval;
            }
 
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= assemblyResolver;
 
            if (retval.Components.Count == 0 && _currentComponentType != PipelineComponentType.AddIn && _currentComponentType != PipelineComponentType.AddInBase)
            {
                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, Res.NoAddInModelPartsFound, componentType, _assemblyFileName));
            }
#if ADDIN_VERBOSE_WARNINGS
            foreach (PipelineComponent c in retval.Components)
                retval.Warnings.Add(String.Format(CultureInfo.CurrentCulture, "Found a {0}.  Name: {1}  Assembly: {2}", componentType, c.SimpleName, c.AssemblySimpleName));
#endif
            return retval;
        }
    }
}