1

I'm in C# land today. I'm trying to write a function which accepts a user agent string and returns an object that gives me at least the browser name and version. So I tried this answer, but it reports Chrome as AppleMAC-Safari 5.0. That's not really acceptable to misreport the most popular browser.

I have access to browscap.ini or XML or JSON. It seems like I have to do it manually but the regex in those files isn't compatible with the regex in C#. This is kind of a nightmare.

I'm using Visual Studio and .Net 3.5.

Community
  • 1
  • 1
Andrew
  • 1,571
  • 17
  • 31
  • 1
    The user agent string for Chrome 40.0.2214.94 m is `Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.94 Safari/537.36`. It contains Chrome/ so you can Regex that. Inspect the user agent strings of the other browsers you want to support and do something similar. Is there something business-critical to detecting which browser the user's using? – adamdc78 Feb 03 '15 at 20:28
  • @adamdc78 I wouldn't say it's business-critical. I'm simply recording what browsers users log in with. I got it working in PHP in a matter of minutes, but it seems to be impossible in C#. It might even be easier to use C# to call a simple PHP script at this point ... – Andrew Feb 03 '15 at 20:52

1 Answers1

2

I'm so glad you asked this question! It has been bothering me forever. Basically what you have to do is get and parse one of the browscap files. I used the XML file. From there, you have to check each regex pattern against your user agent string. In the XML file, that is the "name" attribute of each browscapitem.

However, the patterns in the file have to be converted to real regex so C# will understand them. This is the main method required for your problem. Everything else is just a matter of parsing the different file types.

(I used this code to see what others did to make it work in PHP.)

public static Boolean BrowserPatternMatches(string pattern, string input)
{
    string patternConverted = "^" + pattern
                .Replace("\\", "\\\\")
                .Replace(".", "\\.")
                .Replace("?", ".")
                .Replace("*", ".*")
                .Replace("$", "\\$")
                .Replace("[", "\\[")
                .Replace("]", "\\]")
                .Replace("|", "\\|")
                .Replace("(", "\\(")
                .Replace(")", "\\)")
                .Replace("+", "\\+")
                .Replace("{", "\\{")
                .Replace("}", "\\}")
                .Replace("%", "\\%")
                + "$";
    Regex regex = new Regex(patternConverted);
    return regex.IsMatch(input);
}

This is the meat of your problem. The rest is a matter of parsing the XML and getting those values. This is NOT my area of expertise, so I just did enough to get it functional. In my class I have:

private Dictionary<string, Dictionary<string, string>> dic = new Dictionary<string, Dictionary<string, string>>();

private void FillDictionary()
{
    if (this.dic.Count == 0)
    {
        XmlTextReader reader = new XmlTextReader("browscap.xml");

        while (reader.Read())
        {
            if (reader.Name == "browscapitem")
            {
                string pattern = reader.GetAttribute("name");
                if (pattern != null)
                {
                    if (!this.dic.ContainsKey(pattern))
                    {
                        Dictionary<string, string> properties = new Dictionary<string, string>();
                        while (reader.Read())
                        {
                            if (reader.Name == "browscapitem")
                            {
                                break;
                            }
                            if (reader.GetAttribute("name") != null)
                            {
                                properties.Add(reader.GetAttribute("name").ToLower(), reader.GetAttribute("value"));
                            }
                        }
                        this.dic.Add(pattern, properties);
                    }
                }
            }
        }
    }
}

The rest is just some trickery to fill in the "parent" properties. So once you find your match, you have to go back and find its parent, and its parent's parent, etc.

private Dictionary<string, string> GetBrowserProps(string parentId)
{
    return this.dic[parentId];
}

public Dictionary<string, string> GetBrowserObject(string uaString)
{
    this.FillDictionary();

    bool found = false;
    string foundKey = "";

    foreach (string pattern in this.dic.Keys)
    {
        if (!found)
        {
            found = RecordBrowsers.BrowserPatternMatches(pattern, uaString);
            if (found) { foundKey = pattern; break; }
        }
    }

    Dictionary<string, string> browserProps = new Dictionary<string, string>();
    if (foundKey != "")
    {
        browserProps = this.GetBrowserProps(foundKey);
        Dictionary<string, string> current = this.GetBrowserProps(foundKey);
        bool cont = current.ContainsKey("parent");
        while (cont)
        {
            Dictionary<string, string> parent = this.GetBrowserProps(current["parent"]);
            foreach (string s in parent.Keys)
            {
                if (!browserProps.ContainsKey(s))
                {
                    browserProps.Add(s, parent[s]);
                }
            }
            current = parent;
            cont = current.ContainsKey("parent");
        }
    }

    return browserProps;
}

Here are my tests for good measure:

Console.WriteLine("RecordBrowser started");

string[] strs = { "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.94 Safari/537.36", 
                    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0",
                    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.94 Safari/537.36 OPR/27.0.1689.66",
                    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.94 Safari/537.36",
                    "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.8.1.8pre) Gecko/20070928 Firefox/2.0.0.7 Navigator/9.0RC1",
                    "Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20121202 Firefox/17.0 Iceweasel/17.0.1"};

string[] expectedResults = { "Chrome 40.0", "Firefox 35.0", "Opera 27.0", "Chrome 40.0", "Netscape 9.0", "Iceweasel 17.0" };

for(int i=0; i<strs.Length; i++)
{
    Dictionary<string, string> browserProps = this.GetBrowserObject(strs[i]);
    if (browserProps["comment"] == expectedResults[i])
    {
        Console.WriteLine("test " + i + " passed");
    }
    else
    {
        Console.WriteLine("test " + i + " failed");
    }
    Console.WriteLine("**********************************************************");
}
Console.WriteLine("DONE");
Andrew
  • 1,571
  • 17
  • 31