Skip to content

Commit 1ac2631

Browse files
authored
Merge pull request #7 from sunsided/feature/fix-encodings
Add test for character encoding
2 parents a2c0a13 + 0bf0a83 commit 1ac2631

File tree

2 files changed

+82
-4
lines changed

2 files changed

+82
-4
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
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+
13+
### Fixed
14+
15+
- The hash character `#` is now encoded in order to ensure correct parsing of query parameters.
16+
617
## [0.4.0] - 2023-07-08
718

819
### Added
@@ -29,5 +40,7 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2940
- 🎉 Initial release.
3041

3142
[0.3.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.3.0
43+
3244
[0.2.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.2.0
45+
3346
[0.1.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.1.0

src/lib.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,22 @@ use std::fmt::{Debug, Display, Formatter};
2525

2626
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
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
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'+');
3044

3145
/// A query string builder for percent encoding key-value pairs.
3246
///
@@ -229,8 +243,8 @@ impl Display for QueryString {
229243
write!(
230244
f,
231245
"{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)
234248
)?;
235249
}
236250
Ok(())
@@ -327,4 +341,55 @@ mod tests {
327341
"https://example.com/?q=apple&q=pear&answer=42"
328342
);
329343
}
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+
}
330395
}

0 commit comments

Comments
 (0)