From 3d7cce5dbbac513217a9cf1ad813dac2da0f9754 Mon Sep 17 00:00:00 2001 From: durch Date: Sat, 31 Aug 2024 22:41:13 +0200 Subject: [PATCH] Bump dependencies --- s3/Cargo.toml | 83 ++++++-------- s3/src/bucket.rs | 44 +++----- s3/src/error.rs | 12 +- s3/src/request/tokio_backend.rs | 188 ++++++++++---------------------- s3/src/utils/mod.rs | 104 ++++++++++++++++++ 5 files changed, 223 insertions(+), 208 deletions(-) diff --git a/s3/Cargo.toml b/s3/Cargo.toml index 80b1d22180..786a31b209 100644 --- a/s3/Cargo.toml +++ b/s3/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-s3" -version = "0.36.0-alpha.1" +version = "0.36.0-alpha.2" authors = ["Drazen Urch"] description = "Rust library for working with AWS S3 and compatible object storage APIs" repository = "https://github.com/durch/rust-s3" @@ -44,77 +44,68 @@ async-std = { version = "1", optional = true } async-trait = "0.1" attohttpc = { version = "0.28", optional = true, default-features = false } # aws-creds = { version = "*", path = "../aws-creds", default-features = false } -# aws-region = { version = "0.25.4", path = "../aws-region" } -aws-region = "0.25" aws-creds = { version = "0.38", default-features = false } +# aws-region = { version = "*", path = "../aws-region" } +aws-region = "0.26" base64 = "0.22" +block_on_proc = { version = "0.2", optional = true } +bytes = { version = "1.2" } cfg-if = "1" -time = { version = "^0.3.6", features = ["formatting", "macros"] } futures = { version = "0.3", optional = true } futures-io = { version = "0.3", optional = true } futures-util = { version = "0.3", optional = true, features = ["io"] } hex = "0.4" hmac = "0.12" -http = "0.2" -hyper = { version = "0.14", default-features = false, features = [ - "client", - "http1", - "stream", - "tcp", -], optional = true } -hyper-tls = { version = "0.5.0", default-features = false, optional = true } -hyper-rustls = { version = "0.24", default-features = false, optional = true } -rustls = { version = "0.21", optional = true } -tokio-rustls = { version = "0.24.1", optional = true } -rustls-native-certs = { version = "0.6.3", optional = true } +http = "1" log = "0.4" maybe-async = { version = "0.2" } md5 = "0.7" +minidom = { version = "0.16", optional = true } percent-encoding = "2" +quick-xml = { version = "0.36", features = ["serialize"] } +reqwest = { version = "0.12", optional = true, features = ["stream",], default-features = false } serde = "1" -serde_json = "1" serde_derive = "1" -quick-xml = { version = "0.32", features = ["serialize"] } +serde_json = "1" sha2 = "0.10" +surf = { version = "2", optional = true, default-features = false, features = ["h1-client-rustls",] } thiserror = "1" -surf = { version = "2", optional = true, default-features = false, features = [ - "h1-client-rustls", -] } -tokio = { version = "1", features = [ - "io-util", -], optional = true, default-features = false } +time = { version = "^0.3.6", features = ["formatting", "macros"], default-features = false} +tokio = { version = "1", features = ["io-util",], optional = true, default-features = false } tokio-native-tls = { version = "0.3", optional = true } -native-tls = { version = "0.2", optional = true } +tokio-rustls = { version = "0.26", optional = true } tokio-stream = { version = "0.1", optional = true } url = "2" -minidom = { version = "0.15", optional = true } -bytes = { version = "1.2" } -block_on_proc = { version = "0.2", optional = true } [features] -default = ["tags", "use-tokio-native-tls", "fail-on-err"] -use-tokio-native-tls = [ - "with-tokio", - "aws-creds/native-tls", - "tokio-native-tls", - "hyper-tls", - "native-tls", -] -with-tokio = ["hyper", "tokio", "tokio/fs", "tokio-stream", "futures"] -async-std-native-tls = ["with-async-std", "aws-creds/native-tls"] -http-credentials = ["aws-creds/http-credentials"] -with-async-std = ["async-std", "surf", "futures-io", "futures-util", "futures"] +default = ["tags", "tokio-native-tls", "fail-on-err"] + sync = ["attohttpc", "maybe-async/is_sync"] -no-verify-ssl = [] +with-async-std = ["async-std", "surf", "futures-io", "futures-util", "futures"] +with-tokio = ["reqwest", "tokio", "tokio/fs", "tokio-stream", "futures"] + +blocking = ["block_on_proc", "tokio/rt", "tokio/rt-multi-thread"] fail-on-err = [] -tokio-rustls-tls = [ +no-verify-ssl = [] +tags = ["minidom"] + +http-credentials = ["aws-creds/http-credentials"] + +tokio-native-tls = [ + "aws-creds/native-tls", + "reqwest/native-tls", + "dep:tokio-native-tls", "with-tokio", +] +tokio-rustls-tls = [ "aws-creds/rustls-tls", + "reqwest/rustls-tls", "tokio-rustls", - "hyper-rustls", - "rustls", - "rustls-native-certs", + "with-tokio", ] + +async-std-native-tls = ["with-async-std", "aws-creds/native-tls"] + sync-native-tls = ["sync", "aws-creds/native-tls", "attohttpc/tls"] sync-native-tls-vendored = [ "sync", @@ -122,8 +113,6 @@ sync-native-tls-vendored = [ "attohttpc/tls-vendored", ] sync-rustls-tls = ["sync", "aws-creds/rustls-tls", "attohttpc/tls-rustls"] -blocking = ["block_on_proc", "tokio/rt", "tokio/rt-multi-thread"] -tags = ["minidom"] [dev-dependencies] tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "fs"] } diff --git a/s3/src/bucket.rs b/s3/src/bucket.rs index 419ec6fe4b..f14cf7014b 100644 --- a/s3/src/bucket.rs +++ b/s3/src/bucket.rs @@ -9,13 +9,11 @@ use crate::bucket_ops::{BucketConfiguration, CreateBucketResponse}; use crate::command::{Command, Multipart}; use crate::creds::Credentials; use crate::region::Region; -#[cfg(any(feature = "with-tokio", feature = "use-tokio-native-tls"))] +#[cfg(any(feature = "with-tokio", feature = "tokio-native-tls"))] use crate::request::tokio_backend::client; -#[cfg(any(feature = "use-tokio-native-tls", feature = "tokio-rustls-tls"))] -use crate::request::tokio_backend::HttpsConnector; -use crate::request::ResponseData; #[cfg(any(feature = "with-tokio", feature = "with-async-std"))] use crate::request::ResponseDataStream; +use crate::request::{Request as _, ResponseData}; use std::str::FromStr; use std::sync::Arc; @@ -33,7 +31,7 @@ pub type Query = HashMap; #[cfg(feature = "with-async-std")] use crate::request::async_std_backend::SurfRequest as RequestImpl; #[cfg(feature = "with-tokio")] -use crate::request::tokio_backend::HyperRequest as RequestImpl; +use crate::request::tokio_backend::ReqwestRequest as RequestImpl; #[cfg(feature = "with-async-std")] use async_std::io::Write as AsyncWrite; @@ -52,7 +50,6 @@ use async_std::io::Read as AsyncRead; use crate::error::S3Error; use crate::post_policy::PresignedPost; -use crate::request::Request; use crate::serde_types::{ BucketLifecycleConfiguration, BucketLocationResult, CompleteMultipartUploadData, CorsConfiguration, HeadObjectResult, InitiateMultipartUploadResponse, ListBucketResult, @@ -108,14 +105,14 @@ pub struct Bucket { pub request_timeout: Option, path_style: bool, listobjects_v2: bool, - #[cfg(any(feature = "use-tokio-native-tls", feature = "tokio-rustls-tls"))] - http_client: Arc>>, + #[cfg(any(feature = "tokio-native-tls", feature = "tokio-rustls-tls"))] + http_client: Arc, #[cfg(all( feature = "with-tokio", - not(feature = "use-tokio-native-tls"), + not(feature = "tokio-native-tls"), not(feature = "tokio-rustls-tls") ))] - http_client: Arc>, + http_client: Arc, } impl Bucket { @@ -134,17 +131,8 @@ impl Bucket { } } - #[cfg(all( - feature = "with-tokio", - not(feature = "use-tokio-native-tls"), - not(feature = "tokio-rustls-tls") - ))] - pub fn http_client(&self) -> Arc> { - Arc::clone(&self.http_client) - } - - #[cfg(any(feature = "use-tokio-native-tls", feature = "tokio-rustls-tls"))] - pub fn http_client(&self) -> Arc>> { + #[cfg(feature = "with-tokio")] + pub fn http_client(&self) -> Arc { Arc::clone(&self.http_client) } } @@ -588,7 +576,7 @@ impl Bucket { request_timeout: DEFAULT_REQUEST_TIMEOUT, path_style: false, listobjects_v2: true, - #[cfg(any(feature = "use-tokio-native-tls", feature = "with-tokio"))] + #[cfg(any(feature = "tokio-native-tls", feature = "with-tokio"))] http_client: Arc::new(client(DEFAULT_REQUEST_TIMEOUT)?), })) } @@ -614,7 +602,7 @@ impl Bucket { request_timeout: DEFAULT_REQUEST_TIMEOUT, path_style: false, listobjects_v2: true, - #[cfg(any(feature = "use-tokio-native-tls", feature = "with-tokio"))] + #[cfg(any(feature = "tokio-native-tls", feature = "with-tokio"))] http_client: Arc::new(client(DEFAULT_REQUEST_TIMEOUT)?), }) } @@ -629,7 +617,7 @@ impl Bucket { request_timeout: self.request_timeout, path_style: true, listobjects_v2: self.listobjects_v2, - #[cfg(any(feature = "use-tokio-native-tls", feature = "with-tokio"))] + #[cfg(any(feature = "tokio-native-tls", feature = "with-tokio"))] http_client: self.http_client.clone(), }) } @@ -644,7 +632,7 @@ impl Bucket { request_timeout: self.request_timeout, path_style: self.path_style, listobjects_v2: self.listobjects_v2, - #[cfg(any(feature = "use-tokio-native-tls", feature = "with-tokio"))] + #[cfg(any(feature = "tokio-native-tls", feature = "with-tokio"))] http_client: self.http_client.clone(), }) } @@ -662,7 +650,7 @@ impl Bucket { request_timeout: self.request_timeout, path_style: self.path_style, listobjects_v2: self.listobjects_v2, - #[cfg(any(feature = "use-tokio-native-tls", feature = "with-tokio"))] + #[cfg(any(feature = "tokio-native-tls", feature = "with-tokio"))] http_client: self.http_client.clone(), }) } @@ -677,7 +665,7 @@ impl Bucket { request_timeout: Some(request_timeout), path_style: self.path_style, listobjects_v2: self.listobjects_v2, - #[cfg(any(feature = "use-tokio-native-tls", feature = "with-tokio"))] + #[cfg(any(feature = "tokio-native-tls", feature = "with-tokio"))] http_client: Arc::new(client(Some(request_timeout))?), })) } @@ -692,7 +680,7 @@ impl Bucket { request_timeout: self.request_timeout, path_style: self.path_style, listobjects_v2: false, - #[cfg(any(feature = "use-tokio-native-tls", feature = "with-tokio"))] + #[cfg(any(feature = "tokio-native-tls", feature = "with-tokio"))] http_client: self.http_client.clone(), } } diff --git a/s3/src/error.rs b/s3/src/error.rs index 2557f44021..8e8e87f403 100644 --- a/s3/src/error.rs +++ b/s3/src/error.rs @@ -25,11 +25,12 @@ pub enum S3Error { #[error("http: {0}")] Http(#[from] http::Error), #[cfg(feature = "with-tokio")] - #[error("hyper: {0}")] - Hyper(#[from] hyper::Error), - #[cfg(feature = "use-tokio-native-tls")] - #[error("native-tls: {0}")] - NativeTls(#[from] native_tls::Error), + #[error("reqwest: {0}")] + Reqwest(#[from] reqwest::Error), + #[cfg(feature = "with-tokio")] + #[error("reqwest: {0}")] + ReqwestHeaderToStr(#[from] reqwest::header::ToStrError), + #[cfg(feature = "with-async-std")] #[error("header to string: {0}")] HeaderToStr(#[from] http::header::ToStrError), #[error("from utf8: {0}")] @@ -38,6 +39,7 @@ pub enum S3Error { SerdeXml(#[from] quick_xml::de::DeError), #[error("invalid header value: {0}")] InvalidHeaderValue(#[from] http::header::InvalidHeaderValue), + #[cfg(feature = "with-async-std")] #[error("invalid header name: {0}")] InvalidHeaderName(#[from] http::header::InvalidHeaderName), #[cfg(feature = "with-async-std")] diff --git a/s3/src/request/tokio_backend.rs b/s3/src/request/tokio_backend.rs index f3fdd12f19..d833b99cd8 100644 --- a/s3/src/request/tokio_backend.rs +++ b/s3/src/request/tokio_backend.rs @@ -3,10 +3,9 @@ extern crate md5; use bytes::Bytes; use futures::TryStreamExt; -use hyper::client::HttpConnector; -use hyper::{Body, Client}; use maybe_async::maybe_async; use std::collections::HashMap; +use std::str::FromStr as _; use time::OffsetDateTime; use super::request_trait::{Request, ResponseData, ResponseDataStream}; @@ -18,98 +17,30 @@ use crate::utils::now_utc; use tokio_stream::StreamExt; -#[cfg(feature = "tokio-rustls-tls")] -pub use hyper_rustls::HttpsConnector; -#[cfg(feature = "use-tokio-native-tls")] -pub use hyper_tls::HttpsConnector; +#[cfg(feature = "with-tokio")] +pub fn client(request_timeout: Option) -> Result { + let client = reqwest::Client::builder(); -#[cfg(feature = "use-tokio-native-tls")] -pub fn client( - request_timeout: Option, -) -> Result>, S3Error> { - let mut tls_connector_builder = native_tls::TlsConnector::builder(); + let client = if let Some(timeout) = request_timeout { + client.timeout(timeout) + } else { + client + }; - if cfg!(feature = "no-verify-ssl") { - tls_connector_builder.danger_accept_invalid_hostnames(true); - tls_connector_builder.danger_accept_invalid_certs(true); - } - - let tls_connector = tokio_native_tls::TlsConnector::from(tls_connector_builder.build()?); - - let mut http_connector = HttpConnector::new(); - http_connector.set_connect_timeout(request_timeout); - http_connector.enforce_http(false); - let https_connector = HttpsConnector::from((http_connector, tls_connector)); - - Ok(Client::builder().build::<_, hyper::Body>(https_connector)) -} - -#[cfg(all( - feature = "with-tokio", - not(feature = "use-tokio-native-tls"), - not(feature = "tokio-rustls-tls") -))] -pub fn client( - request_timeout: Option, -) -> Result, S3Error> { - let mut http_connector = HttpConnector::new(); - http_connector.set_connect_timeout(request_timeout); - http_connector.enforce_http(false); - - Ok(Client::builder().build::<_, hyper::Body>(http_connector)) -} - -#[cfg(all(feature = "tokio-rustls-tls", feature = "no-verify-ssl"))] -pub struct NoCertificateVerification {} -#[cfg(all(feature = "tokio-rustls-tls", feature = "no-verify-ssl"))] -impl rustls::client::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _end_entity: &rustls::Certificate, - _intermediates: &[rustls::Certificate], - _server_name: &rustls::ServerName, - _scts: &mut dyn Iterator, - _ocsp: &[u8], - _now: std::time::SystemTime, - ) -> Result { - Ok(rustls::client::ServerCertVerified::assertion()) - } -} - -#[cfg(feature = "tokio-rustls-tls")] -pub fn client( - request_timeout: Option, -) -> Result>, S3Error> { - let mut roots = rustls::RootCertStore::empty(); - rustls_native_certs::load_native_certs()? - .into_iter() - .for_each(|cert| { - roots.add(&rustls::Certificate(cert.0)).unwrap(); - }); - - #[allow(unused_mut)] - let mut config = rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(roots) - .with_no_client_auth(); - - #[cfg(feature = "no-verify-ssl")] - { - let mut dangerous_config = rustls::ClientConfig::dangerous(&mut config); - dangerous_config - .set_certificate_verifier(std::sync::Arc::new(NoCertificateVerification {})); + cfg_if::cfg_if! { + if #[cfg(any(feature = "tokio-native-tls", feature = "tokio-rustls-tls"))] { + let client = if cfg!(feature = "no-verify-ssl") { + client.danger_accept_invalid_certs(true) + } else { + client + }; + } } - let mut http_connector = HttpConnector::new(); - http_connector.set_connect_timeout(request_timeout); - http_connector.enforce_http(false); - let https_connector = HttpsConnector::from((http_connector, config)); - - Ok(Client::builder().build::<_, hyper::Body>(https_connector)) + Ok(client.build()?) } - // Temporary structure for making a request -pub struct HyperRequest<'a> { +pub struct ReqwestRequest<'a> { pub bucket: &'a Bucket, pub path: &'a str, pub command: Command<'a>, @@ -118,44 +49,45 @@ pub struct HyperRequest<'a> { } #[maybe_async] -impl<'a> Request for HyperRequest<'a> { - type Response = http::Response; - type HeaderMap = http::header::HeaderMap; - - async fn response(&self) -> Result, S3Error> { - // Build headers - let headers = match self.headers().await { - Ok(headers) => headers, - Err(e) => return Err(e), - }; +impl<'a> Request for ReqwestRequest<'a> { + type Response = reqwest::Response; + type HeaderMap = reqwest::header::HeaderMap; + + async fn response(&self) -> Result { + let headers = self + .headers() + .await? + .iter() + .map(|(k, v)| { + ( + reqwest::header::HeaderName::from_str(k.as_str()), + reqwest::header::HeaderValue::from_str(v.to_str().unwrap_or_default()), + ) + }) + .filter(|(k, v)| k.is_ok() && v.is_ok()) + .map(|(k, v)| (k.unwrap(), v.unwrap())) + .collect(); let client = self.bucket.http_client(); let method = match self.command.http_verb() { - HttpMethod::Delete => http::Method::DELETE, - HttpMethod::Get => http::Method::GET, - HttpMethod::Post => http::Method::POST, - HttpMethod::Put => http::Method::PUT, - HttpMethod::Head => http::Method::HEAD, + HttpMethod::Delete => reqwest::Method::DELETE, + HttpMethod::Get => reqwest::Method::GET, + HttpMethod::Post => reqwest::Method::POST, + HttpMethod::Put => reqwest::Method::PUT, + HttpMethod::Head => reqwest::Method::HEAD, }; - let request = { - let mut request = http::Request::builder() - .method(method) - .uri(self.url()?.as_str()); + let request = client + .request(method, self.url()?.as_str()) + .headers(headers) + .body(self.request_body()?); - for (header, value) in headers.iter() { - request = request.header(header, value); - } - - request.body(Body::from(self.request_body()?))? - }; - let response = client.request(request).await?; + let response = client.execute(request.build()?).await?; if cfg!(feature = "fail-on-err") && !response.status().is_success() { let status = response.status().as_u16(); - let text = - String::from_utf8(hyper::body::to_bytes(response.into_body()).await?.into())?; + let text = response.text().await?; return Err(S3Error::HttpFailWithBody(status, text)); } @@ -185,7 +117,7 @@ impl<'a> Request for HyperRequest<'a> { Bytes::from("") } } else { - hyper::body::to_bytes(response.into_body()).await? + response.bytes().await? }; Ok(ResponseData::new(body_vec, status_code, response_headers)) } @@ -198,7 +130,7 @@ impl<'a> Request for HyperRequest<'a> { let response = self.response().await?; let status_code = response.status(); - let mut stream = response.into_body().into_stream(); + let mut stream = response.bytes_stream(); while let Some(item) = stream.next().await { writer.write_all(&item?).await?; @@ -210,7 +142,7 @@ impl<'a> Request for HyperRequest<'a> { async fn response_data_to_stream(&self) -> Result { let response = self.response().await?; let status_code = response.status(); - let stream = response.into_body().into_stream().map_err(S3Error::Hyper); + let stream = response.bytes_stream().map_err(S3Error::Reqwest); Ok(ResponseDataStream { bytes: Box::pin(stream), @@ -242,12 +174,12 @@ impl<'a> Request for HyperRequest<'a> { } } -impl<'a> HyperRequest<'a> { +impl<'a> ReqwestRequest<'a> { pub async fn new( bucket: &'a Bucket, path: &'a str, command: Command<'a>, - ) -> Result, S3Error> { + ) -> Result, S3Error> { bucket.credentials_refresh().await?; Ok(Self { bucket, @@ -263,7 +195,7 @@ impl<'a> HyperRequest<'a> { mod tests { use crate::bucket::Bucket; use crate::command::Command; - use crate::request::tokio_backend::HyperRequest; + use crate::request::tokio_backend::ReqwestRequest; use crate::request::Request; use awscreds::Credentials; use http::header::{HOST, RANGE}; @@ -281,7 +213,7 @@ mod tests { let region = "custom-region".parse().unwrap(); let bucket = Bucket::new("my-first-bucket", region, fake_credentials()).unwrap(); let path = "/my-first/path"; - let request = HyperRequest::new(&bucket, path, Command::GetObject) + let request = ReqwestRequest::new(&bucket, path, Command::GetObject) .await .unwrap(); @@ -300,7 +232,7 @@ mod tests { .unwrap() .with_path_style(); let path = "/my-first/path"; - let request = HyperRequest::new(&bucket, path, Command::GetObject) + let request = ReqwestRequest::new(&bucket, path, Command::GetObject) .await .unwrap(); @@ -317,7 +249,7 @@ mod tests { let region = "http://custom-region".parse().unwrap(); let bucket = Bucket::new("my-second-bucket", region, fake_credentials()).unwrap(); let path = "/my-second/path"; - let request = HyperRequest::new(&bucket, path, Command::GetObject) + let request = ReqwestRequest::new(&bucket, path, Command::GetObject) .await .unwrap(); @@ -335,7 +267,7 @@ mod tests { .unwrap() .with_path_style(); let path = "/my-second/path"; - let request = HyperRequest::new(&bucket, path, Command::GetObject) + let request = ReqwestRequest::new(&bucket, path, Command::GetObject) .await .unwrap(); @@ -354,7 +286,7 @@ mod tests { .with_path_style(); let path = "/my-second/path"; - let request = HyperRequest::new( + let request = ReqwestRequest::new( &bucket, path, Command::GetObjectRange { @@ -368,7 +300,7 @@ mod tests { let range = headers.get(RANGE).unwrap(); assert_eq!(range, "bytes=0-"); - let request = HyperRequest::new( + let request = ReqwestRequest::new( &bucket, path, Command::GetObjectRange { diff --git a/s3/src/utils/mod.rs b/s3/src/utils/mod.rs index f55a0be171..d422d6c7e4 100644 --- a/s3/src/utils/mod.rs +++ b/s3/src/utils/mod.rs @@ -108,6 +108,7 @@ impl GetAndConvertHeaders for http::header::HeaderMap { } } +#[cfg(feature = "with-async-std")] impl From<&http::HeaderMap> for HeadObjectResult { fn from(headers: &http::HeaderMap) -> Self { let mut result = HeadObjectResult { @@ -159,6 +160,109 @@ impl From<&http::HeaderMap> for HeadObjectResult { } } +#[cfg(feature = "with-tokio")] +impl From<&reqwest::header::HeaderMap> for HeadObjectResult { + fn from(headers: &reqwest::header::HeaderMap) -> Self { + let mut result = HeadObjectResult { + accept_ranges: headers + .get("accept-ranges") + .map(|v| v.to_str().unwrap_or_default().to_string()), + cache_control: headers + .get("Cache-Control") + .map(|v| v.to_str().unwrap_or_default().to_string()), + content_disposition: headers + .get("Content-Disposition") + .map(|v| v.to_str().unwrap_or_default().to_string()), + content_encoding: headers + .get("Content-Encoding") + .map(|v| v.to_str().unwrap_or_default().to_string()), + content_language: headers + .get("Content-Language") + .map(|v| v.to_str().unwrap_or_default().to_string()), + content_length: headers + .get("Content-Length") + .map(|v| v.to_str().unwrap_or_default().parse().unwrap_or_default()), + content_type: headers + .get("Content-Type") + .map(|v| v.to_str().unwrap_or_default().to_string()), + delete_marker: headers + .get("x-amz-delete-marker") + .map(|v| v.to_str().unwrap_or_default().parse().unwrap_or_default()), + e_tag: headers + .get("ETag") + .map(|v| v.to_str().unwrap_or_default().to_string()), + expiration: headers + .get("x-amz-expiration") + .map(|v| v.to_str().unwrap_or_default().to_string()), + expires: headers + .get("Expires") + .map(|v| v.to_str().unwrap_or_default().to_string()), + last_modified: headers + .get("Last-Modified") + .map(|v| v.to_str().unwrap_or_default().to_string()), + ..Default::default() + }; + let mut values = ::std::collections::HashMap::new(); + for (key, value) in headers.iter() { + if key.as_str().starts_with("x-amz-meta-") { + if let Ok(value) = value.to_str() { + values.insert( + key.as_str()["x-amz-meta-".len()..].to_owned(), + value.to_owned(), + ); + } + } + } + result.metadata = Some(values); + result.missing_meta = headers + .get("x-amz-missing-meta") + .map(|v| v.to_str().unwrap_or_default().parse().unwrap_or_default()); + result.object_lock_legal_hold_status = headers + .get("x-amz-object-lock-legal-hold") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.object_lock_mode = headers + .get("x-amz-object-lock-mode") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.object_lock_retain_until_date = headers + .get("x-amz-object-lock-retain-until-date") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.parts_count = headers + .get("x-amz-mp-parts-count") + .map(|v| v.to_str().unwrap_or_default().parse().unwrap_or_default()); + result.replication_status = headers + .get("x-amz-replication-status") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.request_charged = headers + .get("x-amz-request-charged") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.restore = headers + .get("x-amz-restore") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.sse_customer_algorithm = headers + .get("x-amz-server-side-encryption-customer-algorithm") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.sse_customer_key_md5 = headers + .get("x-amz-server-side-encryption-customer-key-MD5") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.ssekms_key_id = headers + .get("x-amz-server-side-encryption-aws-kms-key-id") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.server_side_encryption = headers + .get("x-amz-server-side-encryption") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.storage_class = headers + .get("x-amz-storage-class") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.version_id = headers + .get("x-amz-version-id") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result.website_redirect_location = headers + .get("x-amz-website-redirect-location") + .map(|v| v.to_str().unwrap_or_default().to_string()); + result + } +} + #[cfg(feature = "sync")] impl From<&attohttpc::header::HeaderMap> for HeadObjectResult { fn from(headers: &attohttpc::header::HeaderMap) -> Self {