Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.Web / Configuration / BrowserCapabilitiesCodeGenerator.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="BrowserCapabilitiesCodeGenerator.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.Configuration {
8     using System;
9     using System.CodeDom;
10     using System.CodeDom.Compiler;
11     using System.Configuration;
12     using System.Collections;
13     using System.Collections.Specialized;
14     using System.IO;
15 #if !FEATURE_PAL
16     using System.ServiceProcess;
17 #endif // !FEATURE_PAL
18     using System.Linq;
19     using System.Reflection;
20     using System.Security;
21     using System.Security.Permissions;
22     using System.Text;
23     using System.Text.RegularExpressions;
24     using System.Web;
25     using System.Web.Compilation;
26     using System.Web.Configuration;
27     using System.Web.Hosting;
28     using System.Web.UI;
29     using System.Web.Util;
30     using System.Xml;
31     using System.Xml.Schema;
32
33     using Microsoft.Build.Utilities;
34     using Microsoft.CSharp;
35     using System.Diagnostics.CodeAnalysis;
36
37     [PermissionSet(SecurityAction.LinkDemand, Unrestricted = true)]
38     [PermissionSet(SecurityAction.InheritanceDemand, Unrestricted = true)]
39     public class BrowserCapabilitiesCodeGenerator {
40         private static readonly string _browsersDirectory;
41         private static readonly string _publicKeyTokenFile;
42
43         private static object _staticLock = new object();
44
45         private BrowserTree _browserTree;
46         private BrowserTree _defaultTree;
47         private BrowserDefinitionCollection _browserDefinitionCollection;
48
49         internal const string browserCapsVariable = "browserCaps";
50         internal const string IgnoreApplicationBrowserVariableName = "ignoreApplicationBrowsers";
51         private const string _factoryTypeName = "BrowserCapabilitiesFactory";
52         private const string _headerDictionaryVarName = "_headerDictionary";
53         private const string _disableOptimizedCacheKeyMethodName = "DisableOptimizedCacheKey";
54         private const string _matchedHeadersMethodName = "PopulateMatchedHeaders";
55         private const string _browserElementsMethodName = "PopulateBrowserElements";
56         private const string _dictionaryRefName = "dictionary";
57         private const string _regexWorkerRefName = "regexWorker";
58         private const string _headersRefName = "headers";
59         private const string _resultVarName = "result";
60         private const string _processRegexMethod = "ProcessRegex";
61         private static readonly string _strongNameKeyFileName = browserCapsVariable + ".snk";
62         private static readonly string _publicKeyTokenFileName = browserCapsVariable + ".token";
63         private static bool _publicKeyTokenLoaded;
64         private static string _publicKeyToken;
65
66         private CodeVariableReferenceExpression _dictionaryRefExpr = new CodeVariableReferenceExpression(_dictionaryRefName);
67         private CodeVariableReferenceExpression _regexWorkerRefExpr = new CodeVariableReferenceExpression(_regexWorkerRefName);
68         private CodeVariableReferenceExpression _headersRefExpr = new CodeVariableReferenceExpression(_headersRefName);
69         private CodeVariableReferenceExpression _browserCapsRefExpr = new CodeVariableReferenceExpression(browserCapsVariable);
70
71         private ArrayList _browserFileList;
72         
73         private ArrayList _customBrowserFileLists;
74         private ArrayList _customTreeList;
75         private ArrayList _customTreeNames;
76         private ArrayList _customBrowserDefinitionCollections;
77
78         private CaseInsensitiveStringSet _headers;
79
80         static BrowserCapabilitiesCodeGenerator() {
81 #if !PLATFORM_UNIX // File system paths must account for UNIX
82             _browsersDirectory = HttpRuntime.ClrInstallDirectoryInternal + "\\config\\browsers";
83             _publicKeyTokenFile = _browsersDirectory + "\\" + _publicKeyTokenFileName;
84 #else // !PLATFORM_UNIX 
85             _browsersDirectory = HttpRuntime.ClrInstallDirectoryInternal + "/config/browsers";
86             _publicKeyTokenFile = _browsersDirectory + "/" + _publicKeyTokenFileName;
87
88 #endif // !PLATFORM_UNIX 
89         }
90
91         public BrowserCapabilitiesCodeGenerator() {
92             _headers = new CaseInsensitiveStringSet();
93         }
94
95         internal BrowserTree BrowserTree {
96             get {
97                 return _browserTree;
98             }
99         }
100
101         internal BrowserTree DefaultTree {
102             get {
103                 return _defaultTree;
104             }
105         }
106
107         internal ArrayList CustomTreeList {
108             get {
109                 return _customTreeList;
110             }
111         }
112
113         internal ArrayList CustomTreeNames {
114             get {
115                 return _customTreeNames;
116             }
117         }
118
119         internal static string BrowserCapAssemblyPublicKeyToken {
120             get {
121                 if (_publicKeyTokenLoaded) {
122                     return _publicKeyToken;
123                 }
124
125                 lock (_staticLock) {
126                     if (_publicKeyTokenLoaded) {
127                         return _publicKeyToken;
128                     }
129
130                     string publicKeyTokenFile;
131                     if (MultiTargetingUtil.IsTargetFramework40OrAbove) {
132                         publicKeyTokenFile = _publicKeyTokenFile;
133                     }
134                     else {
135                         // If we are targeting pre-4.0, we should be using version 2.0 of the assembly
136                         // ASP.BrowserCapsFactory, so we need to read the token file from the 2.0 path.
137                         // (Dev10 bug 795509)
138                         string subPath = @"config\browsers\" + _publicKeyTokenFileName;
139                         publicKeyTokenFile = ToolLocationHelper.GetPathToDotNetFrameworkFile(subPath, TargetDotNetFrameworkVersion.Version20);
140                     }
141                     _publicKeyToken = LoadPublicKeyTokenFromFile(publicKeyTokenFile);
142                     _publicKeyTokenLoaded = true;
143
144                     return _publicKeyToken;
145                 }
146             }
147         }
148
149         internal virtual bool GenerateOverrides { get { return true; } }
150
151         internal virtual string TypeName {
152             get {
153                 return _factoryTypeName;
154             }
155         }
156
157         internal void AddFile(string filePath) {
158             if (_browserFileList == null)
159                 _browserFileList = new ArrayList();
160
161             _browserFileList.Add(filePath);
162         }
163
164         internal void AddCustomFile(string filePath) {
165             if (_customBrowserFileLists == null) {
166                 _customBrowserFileLists = new ArrayList();
167             }
168             
169             _customBrowserFileLists.Add(filePath);
170         }
171
172         //parse the config info and create BrowserTree
173         //then generate code for, compile, and gac the object
174         [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
175         public virtual void Create() {
176             DirectoryInfo browserDirInfo = new DirectoryInfo(_browsersDirectory);
177             //get all the browser files and put them in the "tree"
178             FileInfo[] browserFiles = browserDirInfo.GetFiles("*.browser");
179             
180             if (browserFiles == null || browserFiles.Length == 0) {
181                 return;
182             }
183
184             foreach(FileInfo browserFile in browserFiles) {
185                 AddFile(browserFile.FullName);
186             }
187
188             // First parse the browser files.
189             ProcessBrowserFiles();
190
191             // Then parse custom browser files.
192             ProcessCustomBrowserFiles();
193
194             // Uninstall previously installed generated assembly.
195             Uninstall();
196
197             //generate the source code, compile it, and gac it
198             GenerateAssembly();
199
200             // Restart w3svc service
201             RestartW3SVCIfNecessary();
202         }
203
204         internal bool UninstallInternal() {
205             // Remove existing strong name public token file
206             if (File.Exists(_publicKeyTokenFile)) {
207                 File.Delete(_publicKeyTokenFile);
208             }
209
210             // Removing existing copy from GAC
211             GacUtil gacutil = new GacUtil();
212             bool assemblyRemoved = gacutil.GacUnInstall("ASP.BrowserCapsFactory, Version=" + ThisAssembly.Version + ", Culture=neutral");
213             if (!assemblyRemoved) {
214                 return false;
215             }
216
217             return true;
218         }
219
220         [SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
221         public bool Uninstall() {
222             // Restart w3svc service
223             RestartW3SVCIfNecessary();
224
225             if (!UninstallInternal()) {
226                 return false;
227             }
228
229             // Restart w3svc service again so applications get a fresh copy.
230             RestartW3SVCIfNecessary();
231
232             return true;
233         }
234
235         private void RestartW3SVCIfNecessary() {
236 #if !FEATURE_PAL
237             try {
238                 // Dev10 bug 734918
239                 // We should not fail when the w3svc service is not installed.
240                 ServiceController[] services = ServiceController.GetServices();
241                 ServiceController controller = services.SingleOrDefault(s => String.Equals(s.ServiceName, "W3SVC", StringComparison.OrdinalIgnoreCase)); 
242                 if (controller == null) {
243                     return;
244                 }
245
246                 ServiceControllerStatus status = controller.Status;
247
248                 // Stop the service if it's not currently stopped or pending.
249                 if (!status.Equals(ServiceControllerStatus.Stopped) &&
250                     !status.Equals(ServiceControllerStatus.StopPending) &&
251                     !status.Equals(ServiceControllerStatus.StartPending)) {
252                     controller.Stop();
253
254                     // Give it 5 minutes to stop
255                     controller.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 5, 0));
256                     controller.Start();
257
258                     // If the service was paused, pause it.
259                     if (status.Equals(ServiceControllerStatus.Paused) || status.Equals(ServiceControllerStatus.PausePending)) {
260                         controller.Pause();
261                     }
262                 }
263             }
264             catch (Exception ex) {
265                 throw new InvalidOperationException(SR.GetString(SR.Browser_W3SVC_Failure_Helper_Text, ex));
266             }
267 #endif // !FEATURE_PAL
268         }
269
270         internal void ProcessBrowserFiles() {
271             ProcessBrowserFiles(false, String.Empty);
272         }
273
274         private string NoPathFileName(string fullPath) {
275             int lastSlash = fullPath.LastIndexOf("\\", StringComparison.Ordinal);
276             if(lastSlash > -1) {
277                 return fullPath.Substring(lastSlash + 1);
278             }
279             return fullPath;
280         }
281
282         internal virtual void ProcessBrowserNode(XmlNode node, BrowserTree browserTree) {
283
284             BrowserDefinition browserInfo = null;
285
286             if (node.Name == "gateway") {
287                 browserInfo = new GatewayDefinition(node);
288             }
289             else if (node.Name == "browser") {
290                 browserInfo = new BrowserDefinition(node);
291             }
292             else {
293                 Debug.Assert(node.Name == "defaultBrowser");
294                 browserInfo = new BrowserDefinition(node, true);
295             }
296
297             BrowserDefinition oldNode = (BrowserDefinition)browserTree[browserInfo.Name];
298
299             if (oldNode != null) {
300                 if (browserInfo.IsRefID) {
301                     oldNode.MergeWithDefinition(browserInfo);
302                 }
303                 else {
304                     throw new ConfigurationErrorsException(SR.GetString(SR.Duplicate_browser_id, browserInfo.ID), node);
305                 }
306             }
307             else {
308                 browserTree[browserInfo.Name] = browserInfo;
309             }
310         }
311
312         private void NormalizeAndValidateTree(BrowserTree browserTree, bool isDefaultBrowser) {
313             NormalizeAndValidateTree(browserTree, isDefaultBrowser, false);
314         }
315
316         private void NormalizeAndValidateTree(BrowserTree browserTree, bool isDefaultBrowser, bool isCustomBrowser) {
317             //normalize the tree
318             foreach (DictionaryEntry entry in browserTree) {
319                 BrowserDefinition bd = (BrowserDefinition)entry.Value;
320                 string parentName = bd.ParentName;
321                 BrowserDefinition parentBrowser = null;
322
323                 if (IsRootNode(bd.Name)) {
324                     continue;
325                 }
326
327                 if (parentName.Length > 0) {
328                     parentBrowser = (BrowserDefinition)browserTree[parentName];
329                 }
330
331                 if (parentBrowser != null) {
332                     if (bd.IsRefID) {
333                         if (bd is GatewayDefinition) {
334                             parentBrowser.RefGateways.Add(bd);
335                         }
336                         else {
337                             parentBrowser.RefBrowsers.Add(bd);
338                         }
339                     }
340                     else if (bd is GatewayDefinition) {
341                         parentBrowser.Gateways.Add(bd);
342                     }
343                     else {
344                         parentBrowser.Browsers.Add(bd);
345                     }
346                 }
347                 else {
348                     if (isCustomBrowser) {
349                         throw new ConfigurationErrorsException(SR.GetString(SR.Browser_parentID_Not_Found, bd.ParentID), bd.XmlNode);
350                     }
351                     else {
352                         HandleUnRecognizedParentElement(bd, isDefaultBrowser);
353                     }
354                 }
355             }
356
357             //validate the tree
358             //loop check
359             foreach (DictionaryEntry entry in browserTree) {
360                 BrowserDefinition bd = (BrowserDefinition)entry.Value;
361                 Hashtable loopCheck = new Hashtable();
362                 BrowserDefinition currentBrowser = bd;
363                 string currentId = currentBrowser.Name;
364                 while (!IsRootNode(currentId)) {
365                     if (loopCheck[currentId] != null) {
366                         throw new ConfigurationErrorsException(SR.GetString(SR.Browser_Circular_Reference, currentId), currentBrowser.XmlNode);
367                     }
368                     loopCheck[currentId] = currentId;
369                     currentBrowser = (BrowserDefinition)browserTree[currentBrowser.ParentName];
370                     //in app-level, parent can exist in machine level
371                     if (currentBrowser == null) {
372                         break;
373                     }
374
375                     currentId = currentBrowser.Name;
376                 }
377             }
378         }
379
380         private void SetCustomTreeRoots(BrowserTree browserTree, int index) {
381             foreach (DictionaryEntry entry in browserTree) {
382                 BrowserDefinition bd = (BrowserDefinition)entry.Value;
383                 if (bd.ParentName == null) {
384                     _customTreeNames[index] = bd.Name;
385                     break;
386                 }
387             }
388         }      
389
390         // Now that we support adding custom browser hierarchies, root nodes other than Default are permitted.
391         private bool IsRootNode(string nodeName) {
392             if (String.Compare(nodeName, "Default", StringComparison.OrdinalIgnoreCase) == 0)
393                 return true;
394
395             foreach (string treeRootName in _customTreeNames) {
396                 if (String.Compare(nodeName, treeRootName, StringComparison.OrdinalIgnoreCase) == 0) {
397                     return true;
398                 }
399             }
400
401             return false;
402         }
403
404         [SuppressMessage("Microsoft.Security.Xml", "CA3056:UseXmlReaderForLoad", Justification = "Developer-controlled .xml files in application directory are implicitly trusted by ASP.Net.")]
405         protected void ProcessBrowserFiles(bool useVirtualPath, string virtualDir) {
406             _browserTree = new BrowserTree();
407             _defaultTree = new BrowserTree();
408             _customTreeNames = new ArrayList();
409
410             if (_browserFileList == null) {
411                 _browserFileList = new ArrayList();
412             }
413
414             _browserFileList.Sort();
415 //#if OPTIMIZE_FOR_DESKTOP_BROWSER
416             string mozillaFile = null;
417             string ieFile = null;
418             string operaFile = null;
419
420             // DevDivBugs 180962
421             // IE, Mozilla and Opera are first-class browsers. Their User-Agent profiles need to be compared to the UA profile
422             // of the HTTP request before other browsers. We put them to the head of the list so that the generated browser capabilities 
423             // code will try to match them before other browsers.
424             foreach (String filePath in _browserFileList) {
425                 if (filePath.EndsWith("ie.browser", StringComparison.OrdinalIgnoreCase)) {
426                     ieFile = filePath;
427                 }
428                 else if (filePath.EndsWith("mozilla.browser", StringComparison.OrdinalIgnoreCase)) {
429                     mozillaFile = filePath;
430                 }
431                 else if (filePath.EndsWith("opera.browser", StringComparison.OrdinalIgnoreCase)) {
432                     operaFile = filePath;
433                     break;
434                 }
435             }
436
437             if (ieFile != null) {
438                 _browserFileList.Remove(ieFile);
439                 _browserFileList.Insert(0, ieFile);
440             }
441
442             if (mozillaFile != null) {
443                 _browserFileList.Remove(mozillaFile);
444                 _browserFileList.Insert(1, mozillaFile);
445             }
446
447             if (operaFile != null) {
448                 _browserFileList.Remove(operaFile);
449                 _browserFileList.Insert(2, operaFile);
450             }
451 //#endif
452             foreach (string fileName in _browserFileList) {
453                 XmlDocument doc = new ConfigXmlDocument();
454                 try {
455                     doc.Load(fileName);
456
457                     XmlNode rootNode = doc.DocumentElement;
458                     if(rootNode.Name != "browsers") {
459                         if(useVirtualPath) {
460                             throw new HttpParseException(SR.GetString(SR.Invalid_browser_root), null /*innerException*/, virtualDir + "/" + NoPathFileName(fileName), null /*sourceCode*/, 1);
461                         }
462                         else {
463                             throw new HttpParseException(SR.GetString(SR.Invalid_browser_root), null /*innerException*/, fileName, null /*sourceCode*/, 1);
464                         }
465                     }
466
467                     foreach (XmlNode node in rootNode.ChildNodes) {
468                         if (node.NodeType != XmlNodeType.Element)
469                             continue;
470                         if (node.Name == "browser" || node.Name == "gateway") { 
471                             ProcessBrowserNode(node, _browserTree);
472                         }
473                         else if (node.Name == "defaultBrowser") {
474                             ProcessBrowserNode(node, _defaultTree);
475                         }
476                         else {
477                             HandlerBase.ThrowUnrecognizedElement(node);
478                         }
479                     }
480                 }
481                 catch (XmlException e) {
482                     if(useVirtualPath) {
483                         throw new HttpParseException(e.Message, null /*innerException*/, virtualDir + "/" + NoPathFileName(fileName), null /*sourceCode*/, e.LineNumber);
484                     }
485                     else {
486                         throw new HttpParseException(e.Message, null /*innerException*/, fileName, null /*sourceCode*/, e.LineNumber);
487                     }
488                 }
489                 catch (XmlSchemaException e) {
490                     if(useVirtualPath) {
491                         throw new HttpParseException(e.Message, null /*innerException*/, virtualDir + "/" + NoPathFileName(fileName), null /*sourceCode*/, e.LineNumber);
492                     }
493                     else {
494                         throw new HttpParseException(e.Message, null /*innerException*/, fileName, null /*sourceCode*/, e.LineNumber);
495                     }
496                 }
497             }
498             NormalizeAndValidateTree(_browserTree, false);
499             NormalizeAndValidateTree(_defaultTree, true);
500
501             BrowserDefinition defaultBrowser = (BrowserDefinition)_browserTree["Default"];
502
503             if (defaultBrowser != null) {
504                 AddBrowserToCollectionRecursive(defaultBrowser, 0);
505             }
506         }
507
508         internal void ProcessCustomBrowserFiles() {
509             ProcessCustomBrowserFiles(false, String.Empty);
510         }
511
512         [SuppressMessage("Microsoft.Security.Xml", "CA3056:UseXmlReaderForLoad", Justification = "Developer-controlled .xml files in application directory are implicitly trusted by ASP.Net.")]
513         internal void ProcessCustomBrowserFiles(bool useVirtualPath, string virtualDir) {
514             //get all custom browser files and put them in the "tree"
515             DirectoryInfo browserDirInfo = null;
516             DirectoryInfo[] browserSubDirectories = null;
517             DirectoryInfo[] allBrowserSubDirectories = null;
518             ArrayList customBrowserFileNames;
519             _customTreeList = new ArrayList();
520             _customBrowserFileLists = new ArrayList();
521             _customBrowserDefinitionCollections = new ArrayList();
522
523             /* Machine Level Custom Browsers */
524             if (useVirtualPath == false) {
525                 browserDirInfo = new DirectoryInfo(_browsersDirectory);
526             }
527             /* Application Level Custom Browsers */
528             else {
529                 browserDirInfo = new DirectoryInfo(HostingEnvironment.MapPathInternal(virtualDir));
530             }
531
532             allBrowserSubDirectories = browserDirInfo.GetDirectories();
533             
534             int j = 0;
535             int length = allBrowserSubDirectories.Length;
536             browserSubDirectories = new DirectoryInfo[length];
537             for (int i = 0; i < length; i++) {
538                 if ((allBrowserSubDirectories[i].Attributes & FileAttributes.Hidden) != FileAttributes.Hidden) {
539                     browserSubDirectories[j] = allBrowserSubDirectories[i];
540                     j++;
541                 }
542             }
543             Array.Resize(ref browserSubDirectories, j);
544
545             for (int i = 0; i < browserSubDirectories.Length; i++) {
546                 /* Recursively Into Subdirectories */
547                 FileInfo[] browserFiles = GetFilesNotHidden(browserSubDirectories[i], browserDirInfo);
548
549                 if (browserFiles == null || browserFiles.Length == 0) {
550                     continue;
551                 }
552                 BrowserTree customTree = new BrowserTree();
553                 _customTreeList.Add(customTree);
554                 _customTreeNames.Add(browserSubDirectories[i].Name);
555                 customBrowserFileNames = new ArrayList();
556
557                 foreach (FileInfo browserFile in browserFiles) {
558                     customBrowserFileNames.Add(browserFile.FullName);
559                 }
560                 _customBrowserFileLists.Add(customBrowserFileNames);
561             }
562             for (int i = 0; i < _customBrowserFileLists.Count; i++) {
563                 ArrayList fileNames = (ArrayList)_customBrowserFileLists[i];
564                 foreach (string fileName in fileNames) {
565                     XmlDocument doc = new ConfigXmlDocument();
566                     try {
567                         doc.Load(fileName);
568  
569                         XmlNode rootNode = doc.DocumentElement;
570                         if (rootNode.Name != "browsers") {
571                             if (useVirtualPath) {
572                                 throw new HttpParseException(SR.GetString(SR.Invalid_browser_root), null /*innerException*/, virtualDir + "/" + NoPathFileName(fileName), null /*sourceCode*/, 1);
573                             }
574                             else {
575                                 throw new HttpParseException(SR.GetString(SR.Invalid_browser_root), null /*innerException*/, fileName, null /*sourceCode*/, 1);
576                             }
577                         }
578                         foreach (XmlNode node in rootNode.ChildNodes) {
579                             if (node.NodeType != XmlNodeType.Element) {
580                                 continue;
581                             }
582                             if (node.Name == "browser" || node.Name == "gateway") {
583                                 ProcessBrowserNode(node, (BrowserTree)_customTreeList[i]);
584                             }
585                             else {
586                                 HandlerBase.ThrowUnrecognizedElement(node);
587                             }
588                         }
589                     }
590                     catch (XmlException e) {
591                         if (useVirtualPath) {
592                             throw new HttpParseException(e.Message, null /*innerException*/, virtualDir + "/" + NoPathFileName(fileName), null /*sourceCode*/, e.LineNumber);
593                         }
594                         else {
595                             throw new HttpParseException(e.Message, null /*innerException*/, fileName, null /*sourceCode*/, e.LineNumber);
596                         }
597                     }
598                     catch (XmlSchemaException e) {
599                         if (useVirtualPath) {
600                             throw new HttpParseException(e.Message, null /*innerException*/, virtualDir + "/" + NoPathFileName(fileName), null /*sourceCode*/, e.LineNumber);
601                         }
602                         else {
603                             throw new HttpParseException(e.Message, null /*innerException*/, fileName, null /*sourceCode*/, e.LineNumber);
604                         }
605                     }
606                 }
607                 SetCustomTreeRoots((BrowserTree)_customTreeList[i], i);
608                 NormalizeAndValidateTree((BrowserTree)_customTreeList[i], false, true);
609                 _customBrowserDefinitionCollections.Add(new BrowserDefinitionCollection());
610                 AddCustomBrowserToCollectionRecursive((BrowserDefinition)(((BrowserTree)_customTreeList[i])[_customTreeNames[i]]), 0, i);
611             }
612         }
613
614         internal void AddCustomBrowserToCollectionRecursive(BrowserDefinition bd, int depth, int index) {
615             if(_customBrowserDefinitionCollections[index] == null) {
616                 _customBrowserDefinitionCollections[index] = new BrowserDefinitionCollection();
617             }
618             bd.Depth = depth;
619             bd.IsDeviceNode = true;
620             ((BrowserDefinitionCollection)_customBrowserDefinitionCollections[index]).Add(bd);
621
622             foreach (BrowserDefinition childBrowser in bd.Browsers) {
623                 AddCustomBrowserToCollectionRecursive(childBrowser, depth + 1, index);
624             }
625         }
626
627         internal void AddBrowserToCollectionRecursive(BrowserDefinition bd, int depth) {
628             if (_browserDefinitionCollection == null) {
629                 _browserDefinitionCollection = new BrowserDefinitionCollection();
630             }
631
632             bd.Depth = depth;
633             bd.IsDeviceNode = true;
634             _browserDefinitionCollection.Add(bd);
635
636             foreach(BrowserDefinition childBrowser in bd.Browsers) {
637                 AddBrowserToCollectionRecursive(childBrowser, depth + 1);
638             }
639         }
640
641         internal virtual void HandleUnRecognizedParentElement(BrowserDefinition bd, bool isDefault) {
642             throw new ConfigurationErrorsException(SR.GetString(SR.Browser_parentID_Not_Found, bd.ParentID), bd.XmlNode);
643         }
644         
645         private static FileInfo[] GetFilesNotHidden(DirectoryInfo rootDirectory, DirectoryInfo browserDirInfo) {
646             ArrayList fileList = new ArrayList();
647             FileInfo[] files;
648             DirectoryInfo[] subDirectories = rootDirectory.GetDirectories("*", SearchOption.AllDirectories);
649             
650             files = rootDirectory.GetFiles("*.browser", SearchOption.TopDirectoryOnly);
651             fileList.AddRange(files);
652             for (int i = 0; i < subDirectories.Length; i++) {
653                 if ((HasHiddenParent(subDirectories[i], browserDirInfo) == false)) {
654                     files = subDirectories[i].GetFiles("*.browser", SearchOption.TopDirectoryOnly);
655                     fileList.AddRange(files);
656                 }
657             }
658             return ((FileInfo [])fileList.ToArray(typeof(FileInfo)));
659         } 
660         
661         private static bool HasHiddenParent(DirectoryInfo directory, DirectoryInfo browserDirInfo) {
662             while(!String.Equals(directory.Parent.Name, browserDirInfo.Name)) {
663                 if ((directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) {
664                     return true;
665                 }
666                 directory = directory.Parent;
667             }
668             return false;
669         }
670
671         //generate the code from the parsed BrowserDefinitionTree
672         //compile it, and install it in the gac
673         private void GenerateAssembly() {
674             Debug.Assert(_browserTree != null);
675             BrowserDefinition root = (BrowserDefinition)_browserTree["Default"];
676             BrowserDefinition defaultRoot = (BrowserDefinition)_defaultTree["Default"];
677             ArrayList customTreeRoots = new ArrayList();
678             for (int i = 0; i < _customTreeNames.Count; i++) {
679                 customTreeRoots.Add((BrowserDefinition)(((BrowserTree)_customTreeList[i])[_customTreeNames[i]]));
680             }
681
682             //create a CodeCompileUnit
683             //add a CodeNamespace object to the CodeCompileUnit
684             //add a CodeTypeDeclaration to the CodeNamespace
685             //add all the members of the type/class to the CodeTypeDeclaration
686             //use a CodeGenerator to generate code from the CodeCompileUnit
687             //a CodeDomProvider can provide a CodeGenerator
688
689             //translate the BrowserDefinition tree to code
690             CSharpCodeProvider cscp = new CSharpCodeProvider();
691
692             // namespace System.Web.BrowserCapsFactory
693             CodeCompileUnit ccu = new CodeCompileUnit();
694
695             //add strong-name key pair for
696             CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
697                 "System.Reflection.AssemblyKeyFile",
698                 new CodeAttributeArgument[] {
699                     new CodeAttributeArgument(new CodePrimitiveExpression(_strongNameKeyFileName))});
700
701             CodeAttributeDeclaration aptca = new CodeAttributeDeclaration(
702                 "System.Security.AllowPartiallyTrustedCallers");
703             ccu.AssemblyCustomAttributes.Add(aptca);
704
705             ccu.AssemblyCustomAttributes.Add(declaration);
706             //add version number for it so it can distinguished in future versions
707             declaration = new CodeAttributeDeclaration(
708                 "System.Reflection.AssemblyVersion",
709                 new CodeAttributeArgument[] {
710                     new CodeAttributeArgument(new CodePrimitiveExpression(ThisAssembly.Version))});
711             ccu.AssemblyCustomAttributes.Add(declaration);
712
713             CodeNamespace cnamespace = new CodeNamespace("ASP");
714             //GEN: using System;
715             cnamespace.Imports.Add(new CodeNamespaceImport("System"));
716             //GEN: using System.Web;
717             cnamespace.Imports.Add(new CodeNamespaceImport("System.Web"));
718             //GEN: using System.Web.Configuration;
719             cnamespace.Imports.Add(new CodeNamespaceImport("System.Web.Configuration"));
720             //GEN: using System.Reflection;
721             cnamespace.Imports.Add(new CodeNamespaceImport("System.Reflection"));
722             //GEN: class BrowserCapabilitiesFactory
723             ccu.Namespaces.Add(cnamespace);
724
725             CodeTypeDeclaration factoryType = new CodeTypeDeclaration("BrowserCapabilitiesFactory");
726             factoryType.Attributes = MemberAttributes.Private;
727             factoryType.IsClass = true;
728             factoryType.Name = TypeName;
729             factoryType.BaseTypes.Add(new CodeTypeReference("System.Web.Configuration.BrowserCapabilitiesFactoryBase"));
730             cnamespace.Types.Add(factoryType);
731
732             //GEN: protected override object ConfigureBrowserCapabilities(NameValueCollection headers, HttpBrowserCapabilities browserCaps)
733             CodeMemberMethod method = new CodeMemberMethod();
734             method.Attributes = MemberAttributes.Override | MemberAttributes.Public;
735             method.ReturnType = new CodeTypeReference(typeof(void));
736             method.Name = "ConfigureBrowserCapabilities";
737
738             CodeParameterDeclarationExpression cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), _headersRefName);
739             method.Parameters.Add(cpde);
740             cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), browserCapsVariable);
741             method.Parameters.Add(cpde);
742             factoryType.Members.Add(method);
743
744             GenerateSingleProcessCall(root, method);
745             
746             for (int i = 0; i < customTreeRoots.Count; i++) {
747                 GenerateSingleProcessCall((BrowserDefinition)customTreeRoots[i], method);
748             }
749
750             //GEN: if(this.IsBrowserUnknown(browserCaps) == false) return;            
751             CodeConditionStatement istatement = new CodeConditionStatement();
752
753             CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "IsBrowserUnknown");
754             cmie.Parameters.Add(_browserCapsRefExpr);
755             istatement.Condition = new CodeBinaryOperatorExpression(cmie, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(false));
756             istatement.TrueStatements.Add(new CodeMethodReturnStatement());
757             method.Statements.Add(istatement);
758
759             if(defaultRoot != null) {
760                 GenerateSingleProcessCall(defaultRoot, method, "Default");
761             }
762
763             for (int i = 0; i < customTreeRoots.Count; i++) {
764                 foreach (DictionaryEntry entry in (BrowserTree)_customTreeList[i]) {
765                     BrowserDefinition bd = entry.Value as BrowserDefinition;
766                     Debug.Assert(bd != null);
767                     GenerateProcessMethod(bd, factoryType);
768                 }
769             }
770
771             //GenerateCallsToProcessMethods(root, method);
772             foreach (DictionaryEntry entry in _browserTree) {
773                 BrowserDefinition bd = entry.Value as BrowserDefinition;
774                 Debug.Assert(bd != null);
775                 GenerateProcessMethod(bd, factoryType);
776             }
777
778             foreach (DictionaryEntry entry in _defaultTree) {
779                 BrowserDefinition bd = entry.Value as BrowserDefinition;
780                 Debug.Assert(bd != null);
781                 GenerateProcessMethod(bd, factoryType, "Default");
782             }
783
784             GenerateOverrideMatchedHeaders(factoryType);
785             GenerateOverrideBrowserElements(factoryType);
786
787             //TODO: don't actually generate the code, just compile it in memory
788             TextWriter twriter = new StreamWriter(new FileStream(_browsersDirectory + "\\BrowserCapsFactory.cs", FileMode.Create));
789             try {
790                 cscp.GenerateCodeFromCompileUnit(ccu, twriter, null);
791             }
792             finally {
793                 if(twriter != null)
794                     twriter.Close();
795             }
796
797             CompilationSection compConfig = MTConfigUtil.GetCompilationAppConfig();
798
799             bool debug = compConfig.Debug;
800
801 #if !PLATFORM_UNIX // File system paths must account for UNIX
802             string strongNameFile = _browsersDirectory + "\\" + _strongNameKeyFileName;
803 #else // !PLATFORM_UNIX
804             string strongNameFile = _browsersDirectory + "/" + _strongNameKeyFileName;
805 #endif // !PLATFORM_UNIX
806
807             // Generate strong name file
808             StrongNameUtility.GenerateStrongNameFile(strongNameFile);
809
810             //TODO: do not use interim file:  CompileAssemblyFromDom instead
811             string[] referencedAssemblies = new string[2] { "System.dll", "System.Web.dll" };
812             CompilerParameters compilerParameters = new CompilerParameters(referencedAssemblies, "ASP.BrowserCapsFactory", debug /* includeDebugInformation */ );
813             compilerParameters.GenerateInMemory = false;
814             compilerParameters.OutputAssembly = _browsersDirectory + "\\ASP.BrowserCapsFactory.dll";
815             CompilerResults results = null;
816
817             try {
818                 results = cscp.CompileAssemblyFromFile(compilerParameters, _browsersDirectory + "\\BrowserCapsFactory.cs");
819             }
820             finally {
821                 if (File.Exists(strongNameFile)) {
822                     File.Delete(strongNameFile);
823                 }
824             }
825
826             if (results.NativeCompilerReturnValue != 0 || results.Errors.HasErrors) {
827                 foreach (CompilerError error in results.Errors) {
828                     if (!error.IsWarning) {
829                         throw new HttpCompileException(error.ErrorText);
830                     }
831                 }
832
833                 throw new HttpCompileException(SR.GetString(SR.Browser_compile_error));
834             }
835
836             Assembly resultAssembly = results.CompiledAssembly;
837
838             GacUtil gacutil = new GacUtil();
839             gacutil.GacInstall(resultAssembly.Location);
840
841             SavePublicKeyTokenFile(_publicKeyTokenFile, resultAssembly.GetName().GetPublicKeyToken());
842         }
843
844         private void SavePublicKeyTokenFile(string filename, byte[] publicKeyToken) {
845             using (FileStream pktStream = new FileStream(filename, FileMode.Create, FileAccess.Write)) {
846                 using (StreamWriter pktWriter = new StreamWriter(pktStream)) {
847                     foreach (byte b in publicKeyToken) {
848                         pktWriter.Write("{0:X2}", b);
849                     }
850                 }
851             }
852         }
853
854         private static string LoadPublicKeyTokenFromFile(string filename) {
855             IStackWalk fileReadAccess = InternalSecurityPermissions.FileReadAccess(filename);
856             Debug.Assert(fileReadAccess != null);
857             fileReadAccess.Assert();
858             if (!File.Exists(filename)) {
859                 return null;
860             }
861
862             try {
863                 using (FileStream pktStream = new FileStream(filename, FileMode.Open, FileAccess.Read)) {
864                     using (StreamReader pktReader = new StreamReader(pktStream)) {
865                         return pktReader.ReadLine();
866                     }
867                 }
868             }
869             catch (IOException) {
870                 if (HttpRuntime.HasFilePermission(filename)) {
871                     throw;
872                 }
873
874                 // Don't throw exception if we don't have permission to the file.
875                 return null;
876             }
877             finally {
878                 CodeAccessPermission.RevertAssert();
879             }
880         }
881
882         internal void GenerateOverrideBrowserElements(CodeTypeDeclaration typeDeclaration) {
883
884             // Don't generate the property if there's nothing to override.
885             if (_browserDefinitionCollection == null) {
886                 return;
887             }
888
889             // GEN:
890             // protected override void PopulateBrowserElements(IDictionary dictionary) {
891             //     dictionary["Default"] = new Triplet(null, "default description", 0 /*depth_of_node */);
892             //     dictionary["Up"] = new Triplet("Default", "up Description", 1 /*depth_of_node */);
893             // }
894             CodeMemberMethod method = new CodeMemberMethod();
895             method.Name = _browserElementsMethodName;
896             method.Attributes = MemberAttributes.Override | MemberAttributes.Family;
897             method.ReturnType = new CodeTypeReference(typeof(void));
898             CodeParameterDeclarationExpression parameter =
899                 new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(IDictionary)), _dictionaryRefName);
900
901             method.Parameters.Add(parameter);
902             typeDeclaration.Members.Add(method);
903
904             CodeMethodReferenceExpression baseMethod = new CodeMethodReferenceExpression(new CodeBaseReferenceExpression(), _browserElementsMethodName);
905             CodeMethodInvokeExpression baseInvoke = new CodeMethodInvokeExpression(baseMethod, new CodeExpression[] { _dictionaryRefExpr });
906             method.Statements.Add(baseInvoke);
907
908             foreach(BrowserDefinition bd in _browserDefinitionCollection) {
909                 if (!bd.IsDeviceNode)
910                     continue;
911
912                 Debug.Assert(!(bd is GatewayDefinition));
913
914                 CodeAssignStatement cas = new CodeAssignStatement();
915                 cas.Left = new CodeIndexerExpression(_dictionaryRefExpr, new CodeExpression[] {
916                                                                              new CodePrimitiveExpression(bd.ID)
917                                                                           });
918                 cas.Right = new CodeObjectCreateExpression(typeof(Triplet), 
919                     new CodeExpression[] {
920                         new CodePrimitiveExpression(bd.ParentName),
921                         new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeof(String)), "Empty"),
922                         new CodePrimitiveExpression(bd.Depth)});
923
924                 method.Statements.Add(cas);                
925             }
926
927             for (int i = 0; i < _customTreeNames.Count; i++) {
928                 foreach (BrowserDefinition bd in (BrowserDefinitionCollection)_customBrowserDefinitionCollections[i]) {
929                     if (!bd.IsDeviceNode)
930                         continue;
931
932                     Debug.Assert(!(bd is GatewayDefinition));
933
934                     CodeAssignStatement cas = new CodeAssignStatement();
935                     cas.Left = new CodeIndexerExpression(_dictionaryRefExpr, new CodeExpression[] {
936                                                                              new CodePrimitiveExpression(bd.ID)
937                                                                            });
938                     cas.Right = new CodeObjectCreateExpression(typeof(Triplet),
939                         new CodeExpression[] {
940                         new CodePrimitiveExpression(bd.ParentName),
941                         new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeof(String)), "Empty"),
942                         new CodePrimitiveExpression(bd.Depth)});
943
944                     method.Statements.Add(cas);
945                 }
946             }
947         }
948
949         internal void GenerateOverrideMatchedHeaders(CodeTypeDeclaration typeDeclaration) {
950             // GEN:
951             // protected override void PopulateMatchedHeaders(IDictionary dictionary) {
952             //     base.PopulateMatchedHeaders(dictionary);
953             //
954             //     dictionary["header0"] = null;
955             //     dictionary["header1"] = null;
956             // }
957             CodeMemberMethod method = new CodeMemberMethod();
958             method.Name = _matchedHeadersMethodName;
959             method.Attributes = MemberAttributes.Override | MemberAttributes.Family;
960             method.ReturnType = new CodeTypeReference(typeof(void));
961             CodeParameterDeclarationExpression parameter =
962                 new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(IDictionary)), _dictionaryRefName);
963
964             method.Parameters.Add(parameter);
965             typeDeclaration.Members.Add(method);
966
967             CodeMethodReferenceExpression baseMethod = new CodeMethodReferenceExpression(new CodeBaseReferenceExpression(), _matchedHeadersMethodName);
968             CodeMethodInvokeExpression baseInvoke = new CodeMethodInvokeExpression(baseMethod, new CodeExpression[] { _dictionaryRefExpr });
969             method.Statements.Add(baseInvoke);
970
971             foreach(String header in _headers) {
972                 CodeAssignStatement cas = new CodeAssignStatement();
973                 cas.Left = new CodeIndexerExpression(_dictionaryRefExpr, new CodeExpression[] {
974                                                                              new CodePrimitiveExpression(header)
975                                                                           });
976                 cas.Right = new CodePrimitiveExpression(null);
977
978                 method.Statements.Add(cas);
979             }
980         }
981
982         internal void GenerateProcessMethod(BrowserDefinition bd, CodeTypeDeclaration ctd) {
983             GenerateProcessMethod(bd, ctd, String.Empty);
984         }
985
986         //generate the xxxProcess method for an individual BrowserDefinition
987         internal void GenerateProcessMethod(BrowserDefinition bd, CodeTypeDeclaration ctd, string prefix) {
988             //GEN: internal bool XxxProcess(NameValueCollection headers, HttpBrowserCapabilities browserCaps)
989             CodeMemberMethod cmm = new CodeMemberMethod();
990             cmm.Name = prefix + bd.Name + "Process";
991             cmm.ReturnType = new CodeTypeReference(typeof(bool));
992             cmm.Attributes = MemberAttributes.Private;
993             CodeParameterDeclarationExpression cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), _headersRefName);
994             cmm.Parameters.Add(cpde);
995             cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), browserCapsVariable);
996             cmm.Parameters.Add(cpde);
997
998             bool regexWorkerGenerated = false;
999
1000             GenerateIdentificationCode(bd, cmm, ref regexWorkerGenerated);
1001             GenerateCapturesCode(bd, cmm, ref regexWorkerGenerated);
1002             GenerateSetCapabilitiesCode(bd, cmm, ref regexWorkerGenerated);
1003             GenerateSetAdaptersCode(bd, cmm);
1004
1005             // Only add the browser node to the browser collection if it represents a device.
1006             if (bd.IsDeviceNode) {
1007                 Debug.Assert(!(bd is GatewayDefinition));
1008
1009                 //GEN: browserCaps.AddBrowser("xxx");
1010                 CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(browserCapsVariable), "AddBrowser");
1011                 cmie.Parameters.Add(new CodePrimitiveExpression(bd.ID));
1012                 cmm.Statements.Add(cmie);
1013             }
1014
1015             // Generate ref gateway elements
1016             foreach (BrowserDefinition b in bd.RefGateways) {
1017                 AddComment("ref gateways, parent=" + bd.ID, cmm);
1018                 GenerateSingleProcessCall(b, cmm);
1019             }
1020
1021             if ((GenerateOverrides) && (prefix.Length == 0)) {
1022                 //Gen: protected virtual void XxxProcessGateways(NameValueCollection headers, HttpBrowserCapabilities browserCaps) ;
1023                 string methodName = prefix + bd.Name + "ProcessGateways";
1024                 GenerateChildProcessMethod(methodName, ctd, false);
1025
1026                 //Gen: XxxProcessGateways(headers,  browserCaps) ;
1027                 GenerateChildProcessInvokeExpression(methodName, cmm, false);
1028             }
1029
1030             foreach(BrowserDefinition b in bd.Gateways) {
1031                 AddComment("gateway, parent=" + bd.ID, cmm);
1032                 GenerateSingleProcessCall(b, cmm);
1033             }
1034
1035             if (GenerateOverrides) {
1036                 //GEN: bool ignoreApplicationBrowsers = true | false; //bd.Browsers.Count != 0
1037                 CodeVariableDeclarationStatement cvds = new CodeVariableDeclarationStatement(typeof(bool),
1038                     IgnoreApplicationBrowserVariableName, new CodePrimitiveExpression(bd.Browsers.Count != 0));
1039                 cmm.Statements.Add(cvds);
1040             }
1041
1042             if (bd.Browsers.Count > 0) {
1043                 CodeStatementCollection statements = cmm.Statements;
1044                 AddComment("browser, parent=" + bd.ID, cmm);
1045                 foreach (BrowserDefinition b in bd.Browsers) {
1046                     statements = GenerateTrackedSingleProcessCall(statements, b, cmm, prefix);
1047                 }
1048
1049                 if (GenerateOverrides) {
1050                     //GEN: ignoreApplicationBrowsers = false;
1051                     CodeAssignStatement codeAssignStmt = new CodeAssignStatement();
1052                     codeAssignStmt.Left = new CodeVariableReferenceExpression(IgnoreApplicationBrowserVariableName);
1053                     codeAssignStmt.Right = new CodePrimitiveExpression(false);
1054                     statements.Add(codeAssignStmt);
1055                 }
1056             }
1057
1058             // Generate ref browser 
1059             foreach (BrowserDefinition b in bd.RefBrowsers) {
1060                 AddComment("ref browsers, parent=" + bd.ID, cmm);
1061                 if (b.IsDefaultBrowser) {
1062                     GenerateSingleProcessCall(b, cmm, "Default");
1063                 }
1064                 else {
1065                     GenerateSingleProcessCall(b, cmm);
1066                 }
1067             }
1068
1069             if (GenerateOverrides) {
1070                 //Gen: protected virtual void XxxProcessBrowsers(bool ignoreApplicationBrowsers, NameValueCollection headers, HttpBrowserCapabilities browserCaps) ;
1071                 string methodName = prefix + bd.Name + "ProcessBrowsers";
1072                 GenerateChildProcessMethod(methodName, ctd, true);
1073
1074                 //Gen: XxxProcessBrowsers(ignoreApplicationBrowsers, headers, browserCaps);
1075                 GenerateChildProcessInvokeExpression(methodName, cmm, true);
1076             }
1077
1078             //GEN: return true;
1079             CodeMethodReturnStatement cmrs = new CodeMethodReturnStatement(new CodePrimitiveExpression(true));
1080             cmm.Statements.Add(cmrs);
1081
1082             ctd.Members.Add(cmm);
1083         }
1084
1085         private void GenerateChildProcessInvokeExpression(string methodName, CodeMemberMethod cmm, bool generateTracker) {
1086             //Gen: XxxProcessBrowsers(ignoreApplicationBrowsers, headers, browserCaps) ;
1087             CodeMethodInvokeExpression expr = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), methodName);
1088
1089             if (generateTracker) {
1090                 expr.Parameters.Add(new CodeVariableReferenceExpression(IgnoreApplicationBrowserVariableName));
1091             }
1092             expr.Parameters.Add(new CodeVariableReferenceExpression(_headersRefName));
1093             expr.Parameters.Add(new CodeVariableReferenceExpression(browserCapsVariable));
1094
1095             cmm.Statements.Add(expr);
1096         }
1097
1098         private void GenerateChildProcessMethod(string methodName, CodeTypeDeclaration ctd, bool generateTracker) {
1099             //Gen: protected virtual void XxxProcessBrowsers(bool ignoreApplicationBrowsers, NameValueCollection headers, HttpBrowserCapabilities browserCaps) ;
1100             CodeMemberMethod cmm= new CodeMemberMethod();
1101             cmm.Name = methodName;
1102             cmm.ReturnType = new CodeTypeReference(typeof(void));
1103             cmm.Attributes = MemberAttributes.Family;
1104             CodeParameterDeclarationExpression cpde = null;
1105
1106             if (generateTracker) {
1107                 cpde = new CodeParameterDeclarationExpression(typeof(bool), IgnoreApplicationBrowserVariableName);
1108                 cmm.Parameters.Add(cpde);
1109             }
1110
1111             cpde = new CodeParameterDeclarationExpression(typeof(NameValueCollection), _headersRefName);
1112             cmm.Parameters.Add(cpde);
1113             cpde = new CodeParameterDeclarationExpression(typeof(HttpBrowserCapabilities), browserCapsVariable);
1114             cmm.Parameters.Add(cpde);
1115
1116             ctd.Members.Add(cmm);
1117         }
1118
1119         private void GenerateRegexWorkerIfNecessary(CodeMemberMethod cmm, ref bool regexWorkerGenerated) {
1120             if (regexWorkerGenerated) {
1121                 return;
1122             }
1123
1124             regexWorkerGenerated = true;
1125
1126             //GEN: RegexWorker regexWorker;
1127             cmm.Statements.Add(new CodeVariableDeclarationStatement("RegexWorker", _regexWorkerRefName));
1128
1129             //GEN: regexWorker = new RegexWorker(browserCaps);
1130             cmm.Statements.Add(new CodeAssignStatement(_regexWorkerRefExpr, new CodeObjectCreateExpression("RegexWorker", _browserCapsRefExpr)));
1131         }
1132
1133         private void ReturnIfHeaderValueEmpty(CodeMemberMethod cmm, CodeVariableReferenceExpression varExpr) {
1134             //  GEN: if(String.IsNullOrEmpty(varExpr)) {
1135             //  GEN:     return false;
1136             //  GEN: }
1137             CodeConditionStatement emptyCheckStmt = new CodeConditionStatement();
1138             CodeMethodReferenceExpression emptyCheckMethod = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(typeof(String)), "IsNullOrEmpty");
1139             CodeMethodInvokeExpression emptyCheckExpr = new CodeMethodInvokeExpression(emptyCheckMethod, varExpr);
1140
1141             emptyCheckStmt.Condition = emptyCheckExpr;
1142             emptyCheckStmt.TrueStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(false)));
1143             cmm.Statements.Add(emptyCheckStmt);
1144         }
1145
1146         //generate part of the xxxProcess method for handling determining if the requesting
1147         //browser meets the regexes for this browser
1148         private void GenerateIdentificationCode(BrowserDefinition bd, CodeMemberMethod cmm, ref bool regexWorkerGenerated) {
1149
1150             //GEN: IDictionary dictionary;
1151             cmm.Statements.Add(new CodeVariableDeclarationStatement(typeof(IDictionary), _dictionaryRefName));
1152
1153             //GEN: dictionary = browserCaps.Capabilities;
1154             CodeAssignStatement assign = new CodeAssignStatement(
1155                 _dictionaryRefExpr,
1156                 new CodePropertyReferenceExpression(_browserCapsRefExpr, "Capabilities")
1157                 );
1158             cmm.Statements.Add(assign);
1159
1160             bool disableOptimizedKey = false;
1161             CodeVariableReferenceExpression result = null;
1162             CodeVariableReferenceExpression headerValue = null;
1163
1164             if(bd.IdHeaderChecks.Count > 0) {
1165                 AddComment("Identification: check header matches", cmm);
1166                 for (int i = 0; i < bd.IdHeaderChecks.Count; i++) {
1167                     string matchedString = ((CheckPair)bd.IdHeaderChecks[i]).MatchString;
1168
1169                     // Skip matching ".*"
1170                     if (matchedString.Equals(".*")) {
1171                         continue;
1172                     }
1173
1174                     if (headerValue == null) {
1175                         headerValue = GenerateVarReference(cmm, typeof(string), "headerValue");
1176                     }
1177
1178                     CodeAssignStatement valueAssignment = new CodeAssignStatement();
1179                     cmm.Statements.Add(valueAssignment);
1180                     valueAssignment.Left = headerValue;
1181
1182                     if (((CheckPair)bd.IdHeaderChecks[i]).Header.Equals("User-Agent")) {
1183                         _headers.Add(String.Empty);
1184
1185                         // GEN: headerValue = ((string)(browserCaps[String.Empty]));
1186                         valueAssignment.Right = new CodeCastExpression(typeof(string),
1187                                                 new CodeIndexerExpression(
1188                                                     new CodeVariableReferenceExpression(browserCapsVariable),
1189                                                     new CodeExpression[] { 
1190                                                         new CodePropertyReferenceExpression(
1191                                                         new CodeTypeReferenceExpression(typeof(String)), "Empty") }));
1192                     }
1193                     else {
1194                         string header = ((CheckPair)bd.IdHeaderChecks[i]).Header;
1195                         _headers.Add(header);
1196
1197                         //GEN: headerValue = ((String)headers["xxx"]);
1198                         valueAssignment.Right = new CodeCastExpression(typeof(string),
1199                                                    new CodeIndexerExpression(
1200                                                        _headersRefExpr,
1201                                                        new CodeExpression[] { new CodePrimitiveExpression(header) }
1202                                                        )
1203                                                    );
1204
1205                         disableOptimizedKey = true;
1206                     }
1207
1208                     // Don't need to use Regex if matching . only.
1209                     if (matchedString.Equals(".")) {
1210
1211                         // Simply return if the header exists.
1212                         ReturnIfHeaderValueEmpty(cmm, headerValue);
1213
1214                         continue;
1215                     }
1216
1217                     if (result == null) {
1218                         result = GenerateVarReference(cmm, typeof(bool), _resultVarName);
1219                     }
1220
1221                     GenerateRegexWorkerIfNecessary(cmm, ref regexWorkerGenerated);
1222                     CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression(_regexWorkerRefExpr, _processRegexMethod);
1223
1224                     cmie.Parameters.Add(headerValue);
1225                     cmie.Parameters.Add(new CodePrimitiveExpression(matchedString));
1226
1227                     //GEN: result = regexWorker.ProcessRegex(headerValue, {matchedString});
1228                     assign = new CodeAssignStatement();
1229                     assign.Left = result;
1230                     assign.Right = cmie;
1231                     cmm.Statements.Add(assign);
1232
1233                     //GEN: if(result == false) {
1234                     //GEN:     return false;
1235                     //GEN: }
1236                     CodeConditionStatement istatement = new CodeConditionStatement();
1237                     if(((CheckPair)bd.IdHeaderChecks[i]).NonMatch) {
1238                         istatement.Condition = new CodeBinaryOperatorExpression(result, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(true));
1239                     }
1240                     else {
1241                         istatement.Condition = new CodeBinaryOperatorExpression(result, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(false));
1242                     }
1243                     istatement.TrueStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(false)));
1244                     cmm.Statements.Add(istatement);
1245                 }
1246             }
1247
1248             if (bd.IdCapabilityChecks.Count > 0) {
1249                 AddComment("Identification: check capability matches", cmm);
1250                 for (int i = 0; i < bd.IdCapabilityChecks.Count; i++) {
1251                     string matchedString = ((CheckPair)bd.IdCapabilityChecks[i]).MatchString;
1252
1253                     // Skip matching ".*"
1254                     if (matchedString.Equals(".*")) {
1255                         continue;
1256                     }
1257
1258                     if (headerValue == null) {
1259                         headerValue = GenerateVarReference(cmm, typeof(string), "headerValue");
1260                     }
1261
1262                     CodeAssignStatement valueAssignment = new CodeAssignStatement();
1263                     cmm.Statements.Add(valueAssignment);
1264                     valueAssignment.Left = headerValue;
1265                     valueAssignment.Right = (new CodeCastExpression(typeof(string),
1266                                                                new CodeIndexerExpression(
1267                                                                    _dictionaryRefExpr,
1268                                                                    new CodeExpression[] {
1269                                                                        new CodePrimitiveExpression(((CheckPair)bd.IdCapabilityChecks[i]).Header)
1270                                                                    }
1271                                                                    )
1272                                                                ));
1273
1274                     // Don't need to use Regex if matching . only.
1275                     if (matchedString.Equals(".")) {
1276                         continue;
1277                     } 
1278
1279                     if (result == null) {
1280                         result = GenerateVarReference(cmm, typeof(bool), _resultVarName);
1281                     }
1282
1283                     GenerateRegexWorkerIfNecessary(cmm, ref regexWorkerGenerated);
1284                     //GEN: result = regexWorker.ProcessRegex((string)dictionary["xxxCapability"], "xxxRegexString");
1285                     CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression(_regexWorkerRefExpr, _processRegexMethod);
1286
1287                     cmie.Parameters.Add(headerValue);
1288                     cmie.Parameters.Add(new CodePrimitiveExpression(matchedString));
1289                     assign = new CodeAssignStatement();
1290                     assign.Left = result;
1291                     assign.Right = cmie;
1292                     cmm.Statements.Add(assign);
1293
1294                     //GEN: if(result == false) {
1295                     //GEN:      return false;
1296                     //GEN: }
1297                     CodeConditionStatement istatement = new CodeConditionStatement();
1298                     if (((CheckPair)bd.IdCapabilityChecks[i]).NonMatch) {
1299                         istatement.Condition = new CodeBinaryOperatorExpression(result, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(true));
1300                     }
1301                     else {
1302                         istatement.Condition = new CodeBinaryOperatorExpression(result, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(false));
1303                     }
1304                     istatement.TrueStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(false)));
1305                     cmm.Statements.Add(istatement);
1306                 }
1307             }
1308
1309             //GEN: browserCaps.DisableOptimizedCacheKey();
1310             if (disableOptimizedKey) {
1311                 CodeMethodInvokeExpression cme = new CodeMethodInvokeExpression(_browserCapsRefExpr, _disableOptimizedCacheKeyMethodName);
1312                 cmm.Statements.Add(cme);
1313             }
1314         }
1315
1316         private CodeVariableReferenceExpression GenerateVarReference(CodeMemberMethod cmm, Type varType, string varName) {
1317             //GEN: {varType} {varName};
1318             cmm.Statements.Add(new CodeVariableDeclarationStatement(varType, varName));
1319             return new CodeVariableReferenceExpression(varName);
1320         }
1321
1322         //generate part of the xxxProcess method for running and storing the capture regexes
1323         private void GenerateCapturesCode(BrowserDefinition bd, CodeMemberMethod cmm, ref bool regexWorkerGenerated) {
1324             if ((bd.CaptureHeaderChecks.Count == 0) && (bd.CaptureCapabilityChecks.Count == 0)) {
1325                 return;
1326             }
1327
1328             if(bd.CaptureHeaderChecks.Count > 0) {
1329                 AddComment("Capture: header values", cmm);
1330                 for(int i = 0; i < bd.CaptureHeaderChecks.Count; i++) {
1331
1332                     string matchedString = ((CheckPair)bd.CaptureHeaderChecks[i]).MatchString;
1333                     if (matchedString.Equals(".*")) {
1334                         continue;
1335                     }
1336
1337                     GenerateRegexWorkerIfNecessary(cmm, ref regexWorkerGenerated);
1338                     CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression(_regexWorkerRefExpr, _processRegexMethod);
1339
1340                     if (((CheckPair)bd.CaptureHeaderChecks[i]).Header.Equals("User-Agent")) {
1341                         _headers.Add(String.Empty);
1342                         cmie.Parameters.Add(new CodeCastExpression(typeof(string),
1343                             new CodeIndexerExpression(new CodeVariableReferenceExpression(browserCapsVariable), new CodeExpression[] { 
1344                                 new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeof(String)), "Empty") })));
1345                     }
1346                     else {
1347                         string header = ((CheckPair)bd.CaptureHeaderChecks[i]).Header;
1348                         _headers.Add(header);
1349
1350                         //GEN: regexWorker.ProcessRegex((string)headers["xxx"], "xxxRegexString");
1351                         cmie.Parameters.Add(
1352                             new CodeCastExpression(typeof(string),
1353                                                    new CodeIndexerExpression(
1354                                                        _headersRefExpr,
1355                                                        new CodeExpression[] { new CodePrimitiveExpression(header) }
1356                                                        )
1357                                                    )
1358                             );
1359                     }
1360
1361                     cmie.Parameters.Add(new CodePrimitiveExpression(matchedString));
1362                     cmm.Statements.Add(cmie);
1363                 }
1364             }
1365
1366             if (bd.CaptureCapabilityChecks.Count > 0) {
1367                 AddComment("Capture: capability values", cmm);
1368                 for(int i = 0; i < bd.CaptureCapabilityChecks.Count; i++) {
1369
1370                     string matchedString = ((CheckPair)bd.CaptureCapabilityChecks[i]).MatchString;
1371                     if (matchedString.Equals(".*")) {
1372                         continue;
1373                     }
1374
1375                     GenerateRegexWorkerIfNecessary(cmm, ref regexWorkerGenerated);
1376                     //GEN: regexWorker.ProcessRegex((string)dictionary["xxxCapability"], "xxxRegexString");
1377                     CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression(_regexWorkerRefExpr, _processRegexMethod);
1378                     cmie.Parameters.Add(
1379                         new CodeCastExpression(typeof(string),
1380                                                new CodeIndexerExpression(
1381                                                    _dictionaryRefExpr,
1382                                                    new CodeExpression[] { new CodePrimitiveExpression(((CheckPair)bd.CaptureCapabilityChecks[i]).Header) }
1383                                                    )
1384                                                )
1385                         );
1386
1387                     cmie.Parameters.Add(new CodePrimitiveExpression(matchedString));
1388                     cmm.Statements.Add(cmie);
1389                 }
1390             }
1391         }
1392
1393         //generate part of the xxxProcess method for assigning capability values
1394         private void GenerateSetCapabilitiesCode(BrowserDefinition bd, CodeMemberMethod cmm, ref bool regexWorkerGenerated) {
1395             //GEN: browserCaps[aaa] = "bbb";
1396             //GEN: browserCaps[xxx] = "yyy";
1397             NameValueCollection nvc = bd.Capabilities;
1398             CodeAssignStatement assign;
1399
1400             AddComment("Capabilities: set capabilities", cmm);
1401             foreach (string s in nvc.Keys) {
1402                 string capsString = nvc[s];
1403                 //GEN: dictionary["xxx"] = regexWorker["xxx"];
1404                 assign = new CodeAssignStatement();
1405                 assign.Left = new CodeIndexerExpression(
1406                     _dictionaryRefExpr,
1407                     new CodeExpression[] { new CodePrimitiveExpression(s) } );
1408
1409                 CodePrimitiveExpression capabilityExpr = new CodePrimitiveExpression(capsString);
1410                 if (RegexWorker.RefPat.Match(capsString).Success) {
1411
1412                     GenerateRegexWorkerIfNecessary(cmm, ref regexWorkerGenerated);
1413                     assign.Right = new CodeIndexerExpression(
1414                         _regexWorkerRefExpr,
1415                         new CodeExpression[] {capabilityExpr});
1416                 }
1417                 else {
1418                     assign.Right = capabilityExpr;
1419                 }
1420
1421                 cmm.Statements.Add(assign);
1422             }
1423         }
1424
1425         //generate part of the xxxProcess method for setting specific adapters for this browser
1426         internal void GenerateSetAdaptersCode(BrowserDefinition bd, CodeMemberMethod cmm) {
1427             //GEN: browserCaps.Adapters[xxxControl] = yyyAdapter;
1428             foreach (DictionaryEntry entry in bd.Adapters) {
1429                 string controlString = (string)entry.Key;
1430                 string adapterString = (string)entry.Value;
1431                 CodePropertyReferenceExpression cpre = new CodePropertyReferenceExpression(_browserCapsRefExpr, "Adapters");
1432                 CodeIndexerExpression indexerExpression = new CodeIndexerExpression(
1433                     cpre,
1434                     new CodeExpression[] { new CodePrimitiveExpression(controlString) }
1435                     );
1436                 CodeAssignStatement assignAdapter = new CodeAssignStatement();
1437                 assignAdapter.Left = indexerExpression;
1438                 assignAdapter.Right = new CodePrimitiveExpression(adapterString);
1439                 cmm.Statements.Add(assignAdapter);
1440             }
1441
1442             //GEN: browser.HtmlTextWriter = xxxHtmlTextWriter;
1443             if(bd.HtmlTextWriterString != null) {
1444                 CodeAssignStatement assignHtmlTextWriter = new CodeAssignStatement();
1445                 assignHtmlTextWriter.Left = new CodePropertyReferenceExpression(_browserCapsRefExpr, "HtmlTextWriter");
1446                 assignHtmlTextWriter.Right = new CodePrimitiveExpression(bd.HtmlTextWriterString);
1447                 cmm.Statements.Add(assignHtmlTextWriter);
1448             }
1449             return;
1450         }
1451
1452         internal void AddComment(string comment, CodeMemberMethod cmm) {
1453             cmm.Statements.Add(new CodeCommentStatement(comment));
1454         }
1455
1456         internal CodeStatementCollection GenerateTrackedSingleProcessCall(CodeStatementCollection stmts, BrowserDefinition bd, CodeMemberMethod cmm) {
1457             return GenerateTrackedSingleProcessCall(stmts, bd, cmm, String.Empty);
1458         }
1459
1460         internal CodeStatementCollection GenerateTrackedSingleProcessCall(CodeStatementCollection stmts, BrowserDefinition bd, CodeMemberMethod cmm, string prefix) {
1461             //GEN:  if (xProcess(headers, browserCaps)) {
1462             //      }
1463             //      else {
1464             //          ...
1465             //      }
1466             CodeMethodInvokeExpression xProcess = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), prefix + bd.Name + "Process");
1467             xProcess.Parameters.Add(new CodeVariableReferenceExpression(_headersRefName));
1468             xProcess.Parameters.Add(new CodeVariableReferenceExpression(browserCapsVariable));
1469
1470             CodeConditionStatement conditionStmt = new CodeConditionStatement();
1471             conditionStmt.Condition = xProcess;
1472
1473             stmts.Add(conditionStmt);
1474
1475             return conditionStmt.FalseStatements;
1476         }
1477
1478         internal void GenerateSingleProcessCall(BrowserDefinition bd, CodeMemberMethod cmm) {
1479             GenerateSingleProcessCall(bd, cmm, String.Empty);
1480         }
1481
1482         //generate code to call the xxxProcess for a given browser
1483         //and store the result in a local variable
1484         internal void GenerateSingleProcessCall(BrowserDefinition bd, CodeMemberMethod cmm, string prefix) {
1485             //GEN: xProcess(headers, browserCaps);
1486             CodeMethodInvokeExpression xProcess = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), prefix + bd.Name + "Process");
1487             xProcess.Parameters.Add(new CodeVariableReferenceExpression(_headersRefName));
1488             xProcess.Parameters.Add(new CodeVariableReferenceExpression(browserCapsVariable));
1489             cmm.Statements.Add(xProcess);
1490         }
1491     }
1492 }