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