1 //------------------------------------------------------------------------------
2 // <copyright file="BrowserCapabilitiesCompiler.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Web.Compilation {
8 
9     using System;
10     using System.CodeDom;
11     using System.Collections;
12     using System.Collections.Specialized;
13     using System.Configuration;
14     using System.CodeDom.Compiler;
15     using System.Diagnostics.CodeAnalysis;
16     using System.Globalization;
17     using System.IO;
18     using System.Reflection;
19     using System.Security;
20     using System.Security.Permissions;
21     using System.Web.Configuration;
22     using System.Web.Hosting;
23     using System.Web.Util;
24     using System.Web.UI;
25     using System.Xml;
26 
27     static class BrowserCapabilitiesCompiler {
28 
29         internal static readonly VirtualPath AppBrowsersVirtualDir =
30             HttpRuntime.AppDomainAppVirtualPathObject.SimpleCombineWithDir(HttpRuntime.BrowsersDirectoryName);
31 
32         private const string browerCapabilitiesTypeName = "BrowserCapabilities";
33         private const string browerCapabilitiesCacheKey = "__browserCapabilitiesCompiler";
34 
35         private static Type _browserCapabilitiesFactoryBaseType;
36 
37         private static BrowserCapabilitiesFactoryBase _browserCapabilitiesFactoryBaseInstance;
38         private static object _lockObject = new object();
39         internal static Assembly AspBrowserCapsFactoryAssembly { get; set; }
40 
BrowserCapabilitiesCompiler()41         static BrowserCapabilitiesCompiler() {
42             Assembly assembly = null;
43             String publicKeyToken = BrowserCapabilitiesCodeGenerator.BrowserCapAssemblyPublicKeyToken;
44 
45             // If the token file cannot be found, do not load the assembly.
46             if (publicKeyToken != null) {
47                 try {
48                     string version;
49 
50                     // If we are targeting previous versions, try loading the 2.0 version of ASP.BrowserCapsFactory
51                     // (Dev10 bug 795509)
52                     if (MultiTargetingUtil.IsTargetFramework40OrAbove) {
53                         version = ThisAssembly.Version;
54                     } else {
55                         version = "2.0.0.0";
56                     }
57                     assembly = Assembly.Load("ASP.BrowserCapsFactory, Version=" + version + ", Culture=neutral, PublicKeyToken=" + publicKeyToken);
58                     AspBrowserCapsFactoryAssembly = assembly;
59                 }
60                 catch (FileNotFoundException) {
61                 }
62             }
63 
64             // fallback when assembly cannot be found.
65             if((assembly == null) || (!(assembly.GlobalAssemblyCache))) {
66                 _browserCapabilitiesFactoryBaseType = typeof(System.Web.Configuration.BrowserCapabilitiesFactory);
67             }
68             else {
69                 _browserCapabilitiesFactoryBaseType = assembly.GetType("ASP.BrowserCapabilitiesFactory", true);
70             }
71         }
72 
73         internal static BrowserCapabilitiesFactoryBase BrowserCapabilitiesFactory {
74             get {
75                 if(_browserCapabilitiesFactoryBaseInstance != null) {
76                     return _browserCapabilitiesFactoryBaseInstance;
77                 }
78                 Type t = GetBrowserCapabilitiesType();
79                 lock(_lockObject) {
80                     if(_browserCapabilitiesFactoryBaseInstance == null) {
81                         if (t != null) {
82                             _browserCapabilitiesFactoryBaseInstance =
83                                 (BrowserCapabilitiesFactoryBase)Activator.CreateInstance(t);
84                         }
85                     }
86                 }
87                 return _browserCapabilitiesFactoryBaseInstance;
88             }
89         }
90 
GetBrowserCapabilitiesFactoryBaseType()91         internal static Type GetBrowserCapabilitiesFactoryBaseType() {
92             return _browserCapabilitiesFactoryBaseType;
93         }
94 
GetBrowserCapabilitiesType()95         internal static Type GetBrowserCapabilitiesType() {
96 
97             //Need to assert here to check directories and files
98             InternalSecurityPermissions.Unrestricted.Assert();
99 
100             BuildResult result = null;
101 
102             try {
103                 // Try the cache first, and if it's not there, compile it
104                 result = BuildManager.GetBuildResultFromCache(browerCapabilitiesCacheKey);
105                 if (result == null) {
106                     DateTime utcStart = DateTime.UtcNow;
107 
108                     VirtualDirectory directory = AppBrowsersVirtualDir.GetDirectory();
109 
110                     // first try if app browser dir exists
111                     string physicalDir = HostingEnvironment.MapPathInternal(AppBrowsersVirtualDir);
112 
113                     /* DevDivBugs 173531
114                      * For the App_Browsers scenario, we need to cache the generated browser caps processing
115                      * code. We need to add path dependency on all files so that changes to them will
116                      * invalidate the cache entry and cause recompilation. */
117                     if (directory != null && Directory.Exists(physicalDir)) {
118                         ArrayList browserFileList = new ArrayList();
119                         ArrayList browserFileDependenciesList = new ArrayList();
120                         bool hasCustomCaps = AddBrowserFilesToList(directory, browserFileList, false);
121                         if (hasCustomCaps) {
122                             AddBrowserFilesToList(directory, browserFileDependenciesList, true);
123                         }
124                         else {
125                             browserFileDependenciesList = browserFileList;
126                         }
127 
128                         if (browserFileDependenciesList.Count > 0) {
129                             ApplicationBrowserCapabilitiesBuildProvider buildProvider = new ApplicationBrowserCapabilitiesBuildProvider();
130                             foreach (string virtualPath in browserFileList) {
131                                 buildProvider.AddFile(virtualPath);
132                             }
133 
134                             BuildProvidersCompiler bpc = new BuildProvidersCompiler(null /*configPath*/,
135                                 BuildManager.GenerateRandomAssemblyName(BuildManager.AppBrowserCapAssemblyNamePrefix));
136 
137                             bpc.SetBuildProviders(new SingleObjectCollection(buildProvider));
138                             CompilerResults results = bpc.PerformBuild();
139                             Assembly assembly = results.CompiledAssembly;
140                             // Get the type we want from the assembly
141                             Type t = assembly.GetType(
142                                 BaseCodeDomTreeGenerator.defaultNamespace + "." + ApplicationBrowserCapabilitiesCodeGenerator.FactoryTypeName);
143                             // Cache it for next time
144                             result = new BuildResultCompiledType(t);
145                             result.VirtualPath = AppBrowsersVirtualDir;
146                             result.AddVirtualPathDependencies(browserFileDependenciesList);
147 
148                             BuildManager.CacheBuildResult(browerCapabilitiesCacheKey, result, utcStart);
149                         }
150                     }
151                 }
152             }
153             finally {
154                 CodeAccessPermission.RevertAssert();
155             }
156 
157             // Simply return the global factory type.
158             if (result == null)
159                 return _browserCapabilitiesFactoryBaseType;
160 
161             // Return the compiled type
162             return ((BuildResultCompiledType)result).ResultType;
163         }
164 
165         [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands",
166             Justification="Warning was suppressed for previous version of API (different overload) in FxCop project file.")]
AddBrowserFilesToList( VirtualDirectory directory, IList list, bool doRecurse)167         private static bool AddBrowserFilesToList(
168             VirtualDirectory directory, IList list, bool doRecurse) {
169             bool hasCustomCaps = false;
170 
171             foreach (VirtualFileBase fileBase in directory.Children) {
172 
173                 // Recursive into subdirectories.
174                 if (fileBase.IsDirectory) {
175                     if (doRecurse) {
176                         AddBrowserFilesToList((VirtualDirectory)fileBase, list, true);
177                     }
178                     hasCustomCaps = true;
179                     continue;
180                 }
181 
182                 string extension = Path.GetExtension(fileBase.Name);
183                 if (StringUtil.EqualsIgnoreCase(extension, ".browser")) {
184                     list.Add(fileBase.VirtualPath);
185                 }
186             }
187             return hasCustomCaps;
188         }
189     }
190 
191     internal class ApplicationBrowserCapabilitiesCodeGenerator : BrowserCapabilitiesCodeGenerator {
192         internal const string FactoryTypeName = "ApplicationBrowserCapabilitiesFactory";
193         private OrderedDictionary _browserOverrides;
194         private OrderedDictionary _defaultBrowserOverrides;
195         private BrowserCapabilitiesFactoryBase _baseInstance;
196         private BuildProvider _buildProvider;
197 
ApplicationBrowserCapabilitiesCodeGenerator(BuildProvider buildProvider)198         internal ApplicationBrowserCapabilitiesCodeGenerator(BuildProvider buildProvider) {
199             _browserOverrides = new OrderedDictionary();
200             _defaultBrowserOverrides = new OrderedDictionary();
201             _buildProvider = buildProvider;
202         }
203 
204         internal override bool GenerateOverrides { get { return false; } }
205 
206         internal override string TypeName {
207             get {
208                 return FactoryTypeName;
209             }
210         }
211 
Create()212         public override void Create() {
213             throw new NotSupportedException();
214         }
215 
AddStringToHashtable(OrderedDictionary table, object key, String content, bool before)216         private static void AddStringToHashtable(OrderedDictionary table, object key, String content, bool before) {
217             ArrayList list = (ArrayList)table[key];
218             if (list == null) {
219                 list = new ArrayList(1);
220                 table[key] = list;
221             }
222 
223             if (before) {
224                 list.Insert(0, content);
225             }
226             else {
227                 list.Add(content);
228             }
229         }
230 
GetFirstItemFromKey(OrderedDictionary table, object key)231         private static string GetFirstItemFromKey(OrderedDictionary table, object key) {
232             ArrayList list = (ArrayList)table[key];
233 
234             if (list != null && list.Count > 0) {
235                 return list[0] as String;
236             }
237 
238             return null;
239         }
240 
241         [SuppressMessage("Microsoft.Usage", "CA2303:FlagTypeGetHashCode", Justification = "BrowserDefinition is Internal type - okay")]
HandleUnRecognizedParentElement(BrowserDefinition bd, bool isDefault)242         internal override void HandleUnRecognizedParentElement(BrowserDefinition bd, bool isDefault)
243         {
244             // Use the valid type name so we can find the corresponding parent node.
245             String parentName = bd.ParentName;
246             int hashKey = bd.GetType().GetHashCode() ^ parentName.GetHashCode();
247 
248             // Add the refID in front of the list so they gets to be called first.
249             if (isDefault) {
250                 AddStringToHashtable(_defaultBrowserOverrides, hashKey, bd.Name, bd.IsRefID);
251             } else {
252                 AddStringToHashtable(_browserOverrides, hashKey, bd.Name, bd.IsRefID);
253             }
254         }
255 
256         //generate the code from the parsed BrowserDefinitionTree
257         //compile it, and install it in the gac
GenerateCode(AssemblyBuilder assemblyBuilder)258         internal void GenerateCode(AssemblyBuilder assemblyBuilder) {
259 
260             ProcessBrowserFiles(true /*useVirtualPath*/, BrowserCapabilitiesCompiler.AppBrowsersVirtualDir.VirtualPathString);
261             ProcessCustomBrowserFiles(true /*useVirtualPath*/, BrowserCapabilitiesCompiler.AppBrowsersVirtualDir.VirtualPathString);
262 
263             CodeCompileUnit ccu = new CodeCompileUnit();
264 
265             Debug.Assert(BrowserTree != null);
266             ArrayList customTreeRoots = new ArrayList();
267             for (int i = 0; i < CustomTreeNames.Count; i++) {
268                 customTreeRoots.Add((BrowserDefinition)(((BrowserTree)CustomTreeList[i])[CustomTreeNames[i]]));
269             }
270 
271             // namespace ASP
272             CodeNamespace cnamespace = new CodeNamespace(BaseCodeDomTreeGenerator.defaultNamespace);
273             //GEN: using System;
274             cnamespace.Imports.Add(new CodeNamespaceImport("System"));
275             //GEN: using System.Web;
276             cnamespace.Imports.Add(new CodeNamespaceImport("System.Web"));
277             //GEN: using System.Web.Configuration;
278             cnamespace.Imports.Add(new CodeNamespaceImport("System.Web.Configuration"));
279             //GEN: using System.Reflection;
280             cnamespace.Imports.Add(new CodeNamespaceImport("System.Reflection"));
281             //GEN: class BrowserCapabilitiesFactory
282             ccu.Namespaces.Add(cnamespace);
283 
284             Type baseType = BrowserCapabilitiesCompiler.GetBrowserCapabilitiesFactoryBaseType();
285 
286             CodeTypeDeclaration factoryType = new CodeTypeDeclaration();
287             factoryType.Attributes = MemberAttributes.Private;
288             factoryType.IsClass = true;
289             factoryType.Name = TypeName;
290             factoryType.BaseTypes.Add(new CodeTypeReference(baseType));
291             cnamespace.Types.Add(factoryType);
292 
293             BindingFlags flags = BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.NonPublic;
294 
295             BrowserDefinition bd = null;
296             //GEN: protected override object ConfigureBrowserCapabilities(NameValueCollection headers, HttpBrowserCapabilities browserCaps)
297             CodeMemberMethod method = new CodeMemberMethod();
298             method.Attributes = MemberAttributes.Override | MemberAttributes.Public;
299             method.ReturnType = new CodeTypeReference(typeof(void));
300             method.Name = "ConfigureCustomCapabilities";
301             CodeParameterDeclarationExpression cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), "headers");
302             method.Parameters.Add(cpde);
303             cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), "browserCaps");
304             method.Parameters.Add(cpde);
305             factoryType.Members.Add(method);
306 
307             for (int i = 0; i < customTreeRoots.Count; i++) {
308                 GenerateSingleProcessCall((BrowserDefinition)customTreeRoots[i], method);
309             }
310 
311             foreach (DictionaryEntry entry in _browserOverrides) {
312                 object key = entry.Key;
313                 BrowserDefinition firstBrowserDefinition = (BrowserDefinition)BrowserTree[GetFirstItemFromKey(_browserOverrides, key)];
314 
315                 string parentName = firstBrowserDefinition.ParentName;
316 
317                 //
318 
319 
320 
321                 if ((!TargetFrameworkUtil.HasMethod(baseType, parentName + "ProcessBrowsers", flags)) ||
322                     (!TargetFrameworkUtil.HasMethod(baseType, parentName + "ProcessGateways", flags))) {
323                     String parentID = firstBrowserDefinition.ParentID;
324 
325                     if (firstBrowserDefinition != null) {
326                         throw new ConfigurationErrorsException(SR.GetString(SR.Browser_parentID_Not_Found, parentID), firstBrowserDefinition.XmlNode);
327                     } else {
328                         throw new ConfigurationErrorsException(SR.GetString(SR.Browser_parentID_Not_Found, parentID));
329                     }
330                 }
331 
332                 bool isBrowserDefinition = true;
333                 if (firstBrowserDefinition is GatewayDefinition) {
334                     isBrowserDefinition = false;
335                 }
336 
337                 //GenerateMethodsToOverrideBrowsers
338                 //Gen: protected override void Xxx_ProcessChildBrowsers(bool ignoreApplicationBrowsers, MNameValueCollection headers, HttpBrowserCapabilities browserCaps) ;
339 
340                 string methodName = parentName + (isBrowserDefinition ? "ProcessBrowsers" : "ProcessGateways");
341                 CodeMemberMethod cmm = new CodeMemberMethod();
342                 cmm.Name = methodName;
343                 cmm.ReturnType = new CodeTypeReference(typeof(void));
344                 cmm.Attributes = MemberAttributes.Family | MemberAttributes.Override;
345 
346                 if (isBrowserDefinition) {
347                     cpde = new CodeParameterDeclarationExpression(typeof(bool), BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
348                     cmm.Parameters.Add(cpde);
349                 }
350                 cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), "headers");
351                 cmm.Parameters.Add(cpde);
352                 cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), browserCapsVariable);
353                 cmm.Parameters.Add(cpde);
354 
355                 factoryType.Members.Add(cmm);
356 
357                 ArrayList overrides = (ArrayList)_browserOverrides[key];
358                 CodeStatementCollection statements = cmm.Statements;
359 
360                 bool ignoreApplicationBrowsersVarRefGenerated = false;
361 
362                 foreach (string browserID in overrides) {
363                     bd = (BrowserDefinition)BrowserTree[browserID];
364                     if (bd is GatewayDefinition || bd.IsRefID) {
365                         GenerateSingleProcessCall(bd, cmm);
366                     }
367                     else {
368                         if (!ignoreApplicationBrowsersVarRefGenerated) {
369                             Debug.Assert(isBrowserDefinition);
370 
371                             // Gen: if (ignoreApplicationBrowsers) {
372                             //      }
373                             //      else {
374                             //          ...
375                             //      }
376                             CodeConditionStatement istatement = new CodeConditionStatement();
377 
378                             istatement.Condition = new CodeVariableReferenceExpression(BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
379 
380                             cmm.Statements.Add(istatement);
381                             statements = istatement.FalseStatements;
382 
383                             ignoreApplicationBrowsersVarRefGenerated = true;
384                         }
385                         statements = GenerateTrackedSingleProcessCall(statements, bd, cmm);
386                         if (_baseInstance == null) {
387                             // If we are targeting 4.0 or using the ASP.BrowserCapsFactory assembly generated by
388                             // aspnet_regbrowsers.exe, we can simply just instantiate the type.
389                             // If not, then we need to use the type BrowserCapabilitiesFactory35 that contains code
390                             // from the 2.0 version of BrowserCapabilitiesFactory. This is because "baseType" is the 4.0 type
391                             // that contains the new 4.0 definitions.
392                             // (Dev10 bug 795509)
393                             if (MultiTargetingUtil.IsTargetFramework40OrAbove ||
394                                 baseType.Assembly == BrowserCapabilitiesCompiler.AspBrowserCapsFactoryAssembly) {
395                                 _baseInstance = (BrowserCapabilitiesFactoryBase)Activator.CreateInstance(baseType);
396                             }
397                             else {
398                                 _baseInstance = new BrowserCapabilitiesFactory35();
399                             }
400                         }
401                         int parentDepth = (int)((Triplet)_baseInstance.InternalGetBrowserElements()[parentName]).Third;
402                         AddBrowserToCollectionRecursive(bd, parentDepth + 1);
403                     }
404                 }
405 
406             }
407 
408             foreach (DictionaryEntry entry in _defaultBrowserOverrides) {
409                 object key = entry.Key;
410 
411                 BrowserDefinition firstDefaultBrowserDefinition = (BrowserDefinition)DefaultTree[GetFirstItemFromKey(_defaultBrowserOverrides, key)];
412                 string parentName = firstDefaultBrowserDefinition.ParentName;
413 
414                 if (baseType.GetMethod("Default" + parentName + "ProcessBrowsers", flags) == null) {
415                     String parentID = firstDefaultBrowserDefinition.ParentID;
416                     if (firstDefaultBrowserDefinition != null) {
417                         throw new ConfigurationErrorsException(SR.GetString(SR.DefaultBrowser_parentID_Not_Found, parentID), firstDefaultBrowserDefinition.XmlNode);
418                     }
419                 }
420 
421                 string methodName = "Default" + parentName + "ProcessBrowsers";
422                 CodeMemberMethod cmm = new CodeMemberMethod();
423                 cmm.Name = methodName;
424                 cmm.ReturnType = new CodeTypeReference(typeof(void));
425                 cmm.Attributes = MemberAttributes.Family | MemberAttributes.Override;
426                 cpde = new CodeParameterDeclarationExpression(typeof(bool), BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
427                 cmm.Parameters.Add(cpde);
428                 cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), "headers");
429                 cmm.Parameters.Add(cpde);
430                 cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), browserCapsVariable);
431                 cmm.Parameters.Add(cpde);
432                 factoryType.Members.Add(cmm);
433 
434                 ArrayList overrides = (ArrayList)_defaultBrowserOverrides[key];
435 
436                 CodeConditionStatement istatement = new CodeConditionStatement();
437                 istatement.Condition = new CodeVariableReferenceExpression(BrowserCapabilitiesCodeGenerator.IgnoreApplicationBrowserVariableName);
438 
439                 cmm.Statements.Add(istatement);
440                 CodeStatementCollection statements = istatement.FalseStatements;
441 
442                 foreach(string browserID in overrides) {
443                     bd = (BrowserDefinition)DefaultTree[browserID];
444                     Debug.Assert(!(bd is GatewayDefinition));
445 
446                     if(bd.IsRefID) {
447                         GenerateSingleProcessCall(bd, cmm, "Default");
448                     }
449                     else {
450                         statements = GenerateTrackedSingleProcessCall(statements, bd, cmm, "Default");
451                     }
452                 }
453             }
454 
455             // Generate process method for the browser elements
456             foreach (DictionaryEntry entry in BrowserTree) {
457                 bd = entry.Value as BrowserDefinition;
458                 Debug.Assert(bd != null);
459                 GenerateProcessMethod(bd, factoryType);
460             }
461 
462             for (int i = 0; i < customTreeRoots.Count; i++) {
463                 foreach (DictionaryEntry entry in (BrowserTree)CustomTreeList[i]) {
464                     bd = entry.Value as BrowserDefinition;
465                     Debug.Assert(bd != null);
466                     GenerateProcessMethod(bd, factoryType);
467                 }
468             }
469 
470             // Generate process method for the default browser elements
471             foreach (DictionaryEntry entry in DefaultTree) {
472                 bd = entry.Value as BrowserDefinition;
473                 Debug.Assert(bd != null);
474                 GenerateProcessMethod(bd, factoryType, "Default");
475             }
476             GenerateOverrideMatchedHeaders(factoryType);
477             GenerateOverrideBrowserElements(factoryType);
478 
479             Assembly assembly = BrowserCapabilitiesCompiler.GetBrowserCapabilitiesFactoryBaseType().Assembly;
480             assemblyBuilder.AddAssemblyReference(assembly, ccu);
481             assemblyBuilder.AddCodeCompileUnit(_buildProvider, ccu);
482         }
483 
ProcessBrowserNode(XmlNode node, BrowserTree browserTree)484         internal override void ProcessBrowserNode(XmlNode node, BrowserTree browserTree) {
485             if (node.Name == "defaultBrowser") {
486                 throw new ConfigurationErrorsException(SR.GetString(SR.Browser_Not_Allowed_InAppLevel, node.Name), node);
487             }
488 
489             base.ProcessBrowserNode(node, browserTree);
490         }
491     }
492 
493     internal class ApplicationBrowserCapabilitiesBuildProvider : BuildProvider {
494 
495         private ApplicationBrowserCapabilitiesCodeGenerator _codeGenerator;
496 
ApplicationBrowserCapabilitiesBuildProvider()497         internal ApplicationBrowserCapabilitiesBuildProvider() {
498             _codeGenerator = new ApplicationBrowserCapabilitiesCodeGenerator(this);
499         }
500 
AddFile(string virtualPath)501         internal void AddFile(string virtualPath) {
502             String filePath = HostingEnvironment.MapPathInternal(virtualPath);
503             _codeGenerator.AddFile(filePath);
504         }
505 
GenerateCode(AssemblyBuilder assemblyBuilder)506         public override void GenerateCode(AssemblyBuilder assemblyBuilder) {
507             _codeGenerator.GenerateCode(assemblyBuilder);
508         }
509     }
510 }
511