// Copyright 2024 RustFS Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(unused_imports)] #![allow(unused_variables)] #![allow(unused_mut)] #![allow(unused_assignments)] #![allow(unused_must_use)] #![allow(clippy::all)] use super::constants::UNSIGNED_PAYLOAD; use super::credentials::SignatureType; use crate::client::{ api_error_response::http_resp_to_error_response, signer_error, transition_api::{CreateBucketConfiguration, LocationConstraint, TransitionClient}, }; use http::Request; use http_body_util::BodyExt; use hyper::StatusCode; use hyper::body::Body; use hyper::body::Bytes; use hyper::body::Incoming; use rustfs_config::MAX_S3_CLIENT_RESPONSE_SIZE; use rustfs_utils::hash::EMPTY_STRING_SHA256_HASH; use s3s::S3ErrorCode; use std::collections::HashMap; fn signer_error_to_io_error(scope: &str, error: rustfs_signer::SignV4Error) -> std::io::Error { signer_error::signer_error_to_io_error(scope, error) } #[derive(Debug, Clone)] pub struct BucketLocationCache { items: HashMap, } impl BucketLocationCache { pub fn new() -> BucketLocationCache { BucketLocationCache { items: HashMap::new() } } pub fn get(&self, bucket_name: &str) -> Option { self.items.get(bucket_name).map(|s| s.clone()) } pub fn set(&mut self, bucket_name: &str, location: &str) { self.items.insert(bucket_name.to_string(), location.to_string()); } pub fn delete(&mut self, bucket_name: &str) { self.items.remove(bucket_name); } } impl TransitionClient { pub async fn get_bucket_location(&self, bucket_name: &str) -> Result { Ok(self.get_bucket_location_inner(bucket_name).await?) } async fn get_bucket_location_inner(&self, bucket_name: &str) -> Result { if self.region != "" { return Ok(self.region.clone()); } let mut location; { if let Ok(bucket_loc_cache) = self.bucket_loc_cache.lock() { if let Some(location) = bucket_loc_cache.get(bucket_name) { return Ok(location); } } //location = ret?; } let req = self.get_bucket_location_request(bucket_name)?; let mut resp = self.doit(req).await?; location = process_bucket_location_response(resp, bucket_name, &self.tier_type).await?; { if let Ok(mut bucket_loc_cache) = self.bucket_loc_cache.lock() { bucket_loc_cache.set(bucket_name, &location); } } Ok(location) } fn get_bucket_location_request(&self, bucket_name: &str) -> Result, std::io::Error> { let mut url_values = HashMap::new(); url_values.insert("location".to_string(), "".to_string()); let mut target_url = self.endpoint_url.clone(); let scheme = self.endpoint_url.scheme(); let h = target_url.host().expect("host is none."); let default_port = if scheme == "https" { 443 } else { 80 }; let p = target_url.port().unwrap_or(default_port); let is_virtual_style = self.is_virtual_host_style_request(&target_url, bucket_name); let mut url_str: String = "".to_string(); if is_virtual_style { url_str = scheme.to_string(); url_str.push_str("://"); url_str.push_str(bucket_name); url_str.push_str("."); url_str.push_str( target_url .host_str() .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, "host is none"))?, ); url_str.push_str("/?location"); } else { let mut path = bucket_name.to_string(); path.push_str("/"); target_url.set_path(&path); { let mut q = target_url.query_pairs_mut(); for (k, v) in url_values { q.append_pair(&k, &urlencoding::encode(&v)); } } url_str = target_url.to_string(); } let Ok(mut req) = Request::builder() .method(http::Method::GET) .uri(url_str) .body(s3s::Body::empty()) else { return Err(std::io::Error::other("create request error")); }; self.set_user_agent(&mut req); let value; { if let Ok(mut creds_provider) = self.creds_provider.lock() { value = match creds_provider.get_with_context(Some(self.cred_context())) { Ok(v) => v, Err(err) => { return Err(std::io::Error::other(err)); } }; } else { return Err(std::io::Error::other("Failed to acquire credentials provider lock")); } } let mut signer_type = value.signer_type.clone(); let mut access_key_id = value.access_key_id; let mut secret_access_key = value.secret_access_key; let mut session_token = value.session_token; if self.override_signer_type != SignatureType::SignatureDefault { signer_type = self.override_signer_type.clone(); } if value.signer_type == SignatureType::SignatureAnonymous { signer_type = SignatureType::SignatureAnonymous } if signer_type == SignatureType::SignatureAnonymous { return Ok(req); } if signer_type == SignatureType::SignatureV2 { let req = rustfs_signer::sign_v2(req, 0, &access_key_id, &secret_access_key, is_virtual_style); return Ok(req); } let mut content_sha256 = EMPTY_STRING_SHA256_HASH.to_string(); if self.secure { content_sha256 = UNSIGNED_PAYLOAD.to_string(); } let content_sha256_value = content_sha256.parse().map_err(|err| { std::io::Error::new( std::io::ErrorKind::InvalidInput, format!("invalid X-Amz-Content-Sha256 header value: {err}"), ) })?; req.headers_mut().insert("X-Amz-Content-Sha256", content_sha256_value); let req = rustfs_signer::try_sign_v4(req, 0, &access_key_id, &secret_access_key, &session_token, "us-east-1") .map_err(|err| signer_error_to_io_error("failed to sign bucket location request", err))?; Ok(req) } } async fn process_bucket_location_response( mut resp: http::Response, bucket_name: &str, tier_type: &str, ) -> Result { //if resp != nil { if resp.status() != StatusCode::OK { let resp_status = resp.status(); let h = resp.headers().clone(); let err_resp = http_resp_to_error_response(resp_status, &h, vec![], bucket_name, ""); match err_resp.code { S3ErrorCode::NotImplemented => { match err_resp.server.as_str() { "AmazonSnowball" => { return Ok("snowball".to_string()); } "cloudflare" => { return Ok("us-east-1".to_string()); } _ => { return Err(std::io::Error::other(err_resp)); } } } S3ErrorCode::AuthorizationHeaderMalformed | //S3ErrorCode::InvalidRegion | S3ErrorCode::AccessDenied => { if err_resp.region == "" { return Ok("us-east-1".to_string()); } return Ok(err_resp.region); } _ => { return Err(std::io::Error::other(err_resp)); } } } //} let mut body_vec = Vec::new(); let mut body = resp.into_body(); while let Some(frame) = body.frame().await { let frame = frame.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; if let Some(data) = frame.data_ref() { body_vec.extend_from_slice(data); } } let mut location = "".to_string(); if tier_type == "huaweicloud" { if let Ok(body_str) = String::from_utf8(body_vec) { if let Ok(d) = quick_xml::de::from_str::(&body_str) { location = d.location_constraint; } } } else { if let Ok(body_str) = String::from_utf8(body_vec) { if let Ok(LocationConstraint { field }) = quick_xml::de::from_str::(&body_str) { location = field; } } } //debug!("location: {}", location); if location == "" { location = "us-east-1".to_string(); } if location == "EU" { location = "eu-west-1".to_string(); } Ok(location) }