File: Compilation\BrowserCapabilitiesCompiler.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="BrowserCapabilitiesCompiler.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.Compilation {
 
    using System;
    using System.CodeDom;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.CodeDom.Compiler;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Security;
    using System.Security.Permissions;
    using System.Web.Configuration;
    using System.Web.Hosting;
    using System.Web.Util;
    using System.Web.UI;
    using System.Xml;
 
    static class BrowserCapabilitiesCompiler {
 
        internal static readonly VirtualPath AppBrowsersVirtualDir =
            HttpRuntime.AppDomainAppVirtualPathObject.SimpleCombineWithDir(HttpRuntime.BrowsersDirectoryName);
 
        private const string browerCapabilitiesTypeName = "BrowserCapabilities";
        private const string browerCapabilitiesCacheKey = "__browserCapabilitiesCompiler";
 
        private static Type _browserCapabilitiesFactoryBaseType;
 
        private static BrowserCapabilitiesFactoryBase _browserCapabilitiesFactoryBaseInstance;
        private static object _lockObject = new object();
        internal static Assembly AspBrowserCapsFactoryAssembly { get; set; }
 
        static BrowserCapabilitiesCompiler() {
            Assembly assembly = null;
            String publicKeyToken = BrowserCapabilitiesCodeGenerator.BrowserCapAssemblyPublicKeyToken;
 
            // If the token file cannot be found, do not load the assembly.
            if (publicKeyToken != null) {
                try {
                    string version;
 
                    // If we are targeting previous versions, try loading the 2.0 version of ASP.BrowserCapsFactory
                    // (Dev10 bug 795509)
                    if (MultiTargetingUtil.IsTargetFramework40OrAbove) {
                        version = ThisAssembly.Version;
                    } else {
                        version = "2.0.0.0";
                    }
                    assembly = Assembly.Load("ASP.BrowserCapsFactory, Version=" + version + ", Culture=neutral, PublicKeyToken=" + publicKeyToken);
                    AspBrowserCapsFactoryAssembly = assembly;
                }
                catch (FileNotFoundException) {
                }
            }
 
            // fallback when assembly cannot be found.
            if((assembly == null) || (!(assembly.GlobalAssemblyCache))) {
                _browserCapabilitiesFactoryBaseType = typeof(System.Web.Configuration.BrowserCapabilitiesFactory);
            }
            else {
                _browserCapabilitiesFactoryBaseType = assembly.GetType("ASP.BrowserCapabilitiesFactory", true);
            }
        }
 
        internal static BrowserCapabilitiesFactoryBase BrowserCapabilitiesFactory {
            get {
                if(_browserCapabilitiesFactoryBaseInstance != null) {
                    return _browserCapabilitiesFactoryBaseInstance;
                }
                Type t = GetBrowserCapabilitiesType();
                lock(_lockObject) {
                    if(_browserCapabilitiesFactoryBaseInstance == null) {
                        if (t != null) {
                            _browserCapabilitiesFactoryBaseInstance =
                                (BrowserCapabilitiesFactoryBase)Activator.CreateInstance(t);
                        }
                    }
                }
                return _browserCapabilitiesFactoryBaseInstance;
            }
        }
 
        internal static Type GetBrowserCapabilitiesFactoryBaseType() {
            return _browserCapabilitiesFactoryBaseType;
        }
 
        internal static Type GetBrowserCapabilitiesType() {
 
            //Need to assert here to check directories and files
            InternalSecurityPermissions.Unrestricted.Assert();
 
            BuildResult result = null;
 
            try {
                // Try the cache first, and if it's not there, compile it
                result = BuildManager.GetBuildResultFromCache(browerCapabilitiesCacheKey);
                if (result == null) {
                    DateTime utcStart = DateTime.UtcNow;
 
                    VirtualDirectory directory = AppBrowsersVirtualDir.GetDirectory();
 
                    // first try if app browser dir exists
                    string physicalDir = HostingEnvironment.MapPathInternal(AppBrowsersVirtualDir);
 
                    /* DevDivBugs 173531
                     * For the App_Browsers scenario, we need to cache the generated browser caps processing 
                     * code. We need to add path dependency on all files so that changes to them will 
                     * invalidate the cache entry and cause recompilation. */
                    if (directory != null && Directory.Exists(physicalDir)) {
                        ArrayList browserFileList = new ArrayList();
                        ArrayList browserFileDependenciesList = new ArrayList();
                        bool hasCustomCaps = AddBrowserFilesToList(directory, browserFileList, false);
                        if (hasCustomCaps) {
                            AddBrowserFilesToList(directory, browserFileDependenciesList, true);
                        }
                        else {
                            browserFileDependenciesList = browserFileList;
                        }
 
                        if (browserFileDependenciesList.Count > 0) {
                            ApplicationBrowserCapabilitiesBuildProvider buildProvider = new ApplicationBrowserCapabilitiesBuildProvider();
                            foreach (string virtualPath in browserFileList) {
                                buildProvider.AddFile(virtualPath);
                            }
                            
                            BuildProvidersCompiler bpc = new BuildProvidersCompiler(null /*configPath*/,
                                BuildManager.GenerateRandomAssemblyName(BuildManager.AppBrowserCapAssemblyNamePrefix));
                            
                            bpc.SetBuildProviders(new SingleObjectCollection(buildProvider));
                            CompilerResults results = bpc.PerformBuild();
                            Assembly assembly = results.CompiledAssembly;
                            // Get the type we want from the assembly
                            Type t = assembly.GetType(
                                BaseCodeDomTreeGenerator.defaultNamespace + "." + ApplicationBrowserCapabilitiesCodeGenerator.FactoryTypeName);
                            // Cache it for next time
                            result = new BuildResultCompiledType(t);
                            result.VirtualPath = AppBrowsersVirtualDir;
                            result.AddVirtualPathDependencies(browserFileDependenciesList);
 
                            BuildManager.CacheBuildResult(browerCapabilitiesCacheKey, result, utcStart);
                        }
                    }
                }
            }
            finally {
                CodeAccessPermission.RevertAssert();
            }
 
            // Simply return the global factory type.
            if (result == null)
                return _browserCapabilitiesFactoryBaseType;
 
            // Return the compiled type
            return ((BuildResultCompiledType)result).ResultType;
        }
 
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands",
            Justification="Warning was suppressed for previous version of API (different overload) in FxCop project file.")]
        private static bool AddBrowserFilesToList(
            VirtualDirectory directory, IList list, bool doRecurse) {
            bool hasCustomCaps = false;
 
            foreach (VirtualFileBase fileBase in directory.Children) {
 
                // Recursive into subdirectories.
                if (fileBase.IsDirectory) {
                    if (doRecurse) {
                        AddBrowserFilesToList((VirtualDirectory)fileBase, list, true);
                    }
                    hasCustomCaps = true;
                    continue;
                }
 
                string extension = Path.GetExtension(fileBase.Name);
                if (StringUtil.EqualsIgnoreCase(extension, ".browser")) {
                    list.Add(fileBase.VirtualPath);
                }
            }
            return hasCustomCaps;
        }
    }
 
    internal class ApplicationBrowserCapabilitiesCodeGenerator : BrowserCapabilitiesCodeGenerator {
        internal const string FactoryTypeName = "ApplicationBrowserCapabilitiesFactory";
        private OrderedDictionary _browserOverrides;
        private OrderedDictionary _defaultBrowserOverrides;
        private BrowserCapabilitiesFactoryBase _baseInstance;
        private BuildProvider _buildProvider;
 
        internal ApplicationBrowserCapabilitiesCodeGenerator(BuildProvider buildProvider) {
            _browserOverrides = new OrderedDictionary();
            _defaultBrowserOverrides = new OrderedDictionary();
            _buildProvider = buildProvider;
        }
 
        internal override bool GenerateOverrides { get { return false; } }
 
        internal override string TypeName {
            get {
                return FactoryTypeName;
            }
        }
 
        public override void Create() {
            throw new NotSupportedException();
        }
 
        private static void AddStringToHashtable(OrderedDictionary table, object key, String content, bool before) {
            ArrayList list = (ArrayList)table[key];
            if (list == null) {
                list = new ArrayList(1);
                table[key] = list;
            }
 
            if (before) {
                list.Insert(0, content);
            }
            else {
                list.Add(content);
            }
        }
 
        private static string GetFirstItemFromKey(OrderedDictionary table, object key) {
            ArrayList list = (ArrayList)table[key];
 
            if (list != null && list.Count > 0) {
                return list[0] as String;
            }
 
            return null;
        }
 
        [SuppressMessage("Microsoft.Usage", "CA2303:FlagTypeGetHashCode", Justification = "BrowserDefinition is Internal type - okay")]
        internal override void HandleUnRecognizedParentElement(BrowserDefinition bd, bool isDefault)
        {
            // Use the valid type name so we can find the corresponding parent node.
            String parentName = bd.ParentName;
            int hashKey = bd.GetType().GetHashCode() ^ parentName.GetHashCode();
 
            // Add the refID in front of the list so they gets to be called first.
            if (isDefault) {
                AddStringToHashtable(_defaultBrowserOverrides, hashKey, bd.Name, bd.IsRefID);
            } else {
                AddStringToHashtable(_browserOverrides, hashKey, bd.Name, bd.IsRefID);
            }
        }
 
        //generate the code from the parsed BrowserDefinitionTree
        //compile it, and install it in the gac
        internal void GenerateCode(AssemblyBuilder assemblyBuilder) {
            
            ProcessBrowserFiles(true /*useVirtualPath*/, BrowserCapabilitiesCompiler.AppBrowsersVirtualDir.VirtualPathString);
            ProcessCustomBrowserFiles(true /*useVirtualPath*/, BrowserCapabilitiesCompiler.AppBrowsersVirtualDir.VirtualPathString);
            
            CodeCompileUnit ccu = new CodeCompileUnit();
 
            Debug.Assert(BrowserTree != null);
            ArrayList customTreeRoots = new ArrayList();
            for (int i = 0; i < CustomTreeNames.Count; i++) {
                customTreeRoots.Add((BrowserDefinition)(((BrowserTree)CustomTreeList[i])[CustomTreeNames[i]]));
            }
 
            // namespace ASP
            CodeNamespace cnamespace = new CodeNamespace(BaseCodeDomTreeGenerator.defaultNamespace);
            //GEN: using System;
            cnamespace.Imports.Add(new CodeNamespaceImport("System"));
            //GEN: using System.Web;
            cnamespace.Imports.Add(new CodeNamespaceImport("System.Web"));
            //GEN: using System.Web.Configuration;
            cnamespace.Imports.Add(new CodeNamespaceImport("System.Web.Configuration"));
            //GEN: using System.Reflection;
            cnamespace.Imports.Add(new CodeNamespaceImport("System.Reflection"));
            //GEN: class BrowserCapabilitiesFactory
            ccu.Namespaces.Add(cnamespace);
 
            Type baseType = BrowserCapabilitiesCompiler.GetBrowserCapabilitiesFactoryBaseType();
 
            CodeTypeDeclaration factoryType = new CodeTypeDeclaration();
            factoryType.Attributes = MemberAttributes.Private;
            factoryType.IsClass = true;
            factoryType.Name = TypeName;
            factoryType.BaseTypes.Add(new CodeTypeReference(baseType));
            cnamespace.Types.Add(factoryType);
 
            BindingFlags flags = BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.NonPublic;
 
            BrowserDefinition bd = null;
            //GEN: protected override object ConfigureBrowserCapabilities(NameValueCollection headers, HttpBrowserCapabilities browserCaps)
            CodeMemberMethod method = new CodeMemberMethod();
            method.Attributes = MemberAttributes.Override | MemberAttributes.Public;
            method.ReturnType = new CodeTypeReference(typeof(void));
            method.Name = "ConfigureCustomCapabilities";
            CodeParameterDeclarationExpression cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), "headers");
            method.Parameters.Add(cpde);
            cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), "browserCaps");
            method.Parameters.Add(cpde);
            factoryType.Members.Add(method);
 
            for (int i = 0; i < customTreeRoots.Count; i++) {
                GenerateSingleProcessCall((BrowserDefinition)customTreeRoots[i], method);
            }
            
            foreach (DictionaryEntry entry in _browserOverrides) {
                object key = entry.Key;
                BrowserDefinition firstBrowserDefinition = (BrowserDefinition)BrowserTree[GetFirstItemFromKey(_browserOverrides, key)];
                
                string parentName = firstBrowserDefinition.ParentName;
                
                //
 
 
 
                if ((!TargetFrameworkUtil.HasMethod(baseType, parentName + "ProcessBrowsers", flags)) ||
                    (!TargetFrameworkUtil.HasMethod(baseType, parentName + "ProcessGateways", flags))) {
                    String parentID = firstBrowserDefinition.ParentID;
 
                    if (firstBrowserDefinition != null) {
                        throw new ConfigurationErrorsException(SR.GetString(SR.Browser_parentID_Not_Found, parentID), firstBrowserDefinition.XmlNode);
                    } else {
                        throw new ConfigurationErrorsException(SR.GetString(SR.Browser_parentID_Not_Found, parentID));
                    }
                }
               
                bool isBrowserDefinition = true;
                if (firstBrowserDefinition is GatewayDefinition) {
                    isBrowserDefinition = false;
                }
 
                //GenerateMethodsToOverrideBrowsers
                //Gen: protected override void Xxx_ProcessChildBrowsers(bool ignoreApplicationBrowsers, MNameValueCollection headers, HttpBrowserCapabilities browserCaps) ;
 
                string methodName = parentName + (isBrowserDefinition ? "ProcessBrowsers" : "ProcessGateways");
                CodeMemberMethod cmm = new CodeMemberMethod();
                cmm.Name = methodName;
                cmm.ReturnType = new CodeTypeReference(typeof(void));
                cmm.Attributes = MemberAttributes.Family | MemberAttributes.Override;
 
                if (isBrowserDefinition) {
                    cpde = new CodeParameterDeclarationExpression(typeof(bool), BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
                    cmm.Parameters.Add(cpde);
                }
                cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), "headers");
                cmm.Parameters.Add(cpde);
                cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), browserCapsVariable);
                cmm.Parameters.Add(cpde);
 
                factoryType.Members.Add(cmm);
 
                ArrayList overrides = (ArrayList)_browserOverrides[key];
                CodeStatementCollection statements = cmm.Statements;
 
                bool ignoreApplicationBrowsersVarRefGenerated = false;
                
                foreach (string browserID in overrides) {
                    bd = (BrowserDefinition)BrowserTree[browserID];
                    if (bd is GatewayDefinition || bd.IsRefID) {
                        GenerateSingleProcessCall(bd, cmm);
                    }
                    else {
                        if (!ignoreApplicationBrowsersVarRefGenerated) {
                            Debug.Assert(isBrowserDefinition);
 
                            // Gen: if (ignoreApplicationBrowsers) {
                            //      }
                            //      else {
                            //          ...
                            //      }
                            CodeConditionStatement istatement = new CodeConditionStatement();
 
                            istatement.Condition = new CodeVariableReferenceExpression(BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
 
                            cmm.Statements.Add(istatement);
                            statements = istatement.FalseStatements;
 
                            ignoreApplicationBrowsersVarRefGenerated = true;
                        }
                        statements = GenerateTrackedSingleProcessCall(statements, bd, cmm);
                        if (_baseInstance == null) {
                            // If we are targeting 4.0 or using the ASP.BrowserCapsFactory assembly generated by
                            // aspnet_regbrowsers.exe, we can simply just instantiate the type.
                            // If not, then we need to use the type BrowserCapabilitiesFactory35 that contains code
                            // from the 2.0 version of BrowserCapabilitiesFactory. This is because "baseType" is the 4.0 type
                            // that contains the new 4.0 definitions.
                            // (Dev10 bug 795509)
                            if (MultiTargetingUtil.IsTargetFramework40OrAbove || 
                                baseType.Assembly == BrowserCapabilitiesCompiler.AspBrowserCapsFactoryAssembly) {
                                _baseInstance = (BrowserCapabilitiesFactoryBase)Activator.CreateInstance(baseType);
                            }
                            else {
                                _baseInstance = new BrowserCapabilitiesFactory35();
                            }
                        }
                        int parentDepth = (int)((Triplet)_baseInstance.InternalGetBrowserElements()[parentName]).Third;
                        AddBrowserToCollectionRecursive(bd, parentDepth + 1);
                    }
                }
 
            }
 
            foreach (DictionaryEntry entry in _defaultBrowserOverrides) {
                object key = entry.Key;
 
                BrowserDefinition firstDefaultBrowserDefinition = (BrowserDefinition)DefaultTree[GetFirstItemFromKey(_defaultBrowserOverrides, key)];
                string parentName = firstDefaultBrowserDefinition.ParentName;
 
                if (baseType.GetMethod("Default" + parentName + "ProcessBrowsers", flags) == null) {
                    String parentID = firstDefaultBrowserDefinition.ParentID;
                    if (firstDefaultBrowserDefinition != null) {
                        throw new ConfigurationErrorsException(SR.GetString(SR.DefaultBrowser_parentID_Not_Found, parentID), firstDefaultBrowserDefinition.XmlNode);
                    }
                }
 
                string methodName = "Default" + parentName + "ProcessBrowsers";
                CodeMemberMethod cmm = new CodeMemberMethod();
                cmm.Name = methodName;
                cmm.ReturnType = new CodeTypeReference(typeof(void));
                cmm.Attributes = MemberAttributes.Family | MemberAttributes.Override;
                cpde = new CodeParameterDeclarationExpression(typeof(bool), BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
                cmm.Parameters.Add(cpde);
                cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), "headers");
                cmm.Parameters.Add(cpde);
                cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), browserCapsVariable);
                cmm.Parameters.Add(cpde);
                factoryType.Members.Add(cmm);
 
                ArrayList overrides = (ArrayList)_defaultBrowserOverrides[key];
 
                CodeConditionStatement istatement = new CodeConditionStatement();
                istatement.Condition = new CodeVariableReferenceExpression(BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
 
                cmm.Statements.Add(istatement);
                CodeStatementCollection statements = istatement.FalseStatements;
 
                foreach(string browserID in overrides) {
                    bd = (BrowserDefinition)DefaultTree[browserID];
                    Debug.Assert(!(bd is GatewayDefinition));
 
                    if(bd.IsRefID) {
                        GenerateSingleProcessCall(bd, cmm, "Default");
                    }
                    else {
                        statements = GenerateTrackedSingleProcessCall(statements, bd, cmm, "Default");
                    }
                }
            }
 
            // Generate process method for the browser elements
            foreach (DictionaryEntry entry in BrowserTree) {
                bd = entry.Value as BrowserDefinition;
                Debug.Assert(bd != null);
                GenerateProcessMethod(bd, factoryType);
            }
 
            for (int i = 0; i < customTreeRoots.Count; i++) {
                foreach (DictionaryEntry entry in (BrowserTree)CustomTreeList[i]) {
                    bd = entry.Value as BrowserDefinition;
                    Debug.Assert(bd != null);
                    GenerateProcessMethod(bd, factoryType);
                }
            }
 
            // Generate process method for the default browser elements
            foreach (DictionaryEntry entry in DefaultTree) {
                bd = entry.Value as BrowserDefinition;
                Debug.Assert(bd != null);
                GenerateProcessMethod(bd, factoryType, "Default");
            }
            GenerateOverrideMatchedHeaders(factoryType);
            GenerateOverrideBrowserElements(factoryType);
 
            Assembly assembly = BrowserCapabilitiesCompiler.GetBrowserCapabilitiesFactoryBaseType().Assembly;
            assemblyBuilder.AddAssemblyReference(assembly, ccu);
            assemblyBuilder.AddCodeCompileUnit(_buildProvider, ccu);
        }
 
        internal override void ProcessBrowserNode(XmlNode node, BrowserTree browserTree) {
            if (node.Name == "defaultBrowser") {
                throw new ConfigurationErrorsException(SR.GetString(SR.Browser_Not_Allowed_InAppLevel, node.Name), node);
            }
 
            base.ProcessBrowserNode(node, browserTree);
        }
    }
 
    internal class ApplicationBrowserCapabilitiesBuildProvider : BuildProvider {
 
        private ApplicationBrowserCapabilitiesCodeGenerator _codeGenerator;
 
        internal ApplicationBrowserCapabilitiesBuildProvider() {
            _codeGenerator = new ApplicationBrowserCapabilitiesCodeGenerator(this);
        }
 
        internal void AddFile(string virtualPath) {
            String filePath = HostingEnvironment.MapPathInternal(virtualPath);
            _codeGenerator.AddFile(filePath);
        }
 
        public override void GenerateCode(AssemblyBuilder assemblyBuilder) {
            _codeGenerator.GenerateCode(assemblyBuilder);
        }
    }
}