@@ -14,21 +14,23 @@ public static class HttpUserAgentParser
1414
1515{
1616 /// <summary>
17- /// Parses given user agent string without allocating a copy. Prefer this overload to avoid ToString() allocations.
17+ /// Parses given <param name="userAgent"> user agent</param>
1818 /// </summary>
1919 public static HttpUserAgentInformation Parse ( string userAgent )
2020 {
21- ReadOnlySpan < char > span = Cleanup ( userAgent . AsSpan ( ) ) ;
21+ // prepare
22+ userAgent = Cleanup ( userAgent ) ;
2223
23- if ( TryGetRobot ( span , out string ? robotName ) )
24+ // analyze
25+ if ( TryGetRobot ( userAgent , out string ? robotName ) )
2426 {
2527 return HttpUserAgentInformation . CreateForRobot ( userAgent , robotName ) ;
2628 }
2729
28- HttpUserAgentPlatformInformation ? platform = GetPlatform ( span ) ;
29- string ? mobileDeviceType = GetMobileDevice ( span ) ;
30+ HttpUserAgentPlatformInformation ? platform = GetPlatform ( userAgent ) ;
31+ string ? mobileDeviceType = GetMobileDevice ( userAgent ) ;
3032
31- if ( TryGetBrowser ( span , out ( string Name , string ? Version ) ? browser ) )
33+ if ( TryGetBrowser ( userAgent , out ( string Name , string ? Version ) ? browser ) )
3234 {
3335 return HttpUserAgentInformation . CreateForBrowser ( userAgent , platform , browser ? . Name , browser ? . Version , mobileDeviceType ) ;
3436 }
@@ -39,21 +41,23 @@ public static HttpUserAgentInformation Parse(string userAgent)
3941 /// <summary>
4042 /// pre-cleanup of <param name="userAgent">user agent</param>
4143 /// </summary>
42- public static ReadOnlySpan < char > Cleanup ( ReadOnlySpan < char > userAgent ) => userAgent . Trim ( ) ;
44+ public static string Cleanup ( string userAgent ) => userAgent . Trim ( ) ;
4345
4446 /// <summary>
4547 /// returns the platform or null
4648 /// </summary>
47- public static HttpUserAgentPlatformInformation ? GetPlatform ( ReadOnlySpan < char > userAgent )
49+ public static HttpUserAgentPlatformInformation ? GetPlatform ( string userAgent )
4850 {
49- foreach ( ( string Token , string Name , HttpUserAgentPlatformType PlatformType ) p in HttpUserAgentStatics . s_platformRules )
51+ // Fast, allocation-free token scan (keeps public statics untouched)
52+ ReadOnlySpan < char > ua = userAgent . AsSpan ( ) ;
53+ foreach ( ( string Token , string Name , HttpUserAgentPlatformType PlatformType ) p in HttpUserAgentStatics . s_platformRules )
5054 {
51- if ( ContainsIgnoreCase ( userAgent , p . Token ) )
55+ if ( ContainsIgnoreCase ( ua , p . Token ) )
5256 {
5357 return new HttpUserAgentPlatformInformation (
54- regex : HttpUserAgentStatics . GetPlatformRegexForToken ( p . Token ) ,
55- name : p . Name ,
56- platformType : p . PlatformType ) ;
58+ regex : HttpUserAgentStatics . GetPlatformRegexForToken ( p . Token ) ,
59+ name : p . Name ,
60+ platformType : p . PlatformType ) ;
5761 }
5862 }
5963
@@ -63,7 +67,7 @@ public static HttpUserAgentInformation Parse(string userAgent)
6367 /// <summary>
6468 /// returns true if platform was found
6569 /// </summary>
66- public static bool TryGetPlatform ( ReadOnlySpan < char > userAgent , [ NotNullWhen ( true ) ] out HttpUserAgentPlatformInformation ? platform )
70+ public static bool TryGetPlatform ( string userAgent , [ NotNullWhen ( true ) ] out HttpUserAgentPlatformInformation ? platform )
6771 {
6872 platform = GetPlatform ( userAgent ) ;
6973 return platform is not null ;
@@ -72,11 +76,12 @@ public static bool TryGetPlatform(ReadOnlySpan<char> userAgent, [NotNullWhen(tru
7276 /// <summary>
7377 /// returns the browser or null
7478 /// </summary>
75- public static ( string Name , string ? Version ) ? GetBrowser ( ReadOnlySpan < char > userAgent )
79+ public static ( string Name , string ? Version ) ? GetBrowser ( string userAgent )
7680 {
77- foreach ( ( string Name , string DetectToken , string ? VersionToken ) rule in HttpUserAgentStatics . s_browserRules )
81+ ReadOnlySpan < char > ua = userAgent . AsSpan ( ) ;
82+ foreach ( ( string Name , string DetectToken , string ? VersionToken ) rule in HttpUserAgentStatics . s_browserRules )
7883 {
79- if ( ! TryIndexOf ( userAgent , rule . DetectToken , out int detectIndex ) )
84+ if ( ! TryIndexOf ( ua , rule . DetectToken , out int detectIndex ) )
8085 {
8186 continue ;
8287 }
@@ -85,7 +90,7 @@ public static (string Name, string? Version)? GetBrowser(ReadOnlySpan<char> user
8590 int versionSearchStart = detectIndex ;
8691 if ( ! string . IsNullOrEmpty ( rule . VersionToken ) )
8792 {
88- if ( TryIndexOf ( userAgent , rule . VersionToken ! , out int vtIndex ) )
93+ if ( TryIndexOf ( ua , rule . VersionToken ! , out int vtIndex ) )
8994 {
9095 versionSearchStart = vtIndex + rule . VersionToken ! . Length ;
9196 }
@@ -101,10 +106,9 @@ public static (string Name, string? Version)? GetBrowser(ReadOnlySpan<char> user
101106 }
102107
103108 string ? version = null ;
104- if ( TryExtractVersion ( userAgent , versionSearchStart , out Range range ) )
109+ if ( TryExtractVersion ( ua , versionSearchStart , out Range range ) )
105110 {
106- // Only allocate the version substring, not the whole user agent
107- version = userAgent [ range ] . ToString ( ) ;
111+ version = userAgent . AsSpan ( range . Start . Value , range . End . Value - range . Start . Value ) . ToString ( ) ;
108112 }
109113
110114 return ( rule . Name , version ) ;
@@ -116,7 +120,7 @@ public static (string Name, string? Version)? GetBrowser(ReadOnlySpan<char> user
116120 /// <summary>
117121 /// returns true if browser was found
118122 /// </summary>
119- public static bool TryGetBrowser ( ReadOnlySpan < char > userAgent , [ NotNullWhen ( true ) ] out ( string Name , string ? Version ) ? browser )
123+ public static bool TryGetBrowser ( string userAgent , [ NotNullWhen ( true ) ] out ( string Name , string ? Version ) ? browser )
120124 {
121125 browser = GetBrowser ( userAgent ) ;
122126 return browser is not null ;
@@ -125,7 +129,7 @@ public static bool TryGetBrowser(ReadOnlySpan<char> userAgent, [NotNullWhen(true
125129 /// <summary>
126130 /// returns the robot or null
127131 /// </summary>
128- public static string ? GetRobot ( ReadOnlySpan < char > userAgent )
132+ public static string ? GetRobot ( string userAgent )
129133 {
130134 foreach ( ( string key , string value ) in HttpUserAgentStatics . Robots )
131135 {
@@ -141,7 +145,7 @@ public static bool TryGetBrowser(ReadOnlySpan<char> userAgent, [NotNullWhen(true
141145 /// <summary>
142146 /// returns true if robot was found
143147 /// </summary>
144- public static bool TryGetRobot ( ReadOnlySpan < char > userAgent , [ NotNullWhen ( true ) ] out string ? robotName )
148+ public static bool TryGetRobot ( string userAgent , [ NotNullWhen ( true ) ] out string ? robotName )
145149 {
146150 robotName = GetRobot ( userAgent ) ;
147151 return robotName is not null ;
@@ -150,7 +154,7 @@ public static bool TryGetRobot(ReadOnlySpan<char> userAgent, [NotNullWhen(true)]
150154 /// <summary>
151155 /// returns the device or null
152156 /// </summary>
153- public static string ? GetMobileDevice ( ReadOnlySpan < char > userAgent )
157+ public static string ? GetMobileDevice ( string userAgent )
154158 {
155159 foreach ( ( string key , string value ) in HttpUserAgentStatics . Mobiles )
156160 {
@@ -166,7 +170,7 @@ public static bool TryGetRobot(ReadOnlySpan<char> userAgent, [NotNullWhen(true)]
166170 /// <summary>
167171 /// returns true if device was found
168172 /// </summary>
169- public static bool TryGetMobileDevice ( ReadOnlySpan < char > userAgent , [ NotNullWhen ( true ) ] out string ? device )
173+ public static bool TryGetMobileDevice ( string userAgent , [ NotNullWhen ( true ) ] out string ? device )
170174 {
171175 device = GetMobileDevice ( userAgent ) ;
172176 return device is not null ;
0 commit comments