diff --git a/src/HttpUserAgentParser/HttpUserAgentParser.cs b/src/HttpUserAgentParser/HttpUserAgentParser.cs index e0bc8f7..4711e46 100644 --- a/src/HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/HttpUserAgentParser/HttpUserAgentParser.cs @@ -236,8 +236,6 @@ private static bool TryIndexOf(ReadOnlySpan haystack, ReadOnlySpan n /// private static bool TryExtractVersion(ReadOnlySpan haystack, out Range range) { - range = default; - // Vectorization is used in a optimistic way and specialized to common (trimmed down) user agents. // When the first two char-vectors don't yield any success, we fall back to the scalar path. // This penalized not found versions, but has an advantage for found versions. @@ -256,7 +254,7 @@ private static bool TryExtractVersion(ReadOnlySpan haystack, out Range ran if (between0and9 == Vector256.Zero) { - goto Scalar; + return TryExtractVersionScalar(haystack, out range); } uint bitMask = between0and9.ExtractMostSignificantBits(); @@ -269,12 +267,17 @@ private static bool TryExtractVersion(ReadOnlySpan haystack, out Range ran if (byteMask == Vector256.Zero) { - goto Scalar; + return TryExtractVersionScalar(haystack, out range); } bitMask = byteMask.ExtractMostSignificantBits(); bitMask >>= start; + if (bitMask == 0) + { + return TryExtractVersionScalar(haystack, out range); + } + idx = start + (int)uint.TrailingZeroCount(bitMask); Debug.Assert(idx is >= 0 and <= 32); int end = idx; @@ -291,7 +294,7 @@ private static bool TryExtractVersion(ReadOnlySpan haystack, out Range ran if (between0and9 == Vector128.Zero) { - goto Scalar; + return TryExtractVersionScalar(haystack, out range); } uint bitMask = between0and9.ExtractMostSignificantBits(); @@ -304,12 +307,17 @@ private static bool TryExtractVersion(ReadOnlySpan haystack, out Range ran if (byteMask == Vector128.Zero) { - goto Scalar; + return TryExtractVersionScalar(haystack, out range); } bitMask = byteMask.ExtractMostSignificantBits(); bitMask >>= start; + if (bitMask == 0) + { + return TryExtractVersionScalar(haystack, out range); + } + idx = start + (int)uint.TrailingZeroCount(bitMask); Debug.Assert(idx is >= 0 and <= 16); int end = idx; @@ -318,53 +326,57 @@ private static bool TryExtractVersion(ReadOnlySpan haystack, out Range ran return true; } - Scalar: - { - // Limit search window to avoid scanning entire UA string unnecessarily - const int Windows = 128; - if (haystack.Length > Windows) - { - haystack = haystack.Slice(0, Windows); - } + return TryExtractVersionScalar(haystack, out range); + } - int start = -1; - int i = 0; + private static bool TryExtractVersionScalar(ReadOnlySpan haystack, out Range range) + { + range = default; - for (; i < haystack.Length; ++i) - { - char c = haystack[i]; - if (char.IsBetween(c, '0', '9')) - { - start = i; - break; - } - } + // Limit search window to avoid scanning entire UA string unnecessarily + const int Windows = 128; + if (haystack.Length > Windows) + { + haystack = haystack.Slice(0, Windows); + } - if (start < 0) - { - // No digit found => no version - return false; - } + int start = -1; + int i = 0; - haystack = haystack.Slice(i + 1); - for (i = 0; i < haystack.Length; ++i) + for (; i < haystack.Length; ++i) + { + char c = haystack[i]; + if (char.IsBetween(c, '0', '9')) { - char c = haystack[i]; - if (!(char.IsBetween(c, '0', '9') || c == '.')) - { - break; - } + start = i; + break; } + } - i += start + 1; // shift back the previous domain + if (start < 0) + { + // No digit found => no version + return false; + } - if (i == start) + haystack = haystack.Slice(i + 1); + for (i = 0; i < haystack.Length; ++i) + { + char c = haystack[i]; + if (!(char.IsBetween(c, '0', '9') || c == '.')) { - return false; + break; } + } - range = new Range(start, i); - return true; + i += start + 1; // shift back the previous domain + + if (i == start) + { + return false; } + + range = new Range(start, i); + return true; } } diff --git a/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs b/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs index 972f42a..608e9f6 100644 --- a/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs +++ b/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs @@ -54,6 +54,10 @@ public class HttpUserAgentParserTests [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 OPR/76.0.4017.107", "Opera", "76.0.4017.107", "Windows 10", HttpUserAgentPlatformType.Windows, null)] [InlineData("Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 OPR/76.0.4017.107", "Opera", "76.0.4017.107", "Mac OS X", HttpUserAgentPlatformType.MacOS, null)] [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 OPR/76.0.4017.107", "Opera", "76.0.4017.107", "Linux", HttpUserAgentPlatformType.Linux, null)] + [InlineData("Mozilla/5.0 (Linux; U; Android 13; Hisense U53 Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/145.0.7632.79 Mobile Safari/537.36 OPR/98.0.2254.81553", "Opera", "98.0.2254.81553", "Android", HttpUserAgentPlatformType.Android, "Android")] + [InlineData("Mozilla/5.0 (Linux; Android 10; MED-LX9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.192 Mobile Safari/537.36 OPR/74.0.3922.71152", "Opera", "74.0.3922.71152", "Android", HttpUserAgentPlatformType.Android, "Android")] + [InlineData("Mozilla/5.0 (Linux; U; Android 13; Infinix X6526 Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/145.0.7632.79 Mobile Safari/537.36 OPR/97.1.2254.80849", "Opera", "97.1.2254.80849", "Android", HttpUserAgentPlatformType.Android, "Android")] + [InlineData("Mozilla/5.0 (Linux; U; Android 13; Infinix X6836 Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/145.0.7632.120 Mobile Safari/537.36 OPR/98.0.2254.81553", "Opera", "98.0.2254.81553", "Android", HttpUserAgentPlatformType.Android, "Android")] public void BrowserTests(string ua, string name, string version, string platformName, HttpUserAgentPlatformType platformType, string? mobileDeviceType) { HttpUserAgentInformation uaInfo = HttpUserAgentInformation.Parse(ua);