@@ -11,7 +11,6 @@ namespace MyCSharp.HttpUserAgentParser;
1111/// Parser logic for user agents
1212/// </summary>
1313public static class HttpUserAgentParser
14-
1514{
1615 /// <summary>
1716 /// Parses given <param name="userAgent">user agent</param>
@@ -48,7 +47,6 @@ public static HttpUserAgentInformation Parse(string userAgent)
4847 /// </summary>
4948 public static HttpUserAgentPlatformInformation ? GetPlatform ( string userAgent )
5049 {
51- // Fast, allocation-free token scan (keeps public statics untouched)
5250 ReadOnlySpan < char > ua = userAgent . AsSpan ( ) ;
5351 foreach ( ( string Token , string Name , HttpUserAgentPlatformType PlatformType ) platform in HttpUserAgentStatics . s_platformRules )
5452 {
@@ -78,6 +76,7 @@ public static bool TryGetPlatform(string userAgent, [NotNullWhen(true)] out Http
7876 public static ( string Name , string ? Version ) ? GetBrowser ( string userAgent )
7977 {
8078 ReadOnlySpan < char > ua = userAgent . AsSpan ( ) ;
79+
8180 foreach ( ( string Name , string DetectToken , string ? VersionToken ) browserRule in HttpUserAgentStatics . s_browserRules )
8281 {
8382 if ( ! TryIndexOf ( ua , browserRule . DetectToken , out int detectIndex ) )
@@ -86,7 +85,18 @@ public static (string Name, string? Version)? GetBrowser(string userAgent)
8685 }
8786
8887 // Version token may differ (e.g., Safari uses "Version/")
89- int versionSearchStart = detectIndex ;
88+
89+ int versionSearchStart ;
90+ // For rules without a specific version token, ensure pattern Token/<digits>
91+ if ( string . IsNullOrEmpty ( browserRule . VersionToken ) )
92+ {
93+ int afterDetect = detectIndex + browserRule . DetectToken . Length ;
94+ if ( afterDetect >= ua . Length || ua [ afterDetect ] != '/' )
95+ {
96+ // Likely a misspelling or partial token (e.g., Edgg, Oprea, Chromee)
97+ continue ;
98+ }
99+ }
90100 if ( ! string . IsNullOrEmpty ( browserRule . VersionToken ) )
91101 {
92102 if ( TryIndexOf ( ua , browserRule . VersionToken ! , out int vtIndex ) )
@@ -104,14 +114,14 @@ public static (string Name, string? Version)? GetBrowser(string userAgent)
104114 versionSearchStart = detectIndex + browserRule . DetectToken . Length ;
105115 }
106116
107- string ? version = null ;
108- ua = ua . Slice ( versionSearchStart ) ;
109- if ( TryExtractVersion ( ua , out Range range ) )
117+ ReadOnlySpan < char > search = ua . Slice ( versionSearchStart ) ;
118+ if ( TryExtractVersion ( search , out Range range ) )
110119 {
111- version = ua [ range ] . ToString ( ) ;
120+ string ? version = search [ range ] . ToString ( ) ;
121+ return ( browserRule . Name , version ) ;
112122 }
113123
114- return ( browserRule . Name , version ) ;
124+ // If we didn't find a version for this rule, try next rule
115125 }
116126
117127 return null ;
@@ -198,39 +208,43 @@ private static bool TryExtractVersion(ReadOnlySpan<char> haystack, out Range ran
198208
199209 // Limit search window to avoid scanning entire UA string unnecessarily
200210 const int Window = 128 ;
201- if ( haystack . Length >= Window )
211+ if ( haystack . Length > Window )
202212 {
203213 haystack = haystack . Slice ( 0 , Window ) ;
204214 }
205215
206- int i = 0 ;
207- for ( ; i < haystack . Length ; ++ i )
216+ // Find first digit
217+ int start = - 1 ;
218+ for ( int i = 0 ; i < haystack . Length ; i ++ )
208219 {
209220 char c = haystack [ i ] ;
210- if ( char . IsBetween ( c , '0' , '9' ) )
221+ if ( c >= '0' && c <= '9' )
211222 {
223+ start = i ;
212224 break ;
213225 }
214226 }
215227
216- int s = i ;
217- haystack = haystack . Slice ( i + 1 ) ;
218- for ( i = 0 ; i < haystack . Length ; ++ i )
228+ if ( start < 0 )
219229 {
220- char c = haystack [ i ] ;
221- if ( ! ( char . IsBetween ( c , '0' , '9' ) || c == '.' ) )
222- {
223- break ;
224- }
230+ // No digit found => no version
231+ return false ;
225232 }
226- i += s + 1 ; // shift back the previous domain
227233
228- if ( i == s )
234+ // Consume digits and dots after first digit
235+ int end = start + 1 ;
236+ while ( end < haystack . Length )
229237 {
230- return false ;
238+ char c = haystack [ end ] ;
239+ if ( ! ( ( c >= '0' && c <= '9' ) || c == '.' ) )
240+ {
241+ break ;
242+ }
243+ end ++ ;
231244 }
232245
233- range = new Range ( s , i ) ;
246+ // Create exclusive end range
247+ range = new Range ( start , end ) ;
234248 return true ;
235249 }
236250}
0 commit comments