Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 54 additions & 42 deletions src/HttpUserAgentParser/HttpUserAgentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,6 @@ private static bool TryIndexOf(ReadOnlySpan<char> haystack, ReadOnlySpan<char> n
/// </summary>
private static bool TryExtractVersion(ReadOnlySpan<char> 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.
Expand All @@ -256,7 +254,7 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran

if (between0and9 == Vector256<byte>.Zero)
{
goto Scalar;
return TryExtractVersionScalar(haystack, out range);
}

uint bitMask = between0and9.ExtractMostSignificantBits();
Expand All @@ -269,12 +267,17 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran

if (byteMask == Vector256<byte>.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;
Expand All @@ -291,7 +294,7 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran

if (between0and9 == Vector128<byte>.Zero)
{
goto Scalar;
return TryExtractVersionScalar(haystack, out range);
}

uint bitMask = between0and9.ExtractMostSignificantBits();
Expand All @@ -304,12 +307,17 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran

if (byteMask == Vector128<byte>.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;
Expand All @@ -318,53 +326,57 @@ private static bool TryExtractVersion(ReadOnlySpan<char> 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<char> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down