Skip to content

Commit

Permalink
Properly implement CORS rule management
Browse files Browse the repository at this point in the history
  • Loading branch information
durch committed Sep 2, 2024
1 parent 4d9e247 commit f580f9e
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 25 deletions.
55 changes: 47 additions & 8 deletions s3/src/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -991,12 +991,41 @@ impl Bucket {
#[maybe_async::maybe_async]
pub async fn put_bucket_cors(
&self,
cors_config: CorsConfiguration,
expected_bucket_owner: &str,
cors_config: &CorsConfiguration,
) -> Result<ResponseData, S3Error> {
let command = Command::PutBucketCors {
configuration: cors_config,
expected_bucket_owner: expected_bucket_owner.to_string(),
configuration: cors_config.clone(),
};
let request = RequestImpl::new(self, "?cors", command).await?;
let request = RequestImpl::new(self, "", command).await?;
request.response_data(false).await
}

#[maybe_async::maybe_async]
pub async fn get_bucket_cors(
&self,
expected_bucket_owner: &str,
) -> Result<CorsConfiguration, S3Error> {
let command = Command::GetBucketCors {
expected_bucket_owner: expected_bucket_owner.to_string(),
};
let request = RequestImpl::new(self, "", command).await?;
let response = request.response_data(false).await?;
Ok(quick_xml::de::from_str::<CorsConfiguration>(
response.as_str()?,
)?)
}

#[maybe_async::maybe_async]
pub async fn delete_bucket_cors(
&self,
expected_bucket_owner: &str,
) -> Result<ResponseData, S3Error> {
let command = Command::DeleteBucketCors {
expected_bucket_owner: expected_bucket_owner.to_string(),
};
let request = RequestImpl::new(self, "", command).await?;
request.response_data(false).await
}

Expand Down Expand Up @@ -2802,8 +2831,8 @@ mod test {
assert_eq!(response_data.status_code(), 206);
assert_eq!(test[100..1001].to_vec(), response_data.as_slice());
if head {
let (head_object_result, code) = bucket.head_object(s3_path).await.unwrap();
println!("{:?}", head_object_result);
let (_head_object_result, code) = bucket.head_object(s3_path).await.unwrap();
// println!("{:?}", head_object_result);
assert_eq!(code, 200);
}

Expand Down Expand Up @@ -3529,7 +3558,7 @@ mod test {
)
)]
#[ignore]
async fn test_put_bucket_cors() {
async fn test_bucket_cors() {
let bucket = test_aws_bucket();
let rule = CorsRule::new(
None,
Expand All @@ -3539,8 +3568,18 @@ mod test {
None,
None,
);
let expected_bucket_owner = "904662384344";
let cors_config = CorsConfiguration::new(vec![rule]);
let response = bucket.put_bucket_cors(cors_config).await.unwrap();
assert_eq!(response.status_code(), 200)
let response = bucket
.put_bucket_cors(expected_bucket_owner, &cors_config)
.await
.unwrap();
assert_eq!(response.status_code(), 200);

let cors_response = bucket.get_bucket_cors(expected_bucket_owner).await.unwrap();
assert_eq!(cors_response, cors_config);

let response = bucket.delete_bucket_cors(expected_bucket_owner).await.unwrap();
assert_eq!(response.status_code(), 204);
}
}
102 changes: 96 additions & 6 deletions s3/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,16 @@ pub enum Command<'a> {
},
DeleteBucket,
ListBuckets,
GetBucketCors {
expected_bucket_owner: String,
},
PutBucketCors {
expected_bucket_owner: String,
configuration: CorsConfiguration,
},
DeleteBucketCors {
expected_bucket_owner: String,
},
GetBucketLifecycle,
PutBucketLifecycle {
configuration: BucketLifecycleConfiguration,
Expand All @@ -165,6 +172,7 @@ impl<'a> Command<'a> {
match *self {
Command::GetObject
| Command::GetObjectTorrent
| Command::GetBucketCors { .. }
| Command::GetObjectRange { .. }
| Command::ListBuckets
| Command::ListObjects { .. }
Expand All @@ -187,6 +195,7 @@ impl<'a> Command<'a> {
| Command::AbortMultipartUpload { .. }
| Command::PresignDelete { .. }
| Command::DeleteBucket
| Command::DeleteBucketCors { .. }
| Command::DeleteBucketLifecycle => HttpMethod::Delete,
Command::InitiateMultipartUpload { .. } | Command::CompleteMultipartUpload { .. } => {
HttpMethod::Post
Expand All @@ -212,7 +221,31 @@ impl<'a> Command<'a> {
Command::PutBucketLifecycle { configuration } => {
quick_xml::se::to_string(configuration)?.as_bytes().len()
}
_ => 0,
Command::PutBucketCors { configuration, .. } => {
configuration.to_string().as_bytes().len()
}
Command::HeadObject => 0,
Command::DeleteObject => 0,
Command::DeleteObjectTagging => 0,
Command::GetObject => 0,
Command::GetObjectTorrent => 0,
Command::GetObjectRange { .. } => 0,
Command::GetObjectTagging => 0,
Command::ListMultipartUploads { .. } => 0,
Command::ListObjects { .. } => 0,
Command::ListObjectsV2 { .. } => 0,
Command::GetBucketLocation => 0,
Command::PresignGet { .. } => 0,
Command::PresignPut { .. } => 0,
Command::PresignDelete { .. } => 0,
Command::InitiateMultipartUpload { .. } => 0,
Command::AbortMultipartUpload { .. } => 0,
Command::DeleteBucket => 0,
Command::ListBuckets => 0,
Command::GetBucketCors { .. } => 0,
Command::DeleteBucketCors { .. } => 0,
Command::GetBucketLifecycle => 0,
Command::DeleteBucketLifecycle { .. } => 0,
};
Ok(result)
}
Expand All @@ -221,10 +254,34 @@ impl<'a> Command<'a> {
match self {
Command::InitiateMultipartUpload { content_type } => content_type.to_string(),
Command::PutObject { content_type, .. } => content_type.to_string(),
Command::CompleteMultipartUpload { .. } | Command::PutBucketLifecycle { .. } => {
"application/xml".into()
}
_ => "text/plain".into(),
Command::CompleteMultipartUpload { .. }
| Command::PutBucketLifecycle { .. }
| Command::PutBucketCors { .. } => "application/xml".into(),
Command::HeadObject => "text/plain".into(),
Command::DeleteObject => "text/plain".into(),
Command::DeleteObjectTagging => "text/plain".into(),
Command::GetObject => "text/plain".into(),
Command::GetObjectTorrent => "text/plain".into(),
Command::GetObjectRange { .. } => "text/plain".into(),
Command::GetObjectTagging => "text/plain".into(),
Command::ListMultipartUploads { .. } => "text/plain".into(),
Command::ListObjects { .. } => "text/plain".into(),
Command::ListObjectsV2 { .. } => "text/plain".into(),
Command::GetBucketLocation => "text/plain".into(),
Command::PresignGet { .. } => "text/plain".into(),
Command::PresignPut { .. } => "text/plain".into(),
Command::PresignDelete { .. } => "text/plain".into(),
Command::AbortMultipartUpload { .. } => "text/plain".into(),
Command::DeleteBucket => "text/plain".into(),
Command::ListBuckets => "text/plain".into(),
Command::GetBucketCors { .. } => "text/plain".into(),
Command::DeleteBucketCors { .. } => "text/plain".into(),
Command::GetBucketLifecycle => "text/plain".into(),
Command::DeleteBucketLifecycle { .. } => "text/plain".into(),
Command::CopyObject { .. } => "text/plain".into(),
Command::PutObjectTagging { .. } => "text/plain".into(),
Command::UploadPart { .. } => "text/plain".into(),
Command::CreateBucket { .. } => "text/plain".into(),
}
}

Expand Down Expand Up @@ -254,7 +311,40 @@ impl<'a> Command<'a> {
EMPTY_PAYLOAD_SHA.into()
}
}
_ => EMPTY_PAYLOAD_SHA.into(),
Command::PutBucketLifecycle { configuration } => {
let mut sha = Sha256::default();
sha.update(quick_xml::se::to_string(configuration)?.as_bytes());
hex::encode(sha.finalize().as_slice())
}
Command::PutBucketCors { configuration, .. } => {
let mut sha = Sha256::default();
sha.update(configuration.to_string().as_bytes());
hex::encode(sha.finalize().as_slice())
}
Command::HeadObject => EMPTY_PAYLOAD_SHA.into(),
Command::DeleteObject => EMPTY_PAYLOAD_SHA.into(),
Command::DeleteObjectTagging => EMPTY_PAYLOAD_SHA.into(),
Command::GetObject => EMPTY_PAYLOAD_SHA.into(),
Command::GetObjectTorrent => EMPTY_PAYLOAD_SHA.into(),
Command::GetObjectRange { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::GetObjectTagging => EMPTY_PAYLOAD_SHA.into(),
Command::ListMultipartUploads { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::ListObjects { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::ListObjectsV2 { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::GetBucketLocation => EMPTY_PAYLOAD_SHA.into(),
Command::PresignGet { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::PresignPut { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::PresignDelete { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::AbortMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::DeleteBucket => EMPTY_PAYLOAD_SHA.into(),
Command::ListBuckets => EMPTY_PAYLOAD_SHA.into(),
Command::GetBucketCors { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::DeleteBucketCors { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::GetBucketLifecycle => EMPTY_PAYLOAD_SHA.into(),
Command::DeleteBucketLifecycle { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::CopyObject { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::UploadPart { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::InitiateMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(),
};
Ok(result)
}
Expand Down
16 changes: 10 additions & 6 deletions s3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,24 @@ pub fn get_retries() -> u8 {
#[cfg(not(feature = "disable-call-for-funding"))]
#[inline(always)]
pub(crate) fn init_once() {
use ansi_term::Colour::{Blue, Yellow};
use ansi_term::{
Colour::{Blue, Yellow},
Style,
};

if !INITIALIZED.load(std::sync::atomic::Ordering::Relaxed) {
INITIALIZED.store(true, std::sync::atomic::Ordering::SeqCst);
eprintln!(
" {0}------------------------------------------------------------------------------------------------{0}\n
Support {1} crate development by donating {2} to {3} \n
{0}--- {4} ---{0} \n
{0}------------------------------------------------------------------------------------------------{0}",
" {0}---------------------------------------------------------------------------------{0}\n
Support {1} crate -> {5} {2} to {3} \n
{0}--- {4} ---{0} \n
{0}---------------------------------------------------------------------------------{0}",
Yellow.bold().paint("<>"),
Yellow.bold().paint("rust-s3"),
Yellow.bold().paint("BTC"),
Yellow.bold().paint("bc1q7ukqe09zplg2sltgfrkukghpelfaz7qja8pw6u"),
Blue.bold().paint("Thank you!"),
Blue.bold().paint("Thank You!"),
Style::new().bold().underline().paint("donate"),
);
}
}
Expand Down
40 changes: 39 additions & 1 deletion s3/src/request/request_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ pub trait Request {
} else {
Vec::new()
}
} else if let Command::PutBucketLifecycle { configuration } = &self.command() {
} else if let Command::PutBucketLifecycle { configuration, .. } = &self.command() {
quick_xml::se::to_string(configuration)?.as_bytes().to_vec()
} else if let Command::PutBucketCors { configuration, .. } = &self.command() {
let cors = configuration.to_string();
cors.as_bytes().to_vec()
} else {
Vec::new()
};
Expand Down Expand Up @@ -395,6 +398,11 @@ pub trait Request {
| Command::DeleteBucketLifecycle => {
url_str.push_str("?lifecycle");
}
Command::GetBucketCors { .. }
| Command::PutBucketCors { .. }
| Command::DeleteBucketCors { .. } => {
url_str.push_str("?cors");
}
_ => {}
}

Expand Down Expand Up @@ -607,6 +615,36 @@ pub trait Request {
let hash = general_purpose::STANDARD.encode(digest.as_ref());
headers.insert(HeaderName::from_static("content-md5"), hash.parse()?);
headers.remove("x-amz-content-sha256");
} else if let Command::PutBucketCors {
expected_bucket_owner,
configuration,
..
} = self.command()
{
let digest = md5::compute(configuration.to_string().as_bytes());
let hash = general_purpose::STANDARD.encode(digest.as_ref());
headers.insert(HeaderName::from_static("content-md5"), hash.parse()?);

headers.insert(
HeaderName::from_static("x-amz-expected-bucket-owner"),
expected_bucket_owner.parse()?,
);
} else if let Command::GetBucketCors {
expected_bucket_owner,
} = self.command()
{
headers.insert(
HeaderName::from_static("x-amz-expected-bucket-owner"),
expected_bucket_owner.parse()?,
);
} else if let Command::DeleteBucketCors {
expected_bucket_owner,
} = self.command()
{
headers.insert(
HeaderName::from_static("x-amz-expected-bucket-owner"),
expected_bucket_owner.parse()?,
);
}

// This must be last, as it signs the other headers, omitted if no secret key is provided
Expand Down
4 changes: 3 additions & 1 deletion s3/src/request/tokio_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ impl<'a> Request for ReqwestRequest<'a> {
.headers(headers)
.body(self.request_body()?);

let response = client.execute(request.build()?).await?;
let request = request.build()?;

let response = client.execute(request).await?;

if cfg!(feature = "fail-on-err") && !response.status().is_success() {
let status = response.status().as_u16();
Expand Down
21 changes: 18 additions & 3 deletions s3/src/serde_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub struct MultipartUpload {
pub id: String,
}

use std::fmt;
use std::fmt::{self};

impl fmt::Display for CompleteMultipartUploadData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand Down Expand Up @@ -319,7 +319,7 @@ pub struct AwsError {
pub request_id: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename = "CORSConfiguration")]
pub struct CorsConfiguration {
#[serde(rename = "CORSRule")]
Expand All @@ -332,7 +332,21 @@ impl CorsConfiguration {
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
impl fmt::Display for CorsConfiguration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let cors = quick_xml::se::to_string(&self).map_err(|_| fmt::Error)?;
let preamble = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
let cors = format!("{}{}", preamble, cors);
let cors = cors.replace(
"<CORSConfiguration>",
"<CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">",
);

write!(f, "{}", cors)
}
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct CorsRule {
#[serde(rename = "AllowedHeader")]
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -344,6 +358,7 @@ pub struct CorsRule {
#[serde(rename = "ExposeHeader")]
#[serde(skip_serializing_if = "Option::is_none")]
expose_headers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "ID")]
id: Option<String>,
#[serde(rename = "MaxAgeSeconds")]
Expand Down

0 comments on commit f580f9e

Please sign in to comment.