Merge pull request #439 from mono-soc-2012/garyb/iconfix
[mono.git] / mcs / class / System.Web / System.Web.Configuration_2.0 / nBrowser / Node.cs
index 7e47eb20e164a8163d6b8c097952d2493061ee8f..e41e10b14065bcd23adc89bca6715fc6a57c29f1 100644 (file)
@@ -2,7 +2,8 @@
 /*
 Used to determine Browser Capabilities by the Browsers UserAgent String and related
 Browser supplied Headers.
-Copyright (C) 2002-Present  Owen Brady (Ocean at xvision.com)
+Copyright (C) 2002-Present  Owen Brady (Ocean at owenbrady dot net) 
+and Dean Brettle (dean at brettle dot com)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy 
 of this software and associated documentation files (the "Software"), to deal
@@ -27,6 +28,8 @@ namespace System.Web.Configuration.nBrowser
        using System;
        using System.Collections.Generic;
        using System.Text;
+       using System.Text.RegularExpressions;
+               
        internal class Node
        {
                #region Public Properties
@@ -128,6 +131,7 @@ namespace System.Web.Configuration.nBrowser
                private Identification[] Capture;
                private System.Collections.Specialized.NameValueCollection Capabilities;
                private System.Collections.Specialized.NameValueCollection Adapter;
+               private Type[] AdapterControlTypes, AdapterTypes;
                private System.Collections.Generic.List<string> ChildrenKeys;
                private System.Collections.Generic.List<string> DefaultChildrenKeys;
                private System.Collections.Generic.SortedList<string, nBrowser.Node> Children;
@@ -258,7 +262,7 @@ namespace System.Web.Configuration.nBrowser
                /// <param name="node"></param>
                private void ProcessCapabilities(System.Xml.XmlNode node)
                {
-                       Capabilities = new System.Collections.Specialized.NameValueCollection(node.ChildNodes.Count);
+                       Capabilities = new System.Collections.Specialized.NameValueCollection(node.ChildNodes.Count, StringComparer.OrdinalIgnoreCase);
 
                        for (int a = 0;a <= node.ChildNodes.Count - 1;a++)
                        {
@@ -331,6 +335,9 @@ namespace System.Web.Configuration.nBrowser
                                        Adapter[controlType] = adapterType;
                                }
                        }
+
+                       AdapterControlTypes = null;
+                       AdapterTypes = null;
                }
 
                /// <summary>
@@ -397,6 +404,8 @@ namespace System.Web.Configuration.nBrowser
                        Capture = null;
                        Capabilities = null;
                        Adapter = null;
+                       AdapterControlTypes = null;
+                       AdapterTypes = null;
                        if (string.Compare(this.xmlNode.Name, "browser", true, System.Globalization.CultureInfo.CurrentCulture) == 0)
                        {
                                this.NameType = NodeType.Browser;
@@ -461,6 +470,12 @@ namespace System.Web.Configuration.nBrowser
                                {
                                        ProcessSampleHeaders(xmlNode.ChildNodes[a]);
                                }
+                               
+                               if (Id == "default" && (Identification == null || Identification.Length == 0))
+                               {
+                                       Identification = new Identification[1];
+                                       Identification[0] = new System.Web.Configuration.nBrowser.Identification(true, "header", "User-Agent", ".");
+                               }
                        }
                }
                /// <summary>
@@ -505,16 +520,53 @@ namespace System.Web.Configuration.nBrowser
                                DefaultChildrenKeys.Remove(child.Id);
                        }
                }
+                               
+               private Type FindType(string typeName)
+               {
+                       foreach (System.Reflection.Assembly a in System.AppDomain.CurrentDomain.GetAssemblies())
+                       {
+                               string fullTypeName = typeName + "," + a.FullName;
+                               Type t = System.Type.GetType(fullTypeName); // case-sensitive
+                               if (t != null)
+                                       return t;
+                               t = System.Type.GetType(fullTypeName, false, true); // case-insensitive
+                               if (t != null)
+                                       return t;
+                       }
+                       throw new TypeLoadException(typeName);
+               }
                
+               /// <summary>
+               /// Matches the header collection against this subtree and uses the matchList
+               /// and any new matches to augment the result.  This method calls ProcessSubtree()
+               /// but then removes the matches that it adds to the matchList.
+               /// </summary>
+               /// <param name="header">the header collection to evaluate (invariant)</param>
+               /// <param name="result">the result of the match (might be changed if a match is found)</param>
+               /// <param name="matchList">the matches to use to do substitutions (invariant)</param>
+               /// <returns>true iff this node or one of it's descendants matches</returns>
+               internal bool Process(System.Collections.Specialized.NameValueCollection header, nBrowser.Result result,
+                                     System.Collections.Generic.List<Match> matchList)
+               {
+                       // The real work is done in ProcessSubtree.  This method just ensures that matchList is restored
+                       // to its original state before returning.
+                       int origMatchListCount = matchList.Count;
+                       bool matched = ProcessSubtree(header, result, matchList);
+                       if (matchList.Count > origMatchListCount)
+                               matchList.RemoveRange(origMatchListCount, matchList.Count-origMatchListCount);
+                       return matched;
+               }
                
                /// <summary>
-               /// 
+               /// Matches the header collection against this subtree, adds any new matches for this node to
+               /// matchList, and uses the matchList to augment the result.  
                /// </summary>
-               /// <param name="header"></param>
-               /// <param name="result"></param>
-               /// <param name="List"></param>
-               /// <returns></returns>
-               internal nBrowser.Result Process(System.Collections.Specialized.NameValueCollection header, nBrowser.Result result, System.Collections.Generic.List<System.Web.Configuration.nBrowser.Identification> List)
+               /// <param name="header">the header collection to evaluate (invariant)</param>
+               /// <param name="result">the result of the match (might be changed if a match is found)</param>
+               /// <param name="matchList">the matches to use to do substitutions, 
+               /// possibly including new matches for this node.</param>
+               /// <returns>true iff this node or one of it's descendants matches</returns>
+               private bool ProcessSubtree(System.Collections.Specialized.NameValueCollection header, nBrowser.Result result, System.Collections.Generic.List<Match> matchList)
                {
                        //----------------------------------------------------------------------
                        //This is just coded over from MS version since if you pass in an empty
@@ -522,13 +574,17 @@ namespace System.Web.Configuration.nBrowser
                        //----------------------------------------------------------------------
                        result.AddCapabilities("", header["User-Agent"]);
 
-                       //this step shouldn't really be necessary, if it was checked
-                       //prior to calling this method, but we assume it hasn't and
-                       //recheck anyways.
-                       if (BrowserIdentification(header, result) == false)
+                       if (RefId.Length == 0 && this.NameType != NodeType.DefaultBrowser)
                        {
-                               return result;
+                               //----------------------------------------------------------------------
+                               //BrowserIdentification adds all the Identifiction matches to the match
+                               //list if this node matches.
+                               //----------------------------------------------------------------------
+                               if (!BrowserIdentification(header, result, matchList))
+                                       return false;
                        }
+
+                       result.AddMatchingBrowserId (this.Id);
                        #region Browser Identification Successfull
                        //----------------------------------------------------------------------
                        //By reaching this point, it either means there were no Identification 
@@ -542,9 +598,10 @@ namespace System.Web.Configuration.nBrowser
                        //----------------------------------------------------------------------
                        if (Adapter != null)
                        {
+                               LookupAdapterTypes();
                                for (int i = 0;i <= Adapter.Count - 1;i++)
                                {
-                                       result.AddAdapter(Adapter.GetKey(i), Adapter[i]);
+                                       result.AddAdapter(AdapterControlTypes [i], AdapterTypes [i]);
                                }
                        }
 
@@ -561,27 +618,12 @@ namespace System.Web.Configuration.nBrowser
                                        result.MarkupTextWriter = Type.GetType(MarkupTextWriterType, true, true);
                        }
 
-                       //----------------------------------------------------------------------
-                       //Adds all the Identifiction matches to the List of Identification
-                       //list.
-                       //----------------------------------------------------------------------
-                       if (this.NameType != NodeType.DefaultBrowser)
-                       {
-                               for (int i = 0;i <= Identification.Length - 1;i++)
-                               {
-                                       if (Identification[i].HasCaptureGroups == true)
-                                       {
-                                               List.Add(Identification[i]);
-                                       }
-                               }
-                       }
                        #endregion
                        #region Capture
                        if (Capture != null)
                        {
                                //----------------------------------------------------------------------
-                               //Adds all the sucessfull Capture matches to the List of generic 
-                               //Identification list.
+                               //Adds all the sucessfull Capture matches to the matchList
                                //----------------------------------------------------------------------
                                for (int i = 0;i <= Capture.Length - 1;i++)
                                {
@@ -592,19 +634,18 @@ namespace System.Web.Configuration.nBrowser
                                        {
                                                continue;
                                        }
-                                       //take no chances get back to base state.
-                                       Capture[i].Reset();
+                                       Match m = null;
                                        if (Capture[i].Group == "header")
                                        {
-                                               Capture[i].Match(header[Capture[i].Name]);
+                                               m = Capture[i].GetMatch(header[Capture[i].Name]);
                                        }
                                        else if (Capture[i].Group == "capability")
                                        {
-                                               Capture[i].Match(result[Capture[i].Name]);
+                                               m = Capture[i].GetMatch(result[Capture[i].Name]);
                                        }
-                                       if (Capture[i].Success == true && Capture[i].HasCaptureGroups == true)
+                                       if (Capture[i].IsMatchSuccessful(m) && m.Groups.Count > 0)
                                        {
-                                               List.Add(Capture[i]);
+                                               matchList.Add(m);
                                        }
                                }
                        }
@@ -621,84 +662,49 @@ namespace System.Web.Configuration.nBrowser
                                for (int i = 0;i <= Capabilities.Count - 1;i++)
                                {
                                        //----------------------------------------------------------------------
-                                       //Most items do not have any regular expression in them
-                                       //so we can add them directly to the results.
+                                       //We need to further process these Capabilities to 
+                                       //insert the proper information.
                                        //----------------------------------------------------------------------
-                                       if (Capabilities[i].IndexOf("$") == -1 && Capabilities[i].IndexOf("%") == -1)
+                                       string v = Capabilities[i];
+
+                                       //----------------------------------------------------------------------
+                                       //Loop though the list of Identifiction/Capture Matches
+                                       //in reverse order. Meaning the newest Items in the list
+                                       //get checked first, then working to the oldest. Often times
+                                       //Minor /Major revisition numbers will be listed multple times
+                                       //and only the newest one (most recent matches) are the ones
+                                       //we want to insert.
+                                       //----------------------------------------------------------------------
+                                       for (int a = matchList.Count - 1; a >= 0 && v != null && v.Length > 0 &&  v.IndexOf('$') > -1; a--)
                                        {
-                                               result.AddCapabilities(Capabilities.Keys[i], Capabilities[i]);
+                                               // Don't do substitution if the match has no groups or was a nonMatch
+                                               if (matchList[a].Groups.Count == 0 || !matchList[a].Success)
+                                                       continue;
+                                               v = matchList[a].Result(v);
                                        }
-                                       else
-                                       {
-                                               //----------------------------------------------------------------------
-                                               //We need to further process these Capabilities to 
-                                               //insert the proper information.
-                                               //----------------------------------------------------------------------
-                                               string v = string.Empty;
 
+                                       //----------------------------------------------------------------------
+                                       //Checks to make sure we extract the result we where looking for.
+                                       //----------------------------------------------------------------------
+                                       if (v.IndexOf('$') > -1 || v.IndexOf('%') > -1)
+                                       {
                                                //----------------------------------------------------------------------
-                                               //Loop though the list of Identifiction/Capture Matches
-                                               //in reverse order. Meaning the newest Items in the list
-                                               //get checked first, then working to the oldest. Often times
-                                               //Minor /Major revisition numbers will be listed multple times
-                                               //and only the newest one (most recent matches) are the ones
-                                               //we want to insert.
-                                               //----------------------------------------------------------------------
-                                               if (Capabilities[i].IndexOf("$") >= -1)
-                                               {
-                                                       for (int a = List.Count - 1;a >= 0;a--)
-                                                       {
-                                                               v = ((System.Web.Configuration.nBrowser.Identification)List[a]).Result(Capabilities[i]);
-                                                               //----------------------------------------------------------------------
-                                                               //exit the loop once we are able to extract a result.
-                                                               //----------------------------------------------------------------------
-                                                               if (v.Length > 0 && v.IndexOf("$") == -1)
-                                                               {
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-                                               //----------------------------------------------------------------------
-                                               //Checks to make sure we extract the result we where looking for.
+                                               //Microsoft has a nasty habbit of using capability items in regular expressions
+                                               //so I have to figure a nice way to working around it
+                                               // <capability name="msdomversion"              value="${majorversion}${minorversion}" />
                                                //----------------------------------------------------------------------
-                                               if (v.IndexOf("$") > -1 || v.IndexOf("%") > -1)
-                                               {
-                                                       //----------------------------------------------------------------------
-                                                       //Microsoft has a nasty habbit of using capability items in regular expressions
-                                                       //so I have to figure a nice way to working around it
-                                                       // <capability name="msdomversion"              value="${majorversion}${minorversion}" />
-                                                       //----------------------------------------------------------------------
-
-                                                       //double checks the values against the current Capabilities. to 
-                                                       //find any last minute matches. that are not defined by regluar
-                                                       //expressions
-                                                       v = result.Replace(v);
-                                               }
 
-                                               if (v.Length > 0 && v.IndexOf("$") == -1 && v.IndexOf("%") == -1)
-                                               {
-                                                       result.AddCapabilities(Capabilities.Keys[i], v);
-                                               }
-                                               else
-                                               {
-                                                       //----------------------------------------------------------------------
-                                                       //This happens pretty often, especially when mobile phone browsers
-                                                       //are in use, and different proxie servers send back varying amount
-                                                       //of details, when Items are missing, this will often pop.
-                                                       //----------------------------------------------------------------------
-                                                       //Console.WriteLine(this.Id +"\t"+v);
-                                               }
+                                               //double checks the values against the current Capabilities. to 
+                                               //find any last minute matches. that are not defined by regluar
+                                               //expressions
+                                               v = result.Replace(v);
                                        }
+
+                                       result.AddCapabilities(Capabilities.Keys[i], v);
                                }
                        }
                        #endregion
-                       if (Adapter != null)
-                       {
-                               for (int i = 0;i <= Adapter.Count - 1;i++)
-                               {
-                                       result.AddAdapter(Adapter.GetKey(i), Adapter[i]);
-                               }
-                       }
+
                        //----------------------------------------------------------------------
                        //Run the Default Children after the Parent Node is finished with 
                        //what it is doing
@@ -709,7 +715,7 @@ namespace System.Web.Configuration.nBrowser
                                Node node = DefaultChildren[key];
                                if (node.NameType == NodeType.DefaultBrowser)
                                {
-                                       node.Process(header, result, List);
+                                       node.Process(header, result, matchList);
                                }
                        }
                        //----------------------------------------------------------------------
@@ -726,91 +732,37 @@ namespace System.Web.Configuration.nBrowser
                                Node node = Children[key];
                                if (node.NameType == NodeType.Gateway)
                                {
-                                       node.Process(header, result, List);
+                                       node.Process(header, result, matchList);
                                }
                        }
                        for (int i = 0;i <= Children.Count - 1;i++)
                        {
                                string key = ChildrenKeys[i];
                                Node node = Children[key];
-                               if (node.NameType == NodeType.Browser)
-                               {
-                                       if (node.BrowserIdentification(header, result) == true)
-                                       {
-                                               node.Process(header, result, List);
-                                               break;
-                                       }
-                               }
-                       }
-
-                       #region Remove Identification & Caputers from lists
-                       //----------------------------------------------------------------------
-                       //
-                       //---------------------------------------------------------------------
-                       if (Identification != null)
-                       {
-                               for (int i = 0;i <= Identification.Length - 1;i++)
-                               {
-                                       //shouldn't happen often, the null should
-                                       //signal the end of the list, I keep procssing
-                                       //the rest just in case
-                                       if (Identification[i] == null)
-                                       {
-                                               continue;
-                                       }
-                                       if (Identification[i].HasCaptureGroups == true)
-                                       {
-                                               List.Remove(Identification[i]);
-                                       }
-                               }
-                       }
-                       //----------------------------------------------------------------------
-                       //
-                       //---------------------------------------------------------------------
-                       if (Capture != null)
-                       {
-                               for (int i = 0;i <= Capture.Length - 1;i++)
-                               {
-                                       //shouldn't happen often, the null should
-                                       //signal the end of the list, I keep procssing
-                                       //the rest just in case
-                                       if (Capture[i] == null)
-                                       {
-                                               continue;
-                                       }
-                                       if (Capture[i].Success == true && Capture[i].HasCaptureGroups == true)
-                                       {
-                                               List.Remove(Capture[i]);
-                                       }
-                               }
+                               if (node.NameType == NodeType.Browser 
+                                   && node.Process(header, result, matchList))
+                                       break;
                        }
-                       #endregion
 
-                       return result;
+                       return true;
                }
+               
                /// <summary>
                /// 
                /// </summary>
                /// <param name="header"></param>
                /// <param name="result"></param>
-               /// <returns></returns>
-               public bool BrowserIdentification(System.Collections.Specialized.NameValueCollection header, System.Web.Configuration.CapabilitiesResult result)
+               /// <param name="matchList"></param>
+               /// <returns>true iff this node is a match</returns>
+               private bool BrowserIdentification(System.Collections.Specialized.NameValueCollection header, System.Web.Configuration.CapabilitiesResult result, System.Collections.Generic.List<Match> matchList)
                {
                        if (Id.Length > 0 && RefId.Length > 0)
                        {
                                throw new nBrowser.Exception("Id and refID Attributes givin when there should only be one set not both");
                        }
-                       if (RefId.Length > 0)
-                       {
-                               return true;
-                       }
-                       if (this.NameType == NodeType.DefaultBrowser)
-                       {
-                               return true;
-                       }
                        if (Identification == null || Identification.Length == 0)
                        {
-                               throw new nBrowser.Exception("Missing Identification Section where one is required");
+                               throw new nBrowser.Exception(String.Format("Missing Identification Section where one is required (Id={0}, RefID={1})", Id, RefId));
                        }
                        if (header == null)
                        {
@@ -824,7 +776,7 @@ namespace System.Web.Configuration.nBrowser
 #if trace                         
                        System.Diagnostics.Trace.WriteLine(string.Format("{0}[{1}]", ("[" + this.Id + "]").PadRight(45), this.ParentId));
 #endif
-
+                       
                        for (int i = 0;i <= Identification.Length - 1;i++)
                        {
 
@@ -835,8 +787,6 @@ namespace System.Web.Configuration.nBrowser
                                {
                                        continue;
                                }
-                               //take no chances get back to base state.
-                               Identification[i].Reset();
                                string v = string.Empty;
                                if (string.Compare(Identification[i].Group, "header", true, System.Globalization.CultureInfo.CurrentCulture) == 0)
                                {
@@ -852,19 +802,14 @@ namespace System.Web.Configuration.nBrowser
                                {
                                        v = string.Empty;
                                }
-                               Identification[i].Match(v);
+                               Match m = Identification[i].GetMatch(v);
                                //----------------------------------------------------------------------
                                //we exit this method return the orginal Result back to  the calling method.
                                //----------------------------------------------------------------------
-                               if (Identification[i].Success == false)
+                               if (Identification[i].IsMatchSuccessful(m) == false)
                                {
 #if trace 
                                        System.Diagnostics.Trace.WriteLine(string.Format("{0}{1}", "Failed:".PadRight(45), Identification[i].Pattern));
-#endif
-                                       //just making sure to cleanup before we leave.
-                                       Identification[i].Reset();
-#if trace
-                                       System.Diagnostics.Trace.WriteLine("");
 #endif
                                        return false;
                                }
@@ -873,6 +818,10 @@ namespace System.Web.Configuration.nBrowser
 #if trace 
                                        System.Diagnostics.Trace.WriteLine(string.Format("{0}{1}", "Passed:".PadRight(45), Identification[i].Pattern));
 #endif
+                                       if (m.Groups.Count > 0)
+                                       {
+                                               matchList.Add(m);
+                                       }
                                }
                        }
 #if trace
@@ -880,6 +829,30 @@ namespace System.Web.Configuration.nBrowser
 #endif
                        return true;
                }
+               
+               private bool HaveAdapterTypes = false;
+               private object LookupAdapterTypesLock = new object();
+               private void LookupAdapterTypes()
+               {
+                       if (Adapter == null || HaveAdapterTypes) return;
+                       lock (LookupAdapterTypesLock)
+                       {
+                               if (HaveAdapterTypes) return;
+                               /* Lookup the types and store them for future use */
+                               if (AdapterControlTypes == null)
+                                       AdapterControlTypes = new Type [Adapter.Count];
+                               if (AdapterTypes == null)
+                                       AdapterTypes = new Type [Adapter.Count];
+                               for (int i = 0;i <= Adapter.Count - 1;i++) {
+                                       if (AdapterControlTypes [i] == null)
+                                               AdapterControlTypes [i] = FindType (Adapter.GetKey (i));
+                                       if (AdapterTypes [i] == null)
+                                               AdapterTypes [i] = FindType (Adapter [i]);
+                               }
+                               HaveAdapterTypes = true;
+                       }
+               }
+               
                /// <summary>
                /// 
                /// </summary>
@@ -1032,7 +1005,7 @@ namespace System.Web.Configuration.nBrowser
                        if (n.Capabilities != null)
                        {
                                if (Capabilities == null)
-                                       Capabilities =  new System.Collections.Specialized.NameValueCollection(n.Capabilities.Count);
+                                       Capabilities =  new System.Collections.Specialized.NameValueCollection(n.Capabilities.Count, StringComparer.OrdinalIgnoreCase);
                                foreach (string capName in n.Capabilities)
                                        Capabilities[capName] = n.Capabilities[capName];
                        }