//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Hosting { using System; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Remoting; using System.Runtime.Versioning; // Used to locate a custom loader implementation within a bin-deployed assembly. internal sealed class CustomLoaderHelper : MarshalByRefObject { // the first framework version where the custom loader feature was implemented private static readonly string _customLoaderTargetFrameworkName = new FrameworkName(".NETFramework", new Version(4, 5, 1)).ToString(); private static readonly string _customLoaderAssemblyName = typeof(CustomLoaderHelper).Assembly.FullName; private static readonly string _customLoaderTypeName = typeof(CustomLoaderHelper).FullName; private static readonly Guid IID_ICustomLoader = new Guid("50A3CE65-2F9F-44E9-9094-32C6C928F966"); // Instances of this type should only ever be created via reflection (see call to CreateObjectAndUnwrap // in GetCustomLoader). private CustomLoaderHelper() { } internal static IObjectHandle GetCustomLoader(ICustomLoaderHelperFunctions helperFunctions, string appConfigMetabasePath, string configFilePath, string customLoaderPhysicalPath, out AppDomain newlyCreatedAppDomain) { // Step 1: Does the host allow custom loaders? bool? customLoaderIsEnabled = helperFunctions.CustomLoaderIsEnabled; if (customLoaderIsEnabled.HasValue) { if ((bool)customLoaderIsEnabled) { // The custom loader is enabled; move on to the next step. } else { // The custom loader is disabled, fail. throw new NotSupportedException(ApplicationServicesStrings.CustomLoader_ForbiddenByHost); } } else { // The host hasn't set a policy, so we'll fall back to our default logic of checking the application's trust level. if (!IsFullyTrusted(helperFunctions, appConfigMetabasePath)) { throw new NotSupportedException(ApplicationServicesStrings.CustomLoader_NotInFullTrust); } } // Step 2: Create the new AD string binFolderPhysicalPath = helperFunctions.MapPath("/bin/"); AppDomainSetup setup = new AppDomainSetup() { PrivateBinPathProbe = "*", // disable loading from app base PrivateBinPath = binFolderPhysicalPath, ApplicationBase = helperFunctions.AppPhysicalPath, TargetFrameworkName = _customLoaderTargetFrameworkName }; if (configFilePath != null) { setup.ConfigurationFile = configFilePath; } AppDomain newAppDomainForCustomLoader = AppDomain.CreateDomain("aspnet-custom-loader-" + Guid.NewGuid(), null, setup); try { // Step 3: Instantiate helper in new AD so that we can get a reference to the loader CustomLoaderHelper helper = (CustomLoaderHelper)newAppDomainForCustomLoader.CreateInstanceAndUnwrap(_customLoaderAssemblyName, _customLoaderTypeName, ignoreCase: false, bindingAttr: BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, binder: null, args: null, culture: null, activationAttributes: null); ObjectHandle ohCustomLoader = helper.GetCustomLoaderImpl(customLoaderPhysicalPath); // If we got this far, success! newlyCreatedAppDomain = newAppDomainForCustomLoader; return ohCustomLoader; } catch { // If something went wrong, kill the new AD. AppDomain.Unload(newAppDomainForCustomLoader); throw; } } private ObjectHandle GetCustomLoaderImpl(string customLoaderPhysicalPath) { // Step 4: Find the implementation in the custom loader assembly // Since we have set the private bin path, we can use this call to Assembly.Load // to avoid the load-from context, which has weird behaviors. AssemblyName customLoaderAssemblyName = AssemblyName.GetAssemblyName(customLoaderPhysicalPath); Assembly customLoaderAssembly = Assembly.Load(customLoaderAssemblyName); CustomLoaderAttribute customLoaderAttribute = customLoaderAssembly.GetCustomAttribute(); if (customLoaderAttribute == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ApplicationServicesStrings.CustomLoader_NoAttributeFound, customLoaderAssemblyName)); } // Step 5: Instantiate the custom loader and return a reference back to native code object customLoader = Activator.CreateInstance(customLoaderAttribute.CustomLoaderType); // This check isn't strictly necessary since the unmanaged layer will handle QueryInterface failures // appropriately, but we have an opportunity to provide a better error message at this layer. if (!ObjectImplementsComInterface(customLoader, IID_ICustomLoader)) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ApplicationServicesStrings.CustomLoader_MustImplementICustomLoader, customLoader.GetType())); } return new ObjectHandle(customLoader); } private static bool IsFullyTrusted(ICustomLoaderHelperFunctions helperFunctions, string appConfigMetabasePath) { // The managed configuration system hasn't yet been instantiated but the IIS native config system understands // ASP.NET configuration and honors hierarchy and section locking. try { // Must exactly match , as this is what ApplicationManager expects. string trustLevel = helperFunctions.GetTrustLevel(appConfigMetabasePath); return String.Equals("Full", trustLevel, StringComparison.Ordinal); } catch { // If any of the sections are locked or there is a config error, bail. return false; } } private static bool ObjectImplementsComInterface(object o, Guid iid) { IntPtr pUnknown = IntPtr.Zero; IntPtr pInterface = IntPtr.Zero; try { pUnknown = Marshal.GetIUnknownForObject(o); // AddRef int hr = Marshal.QueryInterface(pUnknown, ref iid, out pInterface); // AddRef return (hr == 0 && pInterface != IntPtr.Zero); } finally { if (pUnknown != IntPtr.Zero) { Marshal.Release(pUnknown); } if (pInterface != IntPtr.Zero) { Marshal.Release(pInterface); } } } } }