Reputation: 1573
I have a large list of User-agent strings. For the analytic panel I need to parse them and split to:
Is there is a ready solution or code to do this? Like there: http://useragentstring.com/
Upvotes: 8
Views: 12427
Reputation: 138950
Here is a simple parser. It only parses components of the user agent, doesn't match with existing OS or browser names, etc.:
var ua = UserAgent.Parse("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36");
...
public class UserAgent : IEquatable<UserAgent>
{
public UserAgent()
{
System = new HashSet<string>();
Details = new HashSet<UserAgentPart>();
Extensions = new HashSet<UserAgentPart>();
}
// from here https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
public virtual UserAgentPart Product { get; set; }
public virtual ISet<string> System { get; }
public virtual UserAgentPart Platform { get; set; }
public virtual ISet<UserAgentPart> Details { get; }
public virtual ISet<UserAgentPart> Extensions { get; }
public override int GetHashCode() => base.GetHashCode();
public override bool Equals(object obj) => Equals(obj as UserAgentPart);
public bool Equals(UserAgent other) => !ReferenceEquals(other, null) &&
Equals(Product, other.Product) &&
Equals(System, other.System) &&
Equals(Platform, other.Platform) &&
Equals(Details, other.Details) &&
Equals(Extensions, other.Extensions);
public static bool operator !=(UserAgent lhs, UserAgent rhs) => !(lhs == rhs);
public static bool operator ==(UserAgent lhs, UserAgent rhs)
{
if (lhs is null)
{
if (rhs is null)
return true;
return false;
}
return lhs.Equals(rhs);
}
public override string ToString()
{
var list = new List<string>();
if (Product != null)
{
list.Add(Product.ToString());
}
if (System.Count > 0)
{
list.Add("(" + string.Join("; ", System) + ")");
}
if (Platform != null)
{
list.Add(Platform.ToString());
}
if (Details.Count > 0)
{
list.Add("(" + string.Join("; ", Details) + ")");
}
if (Extensions.Count > 0)
{
list.Add(string.Join(" ", Extensions));
}
return string.Join(" ", list);
}
private static bool Equals(ISet<UserAgentPart> parts1, ISet<UserAgentPart> parts2)
{
if (parts1.Count != parts2.Count)
return false;
foreach (var kv in parts1)
{
if (!parts2.Contains(kv))
return false;
}
return true;
}
internal static string Nullify(string text)
{
if (string.IsNullOrWhiteSpace(text))
return null;
text = text.Trim();
return text.Length == 0 ? null : text;
}
public static UserAgent Parse(string text)
{
if (string.IsNullOrWhiteSpace(text))
return null;
UserAgentPart part;
var space = text.IndexOf(' ');
if (space < 0)
{
part = UserAgentPart.Parse(text);
if (part == null)
return null;
return new UserAgent { Product = part };
}
var product = text.Substring(0, space);
part = UserAgentPart.Parse(product);
if (part == null)
return null;
var ua = new UserAgent { Product = part };
var offset = space;
var startParen = text.IndexOf('(', space + 1);
if (startParen >= 0)
{
var endParen = text.IndexOf(')', startParen + 1);
if (endParen < 0) // syntax error
return ua;
var system = Nullify(text.Substring(startParen + 1, endParen - startParen - 1));
if (system != null)
{
foreach (var sys in system.Split(';'))
{
var syst = Nullify(sys);
if (syst != null)
{
ua.System.Add(syst);
}
}
}
offset = endParen;
}
var platform = text.IndexOf(' ', offset + 1);
if (platform < 0)
{
ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(offset + 1)));
return ua;
}
startParen = text.IndexOf('(', platform + 1);
if (startParen >= 0)
{
ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(platform + 1)));
var endParen = text.IndexOf(')', startParen + 1);
if (endParen < 0) // syntax error
return ua;
var details = Nullify(text.Substring(startParen + 1, endParen - startParen - 1));
if (details != null)
{
foreach (var det in details.Split(';'))
{
var dett = UserAgentPart.Parse(Nullify(det));
if (dett != null)
{
ua.Details.Add(dett);
}
}
}
offset = endParen;
}
else
{
space = text.IndexOf(' ', platform + 1);
if (space < 0)
{
ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(platform + 1)));
return ua;
}
ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(platform + 1, space - platform - 1)));
offset = space;
}
foreach (var ext in text.Substring(offset + 1).Split(' '))
{
var extt = UserAgentPart.Parse(Nullify(ext));
if (extt != null)
{
ua.Extensions.Add(extt);
}
}
return ua;
}
}
public class UserAgentPart : IEquatable<UserAgentPart>
{
public UserAgentPart(string name, string version = null)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
Name = name;
Version = version;
}
public string Name { get; }
public string Version { get; }
public override int GetHashCode()
{
var code = Name.GetHashCode();
if (Version != null)
{
code ^= Version.GetHashCode();
}
return code;
}
public override string ToString() => Version != null ? Name + "/" + Version : Name;
public override bool Equals(object obj) => Equals(obj as UserAgentPart);
public bool Equals(UserAgentPart other) => !ReferenceEquals(other, null) && Name == other.Name && Version == other.Version;
public static bool operator !=(UserAgentPart lhs, UserAgentPart rhs) => !(lhs == rhs);
public static bool operator ==(UserAgentPart lhs, UserAgentPart rhs)
{
if (lhs is null)
{
if (rhs is null)
return true;
return false;
}
return lhs.Equals(rhs);
}
public static UserAgentPart Parse(string text)
{
if (string.IsNullOrWhiteSpace(text))
return null;
var space = text.IndexOf('/');
if (space < 0)
return new UserAgentPart(UserAgent.Nullify(text));
var name = UserAgent.Nullify(text.Substring(0, space));
if (name == null)
return null;
var version = UserAgent.Nullify(text.Substring(space + 1));
var i = 0;
for (; i < version.Length; i++)
{
var c = version[i];
if (c != '.' && !char.IsDigit(c))
break;
}
if (i < (version.Length - 1))
{
version = version.Substring(0, i);
}
return new UserAgentPart(name, UserAgent.Nullify(version));
}
}
Upvotes: 0
Reputation: 192
I recommend installing DannyBoyNg's UserAgentParser to anyone struggling with this issue, as it can easily be downloaded and installed through NuGet. Note that this software is now considered legacy.
So for the information you're trying to get, here is what you would do:
using UserAgentParser;
...
var ua = UserAgent.Parse(Request.UserAgent.ToString());
string b = ua.Browser.ToString() + ", " + ua.BrowserVersion.ToString();
string p = ua.Platform.ToString();
Sorry, there isn't any support for exact versions here, so if you want to differentiate between Windows 10 version 1607 and version 1903, you're out of luck.
Another thing you'll need to do is add an extra line to the assemblies tag in Web.config
<add assembly="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"/>
I wish you didn't have to, but I can't see any workaround for this at the moment.
Upvotes: 4
Reputation: 1904
Install OrbintSoft.Yauaa.NetStandard package from .nuget
1) Create this class to parse your UserAgent:
public static class YauaaSingleton
{
private static UserAgentAnalyzer.UserAgentAnalyzerBuilder Builder { get; }
private static readonly Lazy<UserAgentAnalyzer> analyzer = new Lazy<UserAgentAnalyzer>(() => Builder.Build());
public static UserAgentAnalyzer Analyzer
{
get
{
return analyzer.Value;
}
}
static YauaaSingleton()
{
Builder = UserAgentAnalyzer.NewBuilder();
Builder.DropTests();
Builder.DelayInitialization();
Builder.WithCache(100);
Builder.HideMatcherLoadStats();
Builder.WithAllFields();
}
}
2) Process your user agents like this:
public void ProcessUserAgents(string[] userAgents)
{
foreach (var userAgent in userAgents)
{
var parserdUA = YauaaSingleton.Analyzer.Parse(userAgent);
var browserNameVersion = parserdUA.GetValue(UserAgent.AGENT_NAME_VERSION);
//There is no a single field for platform name/version:
var osNameVersion = parserdUA.GetValue(UserAgent.OPERATING_SYSTEM_NAME_VERSION);
var deviceName = parserdUA.GetValue(UserAgent.DEVICE_NAME);
var deviceVersion = parserdUA.GetValue(UserAgent.DEVICE_VERSION);
//If you want all fields:
var fieldNames = ua.GetAvailableFieldNames();
foreach (var name in fieldNames)
{
var field = ua.Get(name);
Console.WriteLine($"name: {name}, value: {field.GetValue()}, confidence: {field.GetConfidence()}"});
}
}
}
3) Example of parsing:
UserAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 OPR/67.0.3575.137'
Upvotes: 1
Reputation: 61409
I know this is awfull but I could not find anything better,
public static class UserAgentParser
{
/// <summary>
/// Extracts human readible Operating system name.
/// </summary>
/// <param name="userAgent">User Agent string from Request.</param>
/// <returns>Human readible Operating system name.</returns>
public static string GetOperatingSystem(string userAgent)
{
var clientOsName = string.Empty;
if (userAgent.Contains("Windows 98"))
clientOsName = "Windows 98";
else if (userAgent.Contains("Windows NT 5.0"))
clientOsName = "Windows 2000";
else if (userAgent.Contains("Windows NT 5.1"))
clientOsName = "Windows XP";
else if (userAgent.Contains("Windows NT 6.0"))
clientOsName = "Windows Vista";
else if (userAgent.Contains("Windows NT 6.1"))
clientOsName = "Windows 7";
else if (userAgent.Contains("Windows NT 6.2"))
clientOsName = "Windows 8";
else if (userAgent.Contains("Windows"))
{
clientOsName = GetOsVersion(userAgent, "Windows");
}
else if (userAgent.Contains("Android"))
{
clientOsName = GetOsVersion(userAgent, "Android");
}
else if (userAgent.Contains("Linux"))
{
clientOsName = GetOsVersion(userAgent, "Linux");
}
else if (userAgent.Contains("iPhone"))
{
clientOsName = GetOsVersion(userAgent, "iPhone");
}
else if (userAgent.Contains("iPad"))
{
clientOsName = GetOsVersion(userAgent, "iPad");
}
else if (userAgent.Contains("Macintosh"))
{
clientOsName = GetOsVersion(userAgent, "Macintosh");
}
else
{
clientOsName = "Unknown OS";
}
return clientOsName;
}
private static string GetOsVersion(string userAgent, string osName)
{
if (userAgent.Split(new[] {osName}, StringSplitOptions.None)[1].Split(new[]{';',')'}).Length != 0)
{
return string.Format("{0}{1}", osName,userAgent.Split(new[] { osName }, StringSplitOptions.None)[1].Split(new[] { ';', ')' })[0]);
}
return osName;
}
}
[TestFixture]
public class UserAgentParserTest
{
public IEnumerable<TestCaseData> UserAgentStringTestData()
{
yield return new TestCaseData("Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3", "iPhone");
yield return new TestCaseData("Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3", "iPad");
yield return new TestCaseData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.04 Chromium/17.0.963.56 Chrome/17.0.963.56 Safari/535.11", "Linux x86_64");
yield return new TestCaseData("Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Linux i686");
yield return new TestCaseData("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Windows 7");
yield return new TestCaseData("Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Windows Vista");
yield return
new TestCaseData(
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", "Windows 8");
yield return
new TestCaseData(
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Windows XP");
yield return
new TestCaseData(
"Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.198 Safari/532.0",
"Windows 2000");
yield return
new TestCaseData(
"Mozilla/5.0 (compatible; MSIE 7.0; Windows 98; SpamBlockerUtility 6.3.91; SpamBlockerUtility 6.2.91; .NET CLR 4.1.89;GB)", "Windows 98");
yield return
new TestCaseData(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13",
"Macintosh");
yield return
new TestCaseData(
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Unknown OS");
yield return
new TestCaseData(
"Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
"Android 4.0.3");
yield return
new TestCaseData("Mozilla/1.22 (compatible; MSIE 10.0; Windows 3.1)", "Windows 3.1");
}
[Test]
[TestCaseSource("UserAgentStringTestData")]
public void UserAgentParsesOs(string userAgent, string expectedOs)
{
Assert.AreEqual(expectedOs, UserAgentParser.GetOperatingSystem(userAgent));
}
}
Upvotes: 5