@@ -26,8 +26,22 @@ use std::fmt::{Debug, Display, Formatter};
26
26
27
27
use percent_encoding:: { utf8_percent_encode, AsciiSet , CONTROLS } ;
28
28
29
- /// https://url.spec.whatwg.org/#fragment-percent-encode-set
30
- const FRAGMENT : & AsciiSet = & CONTROLS . add ( b' ' ) . add ( b'"' ) . add ( b'<' ) . add ( b'>' ) . add ( b'`' ) ;
29
+ /// https://url.spec.whatwg.org/#query-percent-encode-set
30
+ const QUERY : & AsciiSet = & CONTROLS
31
+ . add ( b' ' )
32
+ . add ( b'"' )
33
+ . add ( b'#' )
34
+ . add ( b'<' )
35
+ . add ( b'>' )
36
+ // The following values are not strictly required by RFC 3986 but could help resolving recursion
37
+ // where a URL is passed as a value. In these cases, occurrences of equal signs and ampersands
38
+ // could break parsing.
39
+ // By a similar logic, encoding the percent sign helps to resolve ambiguity.
40
+ // The plus sign is also added to the set as to not confuse it with a space.
41
+ . add ( b'%' )
42
+ . add ( b'&' )
43
+ . add ( b'=' )
44
+ . add ( b'+' ) ;
31
45
32
46
/// A query string builder for percent encoding key-value pairs.
33
47
///
@@ -224,8 +238,8 @@ impl Display for QueryString {
224
238
write ! (
225
239
f,
226
240
"{key}={value}" ,
227
- key = utf8_percent_encode( & pair. key, FRAGMENT ) ,
228
- value = utf8_percent_encode( & pair. value, FRAGMENT )
241
+ key = utf8_percent_encode( & pair. key, QUERY ) ,
242
+ value = utf8_percent_encode( & pair. value, QUERY )
229
243
) ?;
230
244
}
231
245
Ok ( ( ) )
@@ -326,4 +340,55 @@ mod tests {
326
340
"https://example.com/?q=apple&q=pear&answer=42"
327
341
) ;
328
342
}
343
+
344
+ #[ test]
345
+ fn test_characters ( ) {
346
+ let tests = vec ! [
347
+ ( "space" , " " , "%20" ) ,
348
+ ( "double_quote" , "\" " , "%22" ) ,
349
+ ( "hash" , "#" , "%23" ) ,
350
+ ( "less_than" , "<" , "%3C" ) ,
351
+ ( "equals" , "=" , "%3D" ) ,
352
+ ( "greater_than" , ">" , "%3E" ) ,
353
+ ( "percent" , "%" , "%25" ) ,
354
+ ( "ampersand" , "&" , "%26" ) ,
355
+ ( "plus" , "+" , "%2B" ) ,
356
+ //
357
+ ( "dollar" , "$" , "$" ) ,
358
+ ( "single_quote" , "'" , "'" ) ,
359
+ ( "comma" , "," , "," ) ,
360
+ ( "forward_slash" , "/" , "/" ) ,
361
+ ( "colon" , ":" , ":" ) ,
362
+ ( "semicolon" , ";" , ";" ) ,
363
+ ( "question_mark" , "?" , "?" ) ,
364
+ ( "at" , "@" , "@" ) ,
365
+ ( "left_bracket" , "[" , "[" ) ,
366
+ ( "backslash" , "\\ " , "\\ " ) ,
367
+ ( "right_bracket" , "]" , "]" ) ,
368
+ ( "caret" , "^" , "^" ) ,
369
+ ( "underscore" , "_" , "_" ) ,
370
+ ( "grave" , "^" , "^" ) ,
371
+ ( "left_curly" , "{" , "{" ) ,
372
+ ( "pipe" , "|" , "|" ) ,
373
+ ( "right_curly" , "}" , "}" ) ,
374
+ ] ;
375
+
376
+ let mut qs = QueryString :: new ( ) ;
377
+ for ( key, value, _) in & tests {
378
+ qs. push ( key. to_string ( ) , value. to_string ( ) ) ;
379
+ }
380
+
381
+ let mut expected = String :: new ( ) ;
382
+ for ( i, ( key, _, value) ) in tests. iter ( ) . enumerate ( ) {
383
+ if i > 0 {
384
+ expected. push ( '&' ) ;
385
+ }
386
+ expected. push_str ( & format ! ( "{key}={value}" ) ) ;
387
+ }
388
+
389
+ assert_eq ! (
390
+ format!( "https://example.com/{qs}" ) ,
391
+ format!( "https://example.com/?{expected}" )
392
+ ) ;
393
+ }
329
394
}
0 commit comments