Skip to content

Commit d6faf3a

Browse files
authored
Merge branch 'main' into feature/tostring
2 parents 684da0e + 657407d commit d6faf3a

File tree

3 files changed

+85
-5
lines changed

3 files changed

+85
-5
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111
The functions now change inputs that implement `ToString` rather than requiring `Into<String>`.
1212
This allows for any `Display` types to be used directly.
1313

14+
## [0.4.2] - 2024-05-23
15+
16+
[0.4.2]: https://github.com/sunsided/query-string-builder/releases/tag/v0.4.2
17+
18+
### Added
19+
20+
- More characters are added to the encoding set to ensure recursive values
21+
(e.g. URLs as a value) decode reliably.
22+
23+
### Fixed
24+
25+
- The hash character `#` is now encoded in order to ensure correct parsing of query parameters.
26+
1427
## [0.4.0] - 2023-07-08
1528

1629
### Added
@@ -37,5 +50,7 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3750
- 🎉 Initial release.
3851

3952
[0.3.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.3.0
53+
4054
[0.2.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.2.0
55+
4156
[0.1.0]: https://github.com/sunsided/query-string-builder/releases/tag/0.1.0

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "query-string-builder"
33
description = "A query string builder for percent encoding key-value pairs"
44
authors = ["Markus Mayer"]
5-
version = "0.4.1"
5+
version = "0.4.2"
66
edition = "2021"
77
repository = "https://github.com/sunsided/query-string-builder"
88
keywords = ["url", "query-string", "query-argument"]

src/lib.rs

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

2727
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
2828

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'+');
3145

3246
/// A query string builder for percent encoding key-value pairs.
3347
///
@@ -224,8 +238,8 @@ impl Display for QueryString {
224238
write!(
225239
f,
226240
"{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)
229243
)?;
230244
}
231245
Ok(())
@@ -326,4 +340,55 @@ mod tests {
326340
"https://example.com/?q=apple&q=pear&answer=42"
327341
);
328342
}
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+
}
329394
}

0 commit comments

Comments
 (0)