-
Notifications
You must be signed in to change notification settings - Fork 9
feat(parser): enhance user agent parsing logic and add tests for inva… #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
99d5f6d
5f56311
9c7ad02
af6d579
942b99b
f8d2e26
a54935d
bb953fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,6 @@ namespace MyCSharp.HttpUserAgentParser; | |
| /// Parser logic for user agents | ||
| /// </summary> | ||
| public static class HttpUserAgentParser | ||
|
|
||
| { | ||
| /// <summary> | ||
| /// Parses given <param name="userAgent">user agent</param> | ||
|
|
@@ -78,6 +77,7 @@ public static bool TryGetPlatform(string userAgent, [NotNullWhen(true)] out Http | |
| public static (string Name, string? Version)? GetBrowser(string userAgent) | ||
| { | ||
| ReadOnlySpan<char> ua = userAgent.AsSpan(); | ||
|
|
||
| foreach ((string Name, string DetectToken, string? VersionToken) browserRule in HttpUserAgentStatics.s_browserRules) | ||
| { | ||
| if (!TryIndexOf(ua, browserRule.DetectToken, out int detectIndex)) | ||
|
|
@@ -86,7 +86,20 @@ public static (string Name, string? Version)? GetBrowser(string userAgent) | |
| } | ||
|
|
||
| // Version token may differ (e.g., Safari uses "Version/") | ||
| int versionSearchStart = detectIndex; | ||
| // Keep full span immutable across iterations | ||
|
|
||
| ReadOnlySpan<char> uaFull = userAgent.AsSpan(); | ||
| int versionSearchStart; | ||
| // For rules without a specific version token, ensure pattern Token/<digits> | ||
| if (string.IsNullOrEmpty(browserRule.VersionToken)) | ||
| { | ||
| int afterDetect = detectIndex + browserRule.DetectToken.Length; | ||
| if (afterDetect >= uaFull.Length || uaFull[afterDetect] != '/') | ||
| { | ||
| // Likely a misspelling or partial token (e.g., Edgg, Oprea, Chromee) | ||
| continue; | ||
| } | ||
| } | ||
| if (!string.IsNullOrEmpty(browserRule.VersionToken)) | ||
| { | ||
| if (TryIndexOf(ua, browserRule.VersionToken!, out int vtIndex)) | ||
|
|
@@ -104,14 +117,22 @@ public static (string Name, string? Version)? GetBrowser(string userAgent) | |
| versionSearchStart = detectIndex + browserRule.DetectToken.Length; | ||
| } | ||
|
|
||
| string? version = null; | ||
| ua = ua.Slice(versionSearchStart); | ||
| if (TryExtractVersion(ua, out Range range)) | ||
| // Work on a local slice to avoid mutating the main span for following rules | ||
| if (versionSearchStart < 0 || versionSearchStart >= uaFull.Length) | ||
| { | ||
| // Nothing to search; try next rule | ||
| continue; | ||
| } | ||
|
|
||
| ReadOnlySpan<char> search = uaFull.Slice(versionSearchStart); | ||
| if (TryExtractVersion(search, out Range range)) | ||
| { | ||
| version = ua[range].ToString(); | ||
| string? version = search[range].ToString(); | ||
| return (browserRule.Name, version); | ||
| } | ||
|
|
||
| return (browserRule.Name, version); | ||
| // If we didn't find a version for this rule, try next rule | ||
| continue; | ||
BenjaminAbt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| return null; | ||
|
|
@@ -198,39 +219,43 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran | |
|
|
||
| // Limit search window to avoid scanning entire UA string unnecessarily | ||
| const int Window = 128; | ||
| if (haystack.Length >= Window) | ||
| if (haystack.Length > Window) | ||
| { | ||
| haystack = haystack.Slice(0, Window); | ||
| } | ||
|
|
||
| int i = 0; | ||
| for (; i < haystack.Length; ++i) | ||
| // Find first digit | ||
| int start = -1; | ||
| for (int i = 0; i < haystack.Length; i++) | ||
| { | ||
| char c = haystack[i]; | ||
| if (char.IsBetween(c, '0', '9')) | ||
| if (c >= '0' && c <= '9') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is two comparisons now, instead of just one. Either use the Only the JIT for .NET 10+ is able to optimize that to one comparison (by using the |
||
| { | ||
| start = i; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| int s = i; | ||
| haystack = haystack.Slice(i + 1); | ||
| for (i = 0; i < haystack.Length; ++i) | ||
| if (start < 0) | ||
| { | ||
| char c = haystack[i]; | ||
| if (!(char.IsBetween(c, '0', '9') || c == '.')) | ||
| { | ||
| break; | ||
| } | ||
| // No digit found => no version | ||
| return false; | ||
| } | ||
| i += s + 1; // shift back the previous domain | ||
|
|
||
| if (i == s) | ||
| // Consume digits and dots after first digit | ||
| int end = start + 1; | ||
| while (end < haystack.Length) | ||
| { | ||
| return false; | ||
| char c = haystack[end]; | ||
| if (!((c >= '0' && c <= '9') || c == '.')) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
| { | ||
| break; | ||
| } | ||
| end++; | ||
| } | ||
|
|
||
| range = new Range(s, i); | ||
| // Create exclusive end range | ||
| range = new Range(start, end); | ||
| return true; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.