Skip to content

Commit b403c6d

Browse files
committed
Add test for character encoding
1 parent a2c0a13 commit b403c6d

File tree

2 files changed

+70
-5
lines changed

2 files changed

+70
-5
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
All notable changes to this project will be documented in this file.
44
This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

6+
## Unreleased
7+
8+
### Added
9+
10+
- More characters are added to the encoding set to ensure recursive values
11+
(e.g. URLs as a value) decode reliably.
12+
613
## [0.4.0] - 2023-07-08
714

815
### Added
@@ -29,5 +36,7 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2936
- 🎉 Initial release.
3037

3138
[0.3.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.3.0
39+
3240
[0.2.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.2.0
41+
3342
[0.1.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.1.0

src/lib.rs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@
2323

2424
use std::fmt::{Debug, Display, Formatter};
2525

26-
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
26+
use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
2727

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.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>')
30+
// The following values are not strictly required by RFC 3986 but could help resolving recursion
31+
// where a URL is passed as a value. In these cases, occurrences of equal signs and ampersands
32+
// could break parsing.
33+
// By a similar logic, encoding the percent sign helps to resolve ambiguity.
34+
// The plus sign is also added to the set as to not confuse it with a space.
35+
.add(b'%').add(b'&').add(b'=').add(b'+');
3036

3137
/// A query string builder for percent encoding key-value pairs.
3238
///
@@ -229,8 +235,8 @@ impl Display for QueryString {
229235
write!(
230236
f,
231237
"{key}={value}",
232-
key = utf8_percent_encode(&pair.key, FRAGMENT),
233-
value = utf8_percent_encode(&pair.value, FRAGMENT)
238+
key = utf8_percent_encode(&pair.key, QUERY),
239+
value = utf8_percent_encode(&pair.value, QUERY)
234240
)?;
235241
}
236242
Ok(())
@@ -327,4 +333,54 @@ mod tests {
327333
"https://example.com/?q=apple&q=pear&answer=42"
328334
);
329335
}
336+
337+
#[test]
338+
fn test_characters() {
339+
let tests = vec![
340+
("space", " ", "%20"),
341+
("double_quote", "\"", "%22"),
342+
("hash", "#", "%23"),
343+
("less_than", "<", "%3C"),
344+
("equals", "=", "%3D"),
345+
("greater_than", ">", "%3E"),
346+
("percent", "%", "%25"),
347+
("ampersand", "&", "%26"),
348+
("plus", "+", "%2B"),
349+
//
350+
("dollar", "$", "$"),
351+
("single_quote", "'", "'"),
352+
("comma", ",", ","),
353+
("forward_slash", "/", "/"),
354+
("colon", ":", ":"),
355+
("semicolon", ";", ";"),
356+
("question_mark", "?", "?"),
357+
("at", "@", "@"),
358+
("left_bracket", "[", "["),
359+
("backslash", "\\", "\\"),
360+
("right_bracket", "]", "]"),
361+
("caret", "^", "^"),
362+
("underscore", "_", "_"),
363+
("grave", "^", "^"),
364+
("left_curly", "{", "{"),
365+
("pipe", "|", "|"),
366+
("right_curly", "}", "}"),
367+
];
368+
369+
let mut qs = QueryString::new();
370+
for (key, value, _) in &tests {
371+
qs.push(key.to_string(), value.to_string());
372+
}
373+
374+
let mut expected = String::new();
375+
for (i, (key, _, value)) in tests.iter().enumerate() {
376+
if i > 0 {
377+
expected.push('&');
378+
}
379+
expected.push_str(&format!("{key}={value}"));
380+
}
381+
382+
assert_eq!(
383+
format!("https://example.com/{qs}"),
384+
format!("https://example.com/?{expected}"));
385+
}
330386
}

0 commit comments

Comments
 (0)