From ae173df4f8921753d7c9923a8b4c095c965f0b6a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 11 Sep 2026 05:59:21 -0500 Subject: [PATCH] Redact URLs in missing-path-segment errors --- crates/uv-distribution-types/src/lib.rs | 54 +++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 11a6a370896..013689170dd 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -1220,9 +1220,9 @@ impl RemoteSource for File { impl RemoteSource for Url { fn filename(&self) -> Result, Error> { // Identify the last segment of the URL as the filename. - let mut path_segments = self - .path_segments() - .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?; + let mut path_segments = self.path_segments().ok_or_else(|| { + Error::MissingPathSegments(DisplaySafeUrl::ref_cast(self).to_string()) + })?; // This is guaranteed by the contract of `Url::path_segments`. let last = path_segments @@ -1816,4 +1816,52 @@ mod test { assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}"); } } + + #[test] + fn remote_source_redacts_missing_path_segments() { + for (input, expected) in [ + ( + "mailto:ferris@example.com?X-Amz-Signature=sentinel", + "mailto:ferris@example.com?X-Amz-Signature=****", + ), + ( + "ssh://user:secret@example.com?sig=sentinel", + "ssh://user:****@example.com?sig=****", + ), + ( + "mailto:ferris@example.com?sig=one&X-Amz-Credential=two&X-Amz-Security-Token=three&X-Amz-Signature=four&token=kept#fragment", + "mailto:ferris@example.com?sig=****&X-Amz-Credential=****&X-Amz-Security-Token=****&X-Amz-Signature=****&token=kept#fragment", + ), + ( + "mailto:ferris@example.com?x-amz%2dsignature=sentinel&safe=value", + "mailto:ferris@example.com?x-amz-signature=****&safe=value", + ), + ( + "mailto:ferris@example.com?token=kept#fragment", + "mailto:ferris@example.com?token=kept#fragment", + ), + ] { + let url = url::Url::parse(input).unwrap(); + let error = RemoteSource::filename(&url).unwrap_err(); + let crate::Error::MissingPathSegments(payload) = &error else { + panic!("expected missing path segments"); + }; + assert_eq!(payload, expected); + assert_eq!( + error.to_string(), + format!("Could not extract path segments from URL: {expected}") + ); + assert_eq!( + format!("{error:?}"), + format!("MissingPathSegments({expected:?})") + ); + assert!(std::error::Error::source(&error).is_none()); + assert_eq!(url.as_str(), input); + } + + let input = "https://example.org/demo%20name.whl?sig=sentinel"; + let url = url::Url::parse(input).unwrap(); + assert_eq!(RemoteSource::filename(&url).unwrap(), "demo name.whl"); + assert_eq!(url.as_str(), input); + } }