diff --git a/s3/src/bucket/utils.rs b/s3/src/bucket/utils.rs index 1e52eb6648..324109f594 100644 --- a/s3/src/bucket/utils.rs +++ b/s3/src/bucket/utils.rs @@ -66,16 +66,27 @@ impl Bucket { } pub fn url(&self) -> String { - if self.path_style { - format!( - "{}://{}/{}", - self.scheme(), - self.path_style_host(), - self.name() - ) - } else { - format!("{}://{}", self.scheme(), self.subdomain_style_host()) - } + let scheme = self.scheme(); + let bucket_name = self.name(); + + // Build the host + let host = { + let host = self + .path_style + .then(|| self.path_style_host()) + .unwrap_or_else(|| self.subdomain_style_host()); + + // Test whether the endpoint already contains the bucket name + let ends_with_bucket = host.ends_with(&format!("/{}", bucket_name)); + + // Build the host with respect to the bucket name and path style + (self.path_style && ends_with_bucket) + .then(|| host.clone()) + .unwrap_or_else(|| format!("{host}/{bucket_name}")) + }; + + // Return the URL + format!("{scheme}://{host}") } /// Get a paths-style reference to the hostname of the S3 API endpoint. diff --git a/s3/src/request/tokio_backend.rs b/s3/src/request/tokio_backend.rs index 214e6fe452..a44ed32d31 100644 --- a/s3/src/request/tokio_backend.rs +++ b/s3/src/request/tokio_backend.rs @@ -268,6 +268,30 @@ mod tests { assert_eq!(*host, "custom-region".to_string()); } + #[test] + fn test_path_style_url_ends_in_bucket() { + // Test case without bucket in URL + let region = "http://custom-region".parse().unwrap(); + let bucket = Bucket::new("foo", region, fake_credentials()) + .unwrap() + .with_path_style(); + assert_eq!(bucket.url(), "http://custom-region/foo"); + + // Test case with bucket in URL + let region = "http://custom-region/foo".parse().unwrap(); + let bucket = Bucket::new("foo", region, fake_credentials()) + .unwrap() + .with_path_style(); + assert_eq!(bucket.url(), "http://custom-region/foo"); + + // Just to make sure... + let region = "http://custom-region/foo".parse().unwrap(); + let bucket = Bucket::new("bar", region, fake_credentials()) + .unwrap() + .with_path_style(); + assert_eq!(bucket.url(), "http://custom-region/foo/bar"); + } + #[test] fn test_get_object_range_header() { let region = "http://custom-region".parse().unwrap();