@@ -12,16 +12,42 @@ namespace QsNet.Internal;
1212/// <summary>
1313/// A helper class for decoding query strings into structured data.
1414/// </summary>
15+ #if NETSTANDARD2_0
16+ internal static class Decoder
17+ #else
1518internal static partial class Decoder
19+ #endif
1620{
1721 /// <summary>
1822 /// Regular expression to match dots followed by non-dot and non-bracket characters.
1923 /// This is used to replace dots in keys with brackets for parsing.
2024 /// </summary>
2125 private static readonly Regex DotToBracket = MyRegex ( ) ;
2226
27+ #if NETSTANDARD2_0
28+ private static readonly Regex MyRegexInstance = new ( @"\.([^.\[]+)" , RegexOptions . Compiled ) ;
29+ private static Regex MyRegex ( )
30+ {
31+ return MyRegexInstance ;
32+ }
33+ #else
2334 [ GeneratedRegex ( @"\.([^.\[]+)" , RegexOptions . Compiled ) ]
2435 private static partial Regex MyRegex ( ) ;
36+ #endif
37+
38+ private static Encoding Latin1Encoding =>
39+ #if NETSTANDARD2_0
40+ Encoding . GetEncoding ( 28591 ) ;
41+ #else
42+ Encoding . Latin1 ;
43+ #endif
44+
45+ private static bool IsLatin1 ( Encoding e ) =>
46+ #if NETSTANDARD2_0
47+ e is { CodePage : 28591 } ;
48+ #else
49+ Equals ( e , Encoding . Latin1 ) ;
50+ #endif
2551
2652 /// <summary>
2753 /// Parses a list value from a string or any other type, applying the options provided.
@@ -70,9 +96,15 @@ int currentListLength
7096 options ??= new DecodeOptions ( ) ;
7197 var obj = new Dictionary < string , object ? > ( ) ;
7298
99+ #if NETSTANDARD2_0
100+ var cleanStr = options . IgnoreQueryPrefix ? str . TrimStart ( '?' ) : str ;
101+ cleanStr = ReplaceOrdinalIgnoreCase ( cleanStr , "%5B" , "[" ) ;
102+ cleanStr = ReplaceOrdinalIgnoreCase ( cleanStr , "%5D" , "]" ) ;
103+ #else
73104 var cleanStr = ( options . IgnoreQueryPrefix ? str . TrimStart ( '?' ) : str )
74105 . Replace ( "%5B" , "[" , StringComparison . OrdinalIgnoreCase )
75106 . Replace ( "%5D" , "]" , StringComparison . OrdinalIgnoreCase ) ;
107+ #endif
76108
77109 var limit = options . ParameterLimit == int . MaxValue ? ( int ? ) null : options . ParameterLimit ;
78110
@@ -106,7 +138,7 @@ int currentListLength
106138 charset = parts [ i ] switch
107139 {
108140 var p when p == Sentinel . Charset . GetEncoded ( ) => Encoding . UTF8 ,
109- var p when p == Sentinel . Iso . GetEncoded ( ) => Encoding . Latin1 ,
141+ var p when p == Sentinel . Iso . GetEncoded ( ) => Latin1Encoding ,
110142 _ => charset
111143 } ;
112144 skipIndex = i ;
@@ -132,21 +164,32 @@ int currentListLength
132164 }
133165 else
134166 {
167+ #if NETSTANDARD2_0
168+ key = options . GetDecoder ( part . Substring ( 0 , pos ) , charset ) ? . ToString ( ) ?? string . Empty ;
169+ #else
135170 key = options . GetDecoder ( part [ ..pos ] , charset ) ? . ToString ( ) ?? string . Empty ;
171+ #endif
136172 var currentLength =
137173 obj . TryGetValue ( key , out var val ) && val is IList < object ? > list ? list . Count : 0 ;
138174
175+ #if NETSTANDARD2_0
176+ value = Utils . Apply < object ? > (
177+ ParseListValue ( part . Substring ( pos + 1 ) , options , currentLength ) ,
178+ v => options . GetDecoder ( v ? . ToString ( ) , charset )
179+ ) ;
180+ #else
139181 value = Utils . Apply < object ? > (
140182 ParseListValue ( part [ ( pos + 1 ) ..] , options , currentLength ) ,
141183 v => options . GetDecoder ( v ? . ToString ( ) , charset )
142184 ) ;
185+ #endif
143186 }
144187
145188 if (
146189 value != null
147190 && ! Utils . IsEmpty ( value )
148191 && options . InterpretNumericEntities
149- && Equals ( charset , Encoding . Latin1 )
192+ && IsLatin1 ( charset )
150193 )
151194 value = Utils . InterpretNumericEntities (
152195 value switch
@@ -190,7 +233,13 @@ bool valuesParsed
190233 )
191234 {
192235 var currentListLength = 0 ;
193- if ( chain . Count > 0 && chain [ ^ 1 ] == "[]" )
236+ if ( chain . Count > 0 &&
237+ #if NETSTANDARD2_0
238+ chain [ chain . Count - 1 ] == "[]"
239+ #else
240+ chain [ ^ 1 ] == "[]"
241+ #endif
242+ )
194243 {
195244 var parentKeyStr = string . Join ( "" , chain . Take ( chain . Count - 1 ) ) ;
196245 if (
@@ -232,7 +281,13 @@ bool valuesParsed
232281 else
233282 {
234283 // Unwrap [ ... ] and (optionally) decode %2E -> .
284+ #if NETSTANDARD2_0
285+ var cleanRoot = root . StartsWith ( "[" ) && root . EndsWith ( "]" )
286+ ? root . Substring ( 1 , root . Length - 2 )
287+ : root ;
288+ #else
235289 var cleanRoot = root . StartsWith ( '[' ) && root . EndsWith ( ']' ) ? root [ 1 ..^ 1 ] : root ;
290+ #endif
236291 var decodedRoot = options . DecodeDotInKeys
237292 ? cleanRoot . Replace ( "%2E" , "." )
238293 : cleanRoot ;
@@ -298,7 +353,7 @@ bool valuesParsed
298353 return null ;
299354
300355 var segments = SplitKeyIntoSegments (
301- givenKey ,
356+ givenKey ! ,
302357 options . AllowDots ,
303358 options . Depth ,
304359 options . StrictDepth
@@ -335,7 +390,11 @@ bool strictDepth
335390 var segments = new List < string > ( ) ;
336391
337392 var first = key . IndexOf ( '[' ) ;
393+ #if NETSTANDARD2_0
394+ var parent = first >= 0 ? key . Substring ( 0 , first ) : key ;
395+ #else
338396 var parent = first >= 0 ? key [ ..first ] : key ;
397+ #endif
339398 if ( ! string . IsNullOrEmpty ( parent ) )
340399 segments . Add ( parent ) ;
341400
@@ -346,7 +405,11 @@ bool strictDepth
346405 var close = key . IndexOf ( ']' , open + 1 ) ;
347406 if ( close < 0 )
348407 break ;
408+ #if NETSTANDARD2_0
409+ segments . Add ( key . Substring ( open , close + 1 - open ) ) ; // e.g. "[p]" or "[]"
410+ #else
349411 segments . Add ( key [ open ..( close + 1 ) ] ) ; // e.g. "[p]" or "[]"
412+ #endif
350413 depth ++ ;
351414 open = key . IndexOf ( '[' , close + 1 ) ;
352415 }
@@ -357,9 +420,39 @@ bool strictDepth
357420 throw new IndexOutOfRangeException (
358421 $ "Input depth exceeded depth option of { maxDepth } and strictDepth is true"
359422 ) ;
360- // Stash the remainder as a single segment.
423+ #if NETSTANDARD2_0
424+ segments . Add ( "[" + key . Substring ( open ) + "]" ) ;
425+ #else
361426 segments . Add ( "[" + key [ open ..] + "]" ) ;
427+ #endif
362428
363429 return segments ;
364430 }
431+
432+ #if NETSTANDARD2_0
433+ // Efficient case-insensitive ordinal string replace for NETSTANDARD2_0 (no regex, no allocations beyond matches)
434+ private static string ReplaceOrdinalIgnoreCase ( string input , string oldValue , string newValue )
435+ {
436+ if ( string . IsNullOrEmpty ( input ) || string . IsNullOrEmpty ( oldValue ) )
437+ return input ;
438+
439+ var startIndex = 0 ;
440+ StringBuilder ? sb = null ;
441+ while ( true )
442+ {
443+ var idx = input . IndexOf ( oldValue , startIndex , StringComparison . OrdinalIgnoreCase ) ;
444+ if ( idx < 0 )
445+ {
446+ if ( sb == null ) return input ;
447+ sb . Append ( input , startIndex , input . Length - startIndex ) ;
448+ return sb . ToString ( ) ;
449+ }
450+
451+ sb ??= new StringBuilder ( input . Length ) ;
452+ sb . Append ( input , startIndex , idx - startIndex ) ;
453+ sb . Append ( newValue ) ;
454+ startIndex = idx + oldValue . Length ;
455+ }
456+ }
457+ #endif
365458}
0 commit comments