From 69bb09f7ad1afc12638b23a89d73a9cd61f8a530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 27 Sep 2021 19:16:26 +0300 Subject: [PATCH] rusoto/s3: Allow passing custom AWS-compatible regions For the region property this would be provided as `region-name+https://region.end/point` while for the URI this unfortunately has to be base32 encoded to allow usage as the host part of the URI. --- net/rusoto/Cargo.toml | 1 + net/rusoto/src/s3sink/imp.rs | 36 ++++++++++++++++++++++++++++++++---- net/rusoto/src/s3url.rs | 35 +++++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/net/rusoto/Cargo.toml b/net/rusoto/Cargo.toml index 0a483c65..67f9e736 100644 --- a/net/rusoto/Cargo.toml +++ b/net/rusoto/Cargo.toml @@ -30,6 +30,7 @@ serde = "1" serde_derive = "1" serde_json = "1" atomic_refcell = "0.1" +base32 = "0.4" [lib] name = "gstrusoto" diff --git a/net/rusoto/src/s3sink/imp.rs b/net/rusoto/src/s3sink/imp.rs index 6f74e94e..792ab275 100644 --- a/net/rusoto/src/s3sink/imp.rs +++ b/net/rusoto/src/s3sink/imp.rs @@ -95,7 +95,27 @@ impl Settings { fn to_uri(&self) -> String { format!( "s3://{}/{}/{}", - self.region.name(), + match self.region { + Region::Custom { + ref name, + ref endpoint, + } => { + format!( + "{}+{}", + base32::encode( + base32::Alphabet::RFC4648 { padding: true }, + name.as_bytes(), + ), + base32::encode( + base32::Alphabet::RFC4648 { padding: true }, + endpoint.as_bytes(), + ), + ) + } + _ => { + String::from(self.region.name()) + } + }, self.bucket.as_ref().unwrap(), self.key.as_ref().unwrap() ) @@ -500,9 +520,17 @@ impl ObjectImpl for S3Sink { } } "region" => { - settings.region = - Region::from_str(&value.get::().expect("type checked upstream")) - .unwrap(); + let region = value.get::().expect("type checked upstream"); + settings.region = Region::from_str(®ion) + .or_else(|_| { + let (name, endpoint) = region.split_once('+').ok_or(())?; + Ok(Region::Custom { + name: name.into(), + endpoint: endpoint.into(), + }) + }) + .unwrap_or_else(|_: ()| panic!("Invalid region '{}'", region)); + if settings.key.is_some() && settings.bucket.is_some() { let _ = self.set_uri(obj, Some(&settings.to_uri())); } diff --git a/net/rusoto/src/s3url.rs b/net/rusoto/src/s3url.rs index c059f96f..b5e59ef6 100644 --- a/net/rusoto/src/s3url.rs +++ b/net/rusoto/src/s3url.rs @@ -31,7 +31,27 @@ impl ToString for GstS3Url { fn to_string(&self) -> String { format!( "s3://{}/{}/{}{}", - self.region.name(), + match self.region { + Region::Custom { + ref name, + ref endpoint, + } => { + format!( + "{}+{}", + base32::encode( + base32::Alphabet::RFC4648 { padding: true }, + name.as_bytes(), + ), + base32::encode( + base32::Alphabet::RFC4648 { padding: true }, + endpoint.as_bytes(), + ), + ) + } + _ => { + String::from(self.region.name()) + } + }, self.bucket, percent_encode(self.object.as_bytes(), PATH_SEGMENT), if self.version.is_some() { @@ -55,7 +75,18 @@ pub fn parse_s3_url(url_str: &str) -> Result { } let host = url.host_str().unwrap(); - let region = Region::from_str(host).map_err(|_| format!("Invalid region '{}'", host))?; + let region = Region::from_str(host) + .or_else(|_| { + let (name, endpoint) = host.split_once('+').ok_or(())?; + let name = + base32::decode(base32::Alphabet::RFC4648 { padding: true }, name).ok_or(())?; + let endpoint = + base32::decode(base32::Alphabet::RFC4648 { padding: true }, endpoint).ok_or(())?; + let name = String::from_utf8(name).map_err(|_| ())?; + let endpoint = String::from_utf8(endpoint).map_err(|_| ())?; + Ok(Region::Custom { name, endpoint }) + }) + .map_err(|_: ()| format!("Invalid region '{}'", host))?; let mut path = url .path_segments()