Skip to content

Commit 5c04ac1

Browse files
authored
test(tests): add user agent test cases for Opera on Android (#96)
1 parent f2aa59e commit 5c04ac1

File tree

2 files changed

+58
-42
lines changed

2 files changed

+58
-42
lines changed

src/HttpUserAgentParser/HttpUserAgentParser.cs

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,6 @@ private static bool TryIndexOf(ReadOnlySpan<char> haystack, ReadOnlySpan<char> n
236236
/// </summary>
237237
private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range range)
238238
{
239-
range = default;
240-
241239
// Vectorization is used in a optimistic way and specialized to common (trimmed down) user agents.
242240
// When the first two char-vectors don't yield any success, we fall back to the scalar path.
243241
// This penalized not found versions, but has an advantage for found versions.
@@ -256,7 +254,7 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran
256254

257255
if (between0and9 == Vector256<byte>.Zero)
258256
{
259-
goto Scalar;
257+
return TryExtractVersionScalar(haystack, out range);
260258
}
261259

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

270268
if (byteMask == Vector256<byte>.Zero)
271269
{
272-
goto Scalar;
270+
return TryExtractVersionScalar(haystack, out range);
273271
}
274272

275273
bitMask = byteMask.ExtractMostSignificantBits();
276274
bitMask >>= start;
277275

276+
if (bitMask == 0)
277+
{
278+
return TryExtractVersionScalar(haystack, out range);
279+
}
280+
278281
idx = start + (int)uint.TrailingZeroCount(bitMask);
279282
Debug.Assert(idx is >= 0 and <= 32);
280283
int end = idx;
@@ -291,7 +294,7 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran
291294

292295
if (between0and9 == Vector128<byte>.Zero)
293296
{
294-
goto Scalar;
297+
return TryExtractVersionScalar(haystack, out range);
295298
}
296299

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

305308
if (byteMask == Vector128<byte>.Zero)
306309
{
307-
goto Scalar;
310+
return TryExtractVersionScalar(haystack, out range);
308311
}
309312

310313
bitMask = byteMask.ExtractMostSignificantBits();
311314
bitMask >>= start;
312315

316+
if (bitMask == 0)
317+
{
318+
return TryExtractVersionScalar(haystack, out range);
319+
}
320+
313321
idx = start + (int)uint.TrailingZeroCount(bitMask);
314322
Debug.Assert(idx is >= 0 and <= 16);
315323
int end = idx;
@@ -318,53 +326,57 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran
318326
return true;
319327
}
320328

321-
Scalar:
322-
{
323-
// Limit search window to avoid scanning entire UA string unnecessarily
324-
const int Windows = 128;
325-
if (haystack.Length > Windows)
326-
{
327-
haystack = haystack.Slice(0, Windows);
328-
}
329+
return TryExtractVersionScalar(haystack, out range);
330+
}
329331

330-
int start = -1;
331-
int i = 0;
332+
private static bool TryExtractVersionScalar(ReadOnlySpan<char> haystack, out Range range)
333+
{
334+
range = default;
332335

333-
for (; i < haystack.Length; ++i)
334-
{
335-
char c = haystack[i];
336-
if (char.IsBetween(c, '0', '9'))
337-
{
338-
start = i;
339-
break;
340-
}
341-
}
336+
// Limit search window to avoid scanning entire UA string unnecessarily
337+
const int Windows = 128;
338+
if (haystack.Length > Windows)
339+
{
340+
haystack = haystack.Slice(0, Windows);
341+
}
342342

343-
if (start < 0)
344-
{
345-
// No digit found => no version
346-
return false;
347-
}
343+
int start = -1;
344+
int i = 0;
348345

349-
haystack = haystack.Slice(i + 1);
350-
for (i = 0; i < haystack.Length; ++i)
346+
for (; i < haystack.Length; ++i)
347+
{
348+
char c = haystack[i];
349+
if (char.IsBetween(c, '0', '9'))
351350
{
352-
char c = haystack[i];
353-
if (!(char.IsBetween(c, '0', '9') || c == '.'))
354-
{
355-
break;
356-
}
351+
start = i;
352+
break;
357353
}
354+
}
358355

359-
i += start + 1; // shift back the previous domain
356+
if (start < 0)
357+
{
358+
// No digit found => no version
359+
return false;
360+
}
360361

361-
if (i == start)
362+
haystack = haystack.Slice(i + 1);
363+
for (i = 0; i < haystack.Length; ++i)
364+
{
365+
char c = haystack[i];
366+
if (!(char.IsBetween(c, '0', '9') || c == '.'))
362367
{
363-
return false;
368+
break;
364369
}
370+
}
365371

366-
range = new Range(start, i);
367-
return true;
372+
i += start + 1; // shift back the previous domain
373+
374+
if (i == start)
375+
{
376+
return false;
368377
}
378+
379+
range = new Range(start, i);
380+
return true;
369381
}
370382
}

tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public class HttpUserAgentParserTests
5454
[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)]
5555
[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)]
5656
[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)]
57+
[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")]
58+
[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")]
59+
[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")]
60+
[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")]
5761
public void BrowserTests(string ua, string name, string version, string platformName, HttpUserAgentPlatformType platformType, string? mobileDeviceType)
5862
{
5963
HttpUserAgentInformation uaInfo = HttpUserAgentInformation.Parse(ua);

0 commit comments

Comments
 (0)