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