This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs-object-store.git


The following commit(s) were added to refs/heads/main by this push:
     new 7e854ef  fix(aws): recognize S3 dual-stack endpoints in URL parsing 
(#780)
7e854ef is described below

commit 7e854ef3c728fd7dca4a705bf26141c9a40e3a55
Author: Andrew Duffy <[email protected]>
AuthorDate: Wed Jul 8 21:55:24 2026 +0100

    fix(aws): recognize S3 dual-stack endpoints in URL parsing (#780)
    
    `AmazonS3Builder::parse_url` matched S3 hostnames against a fixed set of
    patterns via `splitn(4, '.')`. Dual-stack endpoints insert a `dualstack`
    label after `s3` (e.g. `s3.dualstack.<region>.amazonaws.com` for path-style
    and `<bucket>.s3.dualstack.<region>.amazonaws.com` for virtual-hosted),
    which matched none of the patterns and fell through to `UrlNotRecognised`.
    
    Because `ObjectStoreScheme::parse` already classifies these URLs as S3,
    `DefaultObjectStoreRegistry::resolve` would call into the S3 builder and
    then fail with `ObjectStore not found`.
    
    Add handling for both dual-stack forms, setting the endpoint explicitly so
    the `dualstack` label is preserved (IPv6 addressing) rather than being lost
    when the endpoint is reconstructed from the region. Includes a registry
    regression test plus builder and scheme-parse coverage.
    
    
    Claude-Session: https://claude.ai/code/session_014n2xnhDiTk4T8RESdRDgDS
    
    Signed-off-by: Andrew Duffy <[email protected]>
    Co-authored-by: Claude <[email protected]>
---
 src/aws/builder.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/parse.rs       |  8 ++++++++
 2 files changed, 60 insertions(+)

diff --git a/src/aws/builder.rs b/src/aws/builder.rs
index b660a6c..b09c665 100644
--- a/src/aws/builder.rs
+++ b/src/aws/builder.rs
@@ -625,6 +625,8 @@ impl AmazonS3Builder {
     /// - `s3a://<bucket>/<path>`
     /// - `https://s3.<region>.amazonaws.com/<bucket>`
     /// - `https://<bucket>.s3.<region>.amazonaws.com`
+    /// - `https://s3.dualstack.<region>.amazonaws.com/<bucket>`
+    /// - `https://<bucket>.s3.dualstack.<region>.amazonaws.com`
     /// - `https://ACCOUNT_ID.r2.cloudflarestorage.com/bucket`
     ///
     /// Note: Settings derived from the URL will override any others set on 
this builder
@@ -807,10 +809,34 @@ impl AmazonS3Builder {
                         self.bucket_name = Some(bucket.into());
                     }
                 }
+                // Path-style dual-stack endpoint, e.g. 
`s3.dualstack.<region>.amazonaws.com`.
+                Some(("s3", "dualstack", region, "amazonaws.com")) => {
+                    self.region = Some(region.to_string());
+                    // Override endpoint to explicitly preserve dualstack 
address
+                    self.endpoint = 
Some(format!("https://s3.dualstack.{region}.amazonaws.com";));
+                    let bucket = 
parsed.path_segments().into_iter().flatten().next();
+                    if let Some(bucket) = bucket {
+                        self.bucket_name = Some(bucket.into());
+                    }
+                }
                 Some((bucket, "s3", "amazonaws", "com")) => {
                     self.bucket_name = Some(bucket.to_string());
                     self.virtual_hosted_style_request = true.into();
                 }
+                // Virtual-hosted dual-stack endpoint, e.g.
+                // `<bucket>.s3.dualstack.<region>.amazonaws.com`
+                Some((bucket, "s3", "dualstack", rest)) => {
+                    let region = rest
+                        .strip_suffix(".amazonaws.com")
+                        .ok_or_else(|| Error::UrlNotRecognised { url: 
url.into() })?;
+                    self.bucket_name = Some(bucket.to_string());
+                    self.region = Some(region.to_string());
+                    // Override endpoint to explicitly preserve dualstack 
address
+                    self.endpoint = Some(format!(
+                        "https://{bucket}.s3.dualstack.{region}.amazonaws.com";
+                    ));
+                    self.virtual_hosted_style_request = true.into();
+                }
                 Some((bucket, "s3", region, "amazonaws.com")) => {
                     self.bucket_name = Some(bucket.to_string());
                     self.region = Some(region.to_string());
@@ -1782,6 +1808,32 @@ mod tests {
         assert_eq!(builder.region, Some("region".to_string()));
         assert!(builder.virtual_hosted_style_request.get().unwrap());
 
+        // Path-style dual-stack endpoint
+        let mut builder = AmazonS3Builder::new();
+        builder
+            
.parse_url("https://s3.dualstack.us-east-1.amazonaws.com/bucket/path";)
+            .unwrap();
+        assert_eq!(builder.region, Some("us-east-1".to_string()));
+        assert_eq!(builder.bucket_name, Some("bucket".to_string()));
+        assert_eq!(
+            builder.endpoint,
+            Some("https://s3.dualstack.us-east-1.amazonaws.com".to_string())
+        );
+        assert!(!builder.virtual_hosted_style_request.get().unwrap());
+
+        // Virtual-hosted dual-stack endpoint
+        let mut builder = AmazonS3Builder::new();
+        builder
+            .parse_url("https://bucket.s3.dualstack.us-east-1.amazonaws.com";)
+            .unwrap();
+        assert_eq!(builder.bucket_name, Some("bucket".to_string()));
+        assert_eq!(builder.region, Some("us-east-1".to_string()));
+        assert_eq!(
+            builder.endpoint,
+            
Some("https://bucket.s3.dualstack.us-east-1.amazonaws.com".to_string())
+        );
+        assert!(builder.virtual_hosted_style_request.get().unwrap());
+
         let mut builder = AmazonS3Builder::new();
         builder
             
.parse_url("https://account123.r2.cloudflarestorage.com/bucket-123";)
diff --git a/src/parse.rs b/src/parse.rs
index 2265547..218fe69 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -263,6 +263,14 @@ mod tests {
                 "https://bucket.s3.region.amazonaws.com";,
                 (ObjectStoreScheme::AmazonS3, ""),
             ),
+            (
+                "https://s3.dualstack.us-east-1.amazonaws.com/bucket/path";,
+                (ObjectStoreScheme::AmazonS3, "path"),
+            ),
+            (
+                "https://bucket.s3.dualstack.us-east-1.amazonaws.com/path";,
+                (ObjectStoreScheme::AmazonS3, "path"),
+            ),
             (
                 "https://ACCOUNT_ID.r2.cloudflarestorage.com/bucket";,
                 (ObjectStoreScheme::AmazonS3, ""),

Reply via email to