Skip to content

Commit a53ffdf

Browse files
weihanglog2p
andcommitted
feat: SourceIdAsUrl is aware of URL encoding
Function `SourceId::as_encoded_url()` is added for that purpose. Co-authored-by: Gabriel de Perthuis <[email protected]>
1 parent 5974123 commit a53ffdf

File tree

3 files changed

+63
-10
lines changed

3 files changed

+63
-10
lines changed

src/cargo/core/source/source_id.rs

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,15 @@ impl SourceId {
195195
pub fn as_url(&self) -> SourceIdAsUrl<'_> {
196196
SourceIdAsUrl {
197197
inner: &*self.inner,
198+
encoded: false,
199+
}
200+
}
201+
202+
/// Like [`Self::as_url`] but with URL parameters encoded.
203+
pub fn as_encoded_url(&self) -> SourceIdAsUrl<'_> {
204+
SourceIdAsUrl {
205+
inner: &*self.inner,
206+
encoded: true,
198207
}
199208
}
200209

@@ -566,7 +575,7 @@ impl fmt::Display for SourceId {
566575
// Don't replace the URL display for git references,
567576
// because those are kind of expected to be URLs.
568577
write!(f, "{}", self.inner.url)?;
569-
if let Some(pretty) = reference.pretty_ref() {
578+
if let Some(pretty) = reference.pretty_ref(false) {
570579
write!(f, "?{}", pretty)?;
571580
}
572581

@@ -714,6 +723,7 @@ impl Ord for SourceKind {
714723
/// A `Display`able view into a `SourceId` that will write it as a url
715724
pub struct SourceIdAsUrl<'a> {
716725
inner: &'a SourceIdInner,
726+
encoded: bool,
717727
}
718728

719729
impl<'a> fmt::Display for SourceIdAsUrl<'a> {
@@ -731,7 +741,7 @@ impl<'a> fmt::Display for SourceIdAsUrl<'a> {
731741
..
732742
} => {
733743
write!(f, "git+{}", url)?;
734-
if let Some(pretty) = reference.pretty_ref() {
744+
if let Some(pretty) = reference.pretty_ref(self.encoded) {
735745
write!(f, "?{}", pretty)?;
736746
}
737747
if let Some(precise) = precise.as_ref() {
@@ -771,27 +781,49 @@ impl<'a> fmt::Display for SourceIdAsUrl<'a> {
771781
impl GitReference {
772782
/// Returns a `Display`able view of this git reference, or None if using
773783
/// the head of the default branch
774-
pub fn pretty_ref(&self) -> Option<PrettyRef<'_>> {
784+
pub fn pretty_ref(&self, url_encoded: bool) -> Option<PrettyRef<'_>> {
775785
match self {
776786
GitReference::DefaultBranch => None,
777-
_ => Some(PrettyRef { inner: self }),
787+
_ => Some(PrettyRef {
788+
inner: self,
789+
url_encoded,
790+
}),
778791
}
779792
}
780793
}
781794

782795
/// A git reference that can be `Display`ed
783796
pub struct PrettyRef<'a> {
784797
inner: &'a GitReference,
798+
url_encoded: bool,
785799
}
786800

787801
impl<'a> fmt::Display for PrettyRef<'a> {
788802
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
789-
match *self.inner {
790-
GitReference::Branch(ref b) => write!(f, "branch={}", b),
791-
GitReference::Tag(ref s) => write!(f, "tag={}", s),
792-
GitReference::Rev(ref s) => write!(f, "rev={}", s),
803+
let value: &str;
804+
match self.inner {
805+
GitReference::Branch(s) => {
806+
write!(f, "branch=")?;
807+
value = s;
808+
}
809+
GitReference::Tag(s) => {
810+
write!(f, "tag=")?;
811+
value = s;
812+
}
813+
GitReference::Rev(s) => {
814+
write!(f, "rev=")?;
815+
value = s;
816+
}
793817
GitReference::DefaultBranch => unreachable!(),
794818
}
819+
if self.url_encoded {
820+
for value in url::form_urlencoded::byte_serialize(value.as_bytes()) {
821+
write!(f, "{value}")?;
822+
}
823+
} else {
824+
write!(f, "{value}")?;
825+
}
826+
Ok(())
795827
}
796828
}
797829

@@ -905,6 +937,27 @@ mod tests {
905937
assert_eq!(formatted, "sparse+https://my-crates.io/");
906938
assert_eq!(source_id, deserialized);
907939
}
940+
941+
#[test]
942+
fn gitrefs_roundtrip() {
943+
let base = "https://host/path".into_url().unwrap();
944+
let branch = GitReference::Branch("*-._+20%30 Z/z#foo=bar&zap[]?to\\()'\"".to_string());
945+
let s1 = SourceId::for_git(&base, branch).unwrap();
946+
let ser1 = format!("{}", s1.as_encoded_url());
947+
let s2 = SourceId::from_url(&ser1).expect("Failed to deserialize");
948+
let ser2 = format!("{}", s2.as_encoded_url());
949+
// Serializing twice should yield the same result
950+
assert_eq!(ser1, ser2, "Serialized forms don't match");
951+
// SourceId serializing the same should have the same semantics
952+
// This used to not be the case (# was ambiguous)
953+
assert_eq!(s1, s2, "SourceId doesn't round-trip");
954+
// Freeze the format to match an x-www-form-urlencoded query string
955+
// https://url.spec.whatwg.org/#application/x-www-form-urlencoded
956+
assert_eq!(
957+
ser1,
958+
"git+https://host/path?branch=*-._%2B20%2530+Z%2Fz%23foo%3Dbar%26zap%5B%5D%3Fto%5C%28%29%27%22"
959+
);
960+
}
908961
}
909962

910963
/// Check if `url` equals to the overridden crates.io URL.

src/cargo/sources/git/source.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl<'cfg> Debug for GitSource<'cfg> {
164164
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
165165
write!(f, "git repo at {}", self.remote.url())?;
166166

167-
match self.manifest_reference.pretty_ref() {
167+
match self.manifest_reference.pretty_ref(false) {
168168
Some(s) => write!(f, " ({})", s),
169169
None => Ok(()),
170170
}

src/cargo/util/toml_mut/dependency.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ impl GitSource {
881881
impl std::fmt::Display for GitSource {
882882
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
883883
let git_ref = self.git_ref();
884-
if let Some(pretty_ref) = git_ref.pretty_ref() {
884+
if let Some(pretty_ref) = git_ref.pretty_ref(false) {
885885
write!(f, "{}?{}", self.git, pretty_ref)
886886
} else {
887887
write!(f, "{}", self.git)

0 commit comments

Comments
 (0)