Skip to content

Commit 9486e76

Browse files
committed
SourceId: Fix ambiguous serialization of Git references
Use x-www-form-urlencoded which is the standard for query strings, matching what the deserializer expects. https://url.spec.whatwg.org/#application/x-www-form-urlencoded Fixes #11085.
1 parent ec3ed33 commit 9486e76

File tree

3 files changed

+43
-4
lines changed

3 files changed

+43
-4
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pretty_env_logger = { version = "0.4", optional = true }
2828
anyhow = "1.0"
2929
filetime = "0.2.9"
3030
flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
31+
form_urlencoded = "1.1.0"
3132
git2 = "0.15.0"
3233
git2-curl = "0.16.0"
3334
glob = "0.3.0"

src/cargo/core/source/source_id.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@ use std::ptr;
1515
use std::sync::Mutex;
1616
use url::Url;
1717

18+
// Encode a name or value for a query string
19+
//
20+
// https://url.spec.whatwg.org/#urlencoded-serializing
21+
//
22+
// The result of running percent-encode after encoding with encoding,
23+
// tuple’s name, the application/x-www-form-urlencoded percent-encode
24+
// set, and true
25+
//
26+
// We can't use percent_encoding directly because spaces are handled in
27+
// a peculiar way.
28+
fn write_form_urlencoded_component(
29+
fmt: &mut Formatter<'_>,
30+
component: &str,
31+
) -> Result<(), fmt::Error> {
32+
for s in form_urlencoded::byte_serialize(component.as_bytes()) {
33+
fmt.write_str(s)?;
34+
}
35+
Ok(())
36+
}
37+
1838
lazy_static::lazy_static! {
1939
static ref SOURCE_ID_CACHE: Mutex<HashSet<&'static SourceIdInner>> = Default::default();
2040
}
@@ -714,9 +734,18 @@ pub struct PrettyRef<'a> {
714734
impl<'a> fmt::Display for PrettyRef<'a> {
715735
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
716736
match *self.inner {
717-
GitReference::Branch(ref b) => write!(f, "branch={}", b),
718-
GitReference::Tag(ref s) => write!(f, "tag={}", s),
719-
GitReference::Rev(ref s) => write!(f, "rev={}", s),
737+
GitReference::Branch(ref b) => {
738+
f.write_str("branch=")?;
739+
write_form_urlencoded_component(f, b)
740+
}
741+
GitReference::Tag(ref s) => {
742+
f.write_str("tag=")?;
743+
write_form_urlencoded_component(f, s)
744+
}
745+
GitReference::Rev(ref s) => {
746+
f.write_str("rev=")?;
747+
write_form_urlencoded_component(f, s)
748+
}
720749
GitReference::DefaultBranch => unreachable!(),
721750
}
722751
}
@@ -751,7 +780,16 @@ mod tests {
751780
let ser1 = format!("{}", s1.as_url());
752781
let s2 = SourceId::from_url(&ser1).expect("Failed to deserialize");
753782
let ser2 = format!("{}", s2.as_url());
783+
// Serializing twice should yield the same result
754784
assert_eq!(ser1, ser2, "Serialized forms don't match");
785+
// SourceId serializing the same should have the same semantics
786+
// This used to not be the case (# was ambiguous)
755787
assert_eq!(s1, s2, "SourceId doesn't round-trip");
788+
// Freeze the format to match an x-www-form-urlencoded query string
789+
// https://url.spec.whatwg.org/#application/x-www-form-urlencoded
790+
assert_eq!(
791+
ser1,
792+
"git+https://host/path?branch=*-._%2B20%2530+Z%2Fz%23foo%3Dbar%26zap%5B%5D%3Fto%5C%28%29%27%22"
793+
);
756794
}
757795
}

tests/testsuite/git.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ fn cargo_compile_git_dep_pull_request() {
273273
.cargo("build")
274274
.with_stderr(&format!(
275275
"[UPDATING] git repository `{}`\n\
276-
[COMPILING] dep1 v0.5.0 ({}?rev=refs/pull/330/head#[..])\n\
276+
[COMPILING] dep1 v0.5.0 ({}?rev=refs%2Fpull%2F330%2Fhead#[..])\n\
277277
[COMPILING] foo v0.0.0 ([CWD])\n\
278278
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
279279
path2url(&git_root),

0 commit comments

Comments
 (0)