From 4814fb3e96a382adaf2b358bfd0e1e533bf94416 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 5 Aug 2014 18:07:50 +0200 Subject: [PATCH] U4-5276 - get package installer to respect binding redirects --- .../Packaging/PackageBinaryByteInspector.cs | 219 ------------------ .../Packaging/PackageBinaryInspector.cs | 107 +++++++-- .../Packaging/PackageInstallation.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 - 4 files changed, 92 insertions(+), 237 deletions(-) delete mode 100644 src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs diff --git a/src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs deleted file mode 100644 index 010e3a8521..0000000000 --- a/src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; -using System.Security.Permissions; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Packaging -{ - internal class PackageBinaryByteInspector : MarshalByRefObject - { - /// - /// Entry point to call from your code - /// - /// - /// - /// - /// - /// - /// Will perform the assembly scan in a separate app domain - /// - public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) - { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryByteInspector); - try - { - var value = (PackageBinaryByteInspector)appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - var result = value.PerformScan(assemblys.ToArray(), out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); - } - } - - /// - /// Performs the assembly scanning - /// - /// - /// - /// - /// - /// - /// This method is executed in a separate app domain - /// - internal IEnumerable PerformScan(IEnumerable assemblies, out string[] errorReport) - { - var dllsWithReference = new List(); - var errors = new List(); - var assembliesWithErrors = new List(); - - //we need this handler to resolve assembly dependencies below - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => - { - var a = Assembly.ReflectionOnlyLoad(e.Name); - if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); - return a; - }; - - //First load each dll file into the context - var loaded = assemblies.Select(Assembly.ReflectionOnlyLoad).ToList(); - - //load each of the LoadFrom assemblies into the Load context too - foreach (var a in loaded) - { - Assembly.ReflectionOnlyLoad(a.FullName); - } - - //get the list of assembly names to compare below - var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); - - //Then load each referenced assembly into the context - foreach (var a in loaded) - { - //don't load any referenced assemblies that are already found in the loaded array - this is based on name - // regardless of version. We'll assume that if the assembly found in the folder matches the assembly name - // being looked for, that is the version the user has shipped their package with and therefore it 'must' be correct - foreach (var assemblyName in a.GetReferencedAssemblies().Where(ass => loadedNames.Contains(ass.Name) == false)) - { - try - { - Assembly.ReflectionOnlyLoad(assemblyName.FullName); - } - catch (FileNotFoundException) - { - //if an exception occurs it means that a referenced assembly could not be found - errors.Add( - string.Concat("This package references the assembly '", - assemblyName.Name, - "' which was not found")); - assembliesWithErrors.Add(a); - } - catch (Exception ex) - { - //if an exception occurs it means that a referenced assembly could not be found - errors.Add( - string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", - assemblyName.Name, - "' see error log for full details.")); - assembliesWithErrors.Add(a); - LogHelper.Error("An error occurred scanning package assemblies", ex); - } - } - } - - var contractType = GetLoadFromContractType(); - - //now that we have all referenced types into the context we can look up stuff - foreach (var a in loaded.Except(assembliesWithErrors)) - { - //now we need to see if they contain any type 'T' - var reflectedAssembly = a; - - try - { - var found = reflectedAssembly.GetExportedTypes() - .Where(contractType.IsAssignableFrom); - - if (found.Any()) - { - dllsWithReference.Add(reflectedAssembly.FullName); - } - } - catch (Exception ex) - { - //This is a hack that nobody can seem to get around, I've read everything and it seems that - // this is quite a common thing when loading types into reflection only load context, so - // we're just going to ignore this specific one for now - var typeLoadEx = ex as TypeLoadException; - if (typeLoadEx != null) - { - if (typeLoadEx.Message.InvariantContains("does not have an implementation")) - { - //ignore - continue; - } - } - else - { - errors.Add( - string.Concat("This package could not be verified for compatibility. An error occurred while scanning a packaged assembly '", - a.GetName().Name, - "' see error log for full details.")); - assembliesWithErrors.Add(a); - LogHelper.Error("An error occurred scanning package assemblies", ex); - } - } - - } - - errorReport = errors.ToArray(); - return dllsWithReference; - } - - - /// - /// In order to compare types, the types must be in the same context, this method will return the type that - /// we are checking against but from the Load context. - /// - /// - /// - private static Type GetLoadFromContractType() - { - var contractAssemblyLoadFrom =Assembly.ReflectionOnlyLoad(typeof (T).Assembly.FullName); - - var contractType = contractAssemblyLoadFrom.GetExportedTypes() - .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); - - if (contractType == null) - { - throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); - } - return contractType; - } - - /// - /// Create an app domain - /// - /// - private static AppDomain GetTempAppDomain() - { - //copy the current app domain setup but don't shadow copy files - var appName = "TempDomain" + Guid.NewGuid(); - var domainSetup = new AppDomainSetup - { - ApplicationName = appName, - ShadowCopyFiles = "false", - ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, - ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, - DynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase, - LicenseFile = AppDomain.CurrentDomain.SetupInformation.LicenseFile, - LoaderOptimization = AppDomain.CurrentDomain.SetupInformation.LoaderOptimization, - PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, - PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe - }; - - //create new domain with full trust - return AppDomain.CreateDomain( - appName, - AppDomain.CurrentDomain.Evidence, - domainSetup, - new PermissionSet(PermissionState.Unrestricted)); - } - - private static string GetAssemblyPath(Assembly a) - { - var codeBase = a.CodeBase; - var uri = new Uri(codeBase); - return uri.LocalPath; - } - - } -} diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index ccf1345074..8852475543 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -9,8 +9,43 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.Packaging { + // Note + // That class uses ReflectionOnlyLoad which does NOT handle policies (bindingRedirect) and + // therefore raised warnings when installing a package, if an exact dependency could not be + // found, though it would be found via policies. So we have to explicitely apply policies + // where appropriate. + internal class PackageBinaryInspector : MarshalByRefObject { + /// + /// Entry point to call from your code + /// + /// + /// + /// + /// + /// + /// Will perform the assembly scan in a separate app domain + /// + public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) + { + var appDomain = GetTempAppDomain(); + var type = typeof(PackageBinaryInspector); + try + { + var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); + // do NOT turn PerformScan into static (even if ReSharper says so)! + var result = value.PerformScan(assemblys.ToArray(), out errorReport); + return result; + } + finally + { + AppDomain.Unload(appDomain); + } + } + /// /// Entry point to call from your code /// @@ -30,6 +65,7 @@ namespace Umbraco.Core.Packaging var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( type.Assembly.FullName, type.FullName); + // do NOT turn PerformScan into static (even if ReSharper says so)! var result = value.PerformScan(dllPath, out errorReport); return result; } @@ -39,6 +75,35 @@ namespace Umbraco.Core.Packaging } } + /// + /// Performs the assembly scanning + /// + /// + /// + /// + /// + /// + /// This method is executed in a separate app domain + /// + private IEnumerable PerformScan(IEnumerable assemblies, out string[] errorReport) + { + //we need this handler to resolve assembly dependencies when loading below + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => + { + var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); + var a = Assembly.ReflectionOnlyLoad(name); + if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); + return a; + }; + + //First load each dll file into the context + // do NOT apply policy here: we want to scan the dlls that are in the binaries + var loaded = assemblies.Select(Assembly.ReflectionOnlyLoad).ToList(); + + //scan + return PerformScan(loaded, out errorReport); + } + /// /// Performs the assembly scanning /// @@ -49,33 +114,42 @@ namespace Umbraco.Core.Packaging /// /// This method is executed in a separate app domain /// - internal IEnumerable PerformScan(string dllPath, out string[] errorReport) + private IEnumerable PerformScan(string dllPath, out string[] errorReport) { if (Directory.Exists(dllPath) == false) { throw new DirectoryNotFoundException("Could not find directory " + dllPath); } + //we need this handler to resolve assembly dependencies when loading below + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => + { + var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); + var a = Assembly.ReflectionOnlyLoad(name); + if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); + return a; + }; + + //First load each dll file into the context + // do NOT apply policy here: we want to scan the dlls that are in the path var files = Directory.GetFiles(dllPath, "*.dll"); + var loaded = files.Select(Assembly.ReflectionOnlyLoadFrom).ToList(); + + //scan + return PerformScan(loaded, out errorReport); + } + + private static IEnumerable PerformScan(IList loaded, out string[] errorReport) + { var dllsWithReference = new List(); var errors = new List(); var assembliesWithErrors = new List(); - //we need this handler to resolve assembly dependencies below - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => - { - var a = Assembly.ReflectionOnlyLoad(e.Name); - if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); - return a; - }; - - //First load each dll file into the context - var loaded = files.Select(Assembly.ReflectionOnlyLoadFrom).ToList(); - //load each of the LoadFrom assemblies into the Load context too foreach (var a in loaded) { - Assembly.ReflectionOnlyLoad(a.FullName); + var name = AppDomain.CurrentDomain.ApplyPolicy(a.FullName); + Assembly.ReflectionOnlyLoad(name); } //get the list of assembly names to compare below @@ -91,7 +165,8 @@ namespace Umbraco.Core.Packaging { try { - Assembly.ReflectionOnlyLoad(assemblyName.FullName); + var name = AppDomain.CurrentDomain.ApplyPolicy(assemblyName.FullName); + Assembly.ReflectionOnlyLoad(name); } catch (FileNotFoundException) { @@ -164,7 +239,6 @@ namespace Umbraco.Core.Packaging return dllsWithReference; } - /// /// In order to compare types, the types must be in the same context, this method will return the type that /// we are checking against but from the Load context. @@ -173,7 +247,8 @@ namespace Umbraco.Core.Packaging /// private static Type GetLoadFromContractType() { - var contractAssemblyLoadFrom =Assembly.ReflectionOnlyLoad(typeof (T).Assembly.FullName); + var name = AppDomain.CurrentDomain.ApplyPolicy(typeof(T).Assembly.FullName); + var contractAssemblyLoadFrom = Assembly.ReflectionOnlyLoad(name); var contractType = contractAssemblyLoadFrom.GetExportedTypes() .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 8ca115ea8b..74d1691c2b 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -455,7 +455,7 @@ namespace Umbraco.Core.Packaging // Now we want to see if the DLLs contain any legacy data types since we want to warn people about that string[] assemblyErrors; IEnumerable assemblyesToScan =_packageExtraction.ReadFilesFromArchive(packagePath, dlls); - return PackageBinaryByteInspector.ScanAssembliesForTypeReference(assemblyesToScan, out assemblyErrors).ToArray(); + return PackageBinaryInspector.ScanAssembliesForTypeReference(assemblyesToScan, out assemblyErrors).ToArray(); } private KeyValuePair[] FindUnsecureFiles(IEnumerable> sourceDestinationPair) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index fa1f6767b9..096ee04948 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -372,7 +372,6 @@ -