use std::collections::HashMap;
use crate::error::S3Error;
use crate::serde_types::{
BucketLifecycleConfiguration, CompleteMultipartUploadData, CorsConfiguration,
};
use crate::EMPTY_PAYLOAD_SHA;
use sha2::{Digest, Sha256};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HttpMethod {
Delete,
Get,
Put,
Post,
Head,
}
use std::fmt;
impl fmt::Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HttpMethod::Delete => write!(f, "DELETE"),
HttpMethod::Get => write!(f, "GET"),
HttpMethod::Post => write!(f, "POST"),
HttpMethod::Put => write!(f, "PUT"),
HttpMethod::Head => write!(f, "HEAD"),
}
}
}
use crate::bucket_ops::BucketConfiguration;
use http::HeaderMap;
#[derive(Clone, Debug)]
pub struct Multipart<'a> {
part_number: u32,
upload_id: &'a str,
}
impl<'a> Multipart<'a> {
pub fn query_string(&self) -> String {
format!(
"?partNumber={}&uploadId={}",
self.part_number, self.upload_id
)
}
pub fn new(part_number: u32, upload_id: &'a str) -> Self {
Multipart {
part_number,
upload_id,
}
}
}
#[derive(Clone, Debug)]
pub enum Command<'a> {
HeadObject,
CopyObject {
from: &'a str,
},
DeleteObject,
DeleteObjectTagging,
GetObject,
GetObjectTorrent,
GetObjectRange {
start: u64,
end: Option<u64>,
},
GetObjectTagging,
PutObject {
content: &'a [u8],
content_type: &'a str,
custom_headers: Option<HeaderMap>,
multipart: Option<Multipart<'a>>,
},
PutObjectTagging {
tags: &'a str,
},
ListMultipartUploads {
prefix: Option<&'a str>,
delimiter: Option<&'a str>,
key_marker: Option<String>,
max_uploads: Option<usize>,
},
ListObjects {
prefix: String,
delimiter: Option<String>,
marker: Option<String>,
max_keys: Option<usize>,
},
ListObjectsV2 {
prefix: String,
delimiter: Option<String>,
continuation_token: Option<String>,
start_after: Option<String>,
max_keys: Option<usize>,
},
GetBucketLocation,
PresignGet {
expiry_secs: u32,
custom_queries: Option<HashMap<String, String>>,
},
PresignPut {
expiry_secs: u32,
custom_headers: Option<HeaderMap>,
custom_queries: Option<HashMap<String, String>>,
},
PresignDelete {
expiry_secs: u32,
},
InitiateMultipartUpload {
content_type: &'a str,
},
UploadPart {
part_number: u32,
content: &'a [u8],
upload_id: &'a str,
},
AbortMultipartUpload {
upload_id: &'a str,
},
CompleteMultipartUpload {
upload_id: &'a str,
data: CompleteMultipartUploadData,
},
CreateBucket {
config: BucketConfiguration,
},
DeleteBucket,
ListBuckets,
GetBucketCors {
expected_bucket_owner: String,
},
PutBucketCors {
expected_bucket_owner: String,
configuration: CorsConfiguration,
},
DeleteBucketCors {
expected_bucket_owner: String,
},
GetBucketLifecycle,
PutBucketLifecycle {
configuration: BucketLifecycleConfiguration,
},
DeleteBucketLifecycle,
GetObjectAttributes {
expected_bucket_owner: String,
version_id: Option<String>,
},
}
impl<'a> Command<'a> {
pub fn http_verb(&self) -> HttpMethod {
match *self {
Command::GetObject
| Command::GetObjectTorrent
| Command::GetBucketCors { .. }
| Command::GetObjectRange { .. }
| Command::ListBuckets
| Command::ListObjects { .. }
| Command::ListObjectsV2 { .. }
| Command::GetBucketLocation
| Command::GetObjectTagging
| Command::GetBucketLifecycle
| Command::ListMultipartUploads { .. }
| Command::PresignGet { .. } => HttpMethod::Get,
Command::PutObject { .. }
| Command::CopyObject { from: _ }
| Command::PutObjectTagging { .. }
| Command::PresignPut { .. }
| Command::UploadPart { .. }
| Command::PutBucketCors { .. }
| Command::CreateBucket { .. }
| Command::PutBucketLifecycle { .. } => HttpMethod::Put,
Command::DeleteObject
| Command::DeleteObjectTagging
| Command::AbortMultipartUpload { .. }
| Command::PresignDelete { .. }
| Command::DeleteBucket
| Command::DeleteBucketCors { .. }
| Command::DeleteBucketLifecycle => HttpMethod::Delete,
Command::InitiateMultipartUpload { .. } | Command::CompleteMultipartUpload { .. } => {
HttpMethod::Post
}
Command::HeadObject => HttpMethod::Head,
Command::GetObjectAttributes { .. } => HttpMethod::Get,
}
}
pub fn content_length(&self) -> Result<usize, S3Error> {
let result = match &self {
Command::CopyObject { from: _ } => 0,
Command::PutObject { content, .. } => content.len(),
Command::PutObjectTagging { tags } => tags.len(),
Command::UploadPart { content, .. } => content.len(),
Command::CompleteMultipartUpload { data, .. } => data.len(),
Command::CreateBucket { config } => {
if let Some(payload) = config.location_constraint_payload() {
Vec::from(payload).len()
} else {
0
}
}
Command::PutBucketLifecycle { configuration } => {
quick_xml::se::to_string(configuration)?.len()
}
Command::PutBucketCors { configuration, .. } => configuration.to_string().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,
Command::GetObjectAttributes { .. } => 0,
};
Ok(result)
}
pub fn content_type(&self) -> String {
match self {
Command::InitiateMultipartUpload { content_type } => content_type.to_string(),
Command::PutObject { content_type, .. } => content_type.to_string(),
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(),
Command::GetObjectAttributes { .. } => "text/plain".into(),
}
}
pub fn sha256(&self) -> Result<String, S3Error> {
let result = match &self {
Command::PutObject { content, .. } => {
let mut sha = Sha256::default();
sha.update(content);
hex::encode(sha.finalize().as_slice())
}
Command::PutObjectTagging { tags } => {
let mut sha = Sha256::default();
sha.update(tags.as_bytes());
hex::encode(sha.finalize().as_slice())
}
Command::CompleteMultipartUpload { data, .. } => {
let mut sha = Sha256::default();
sha.update(data.to_string().as_bytes());
hex::encode(sha.finalize().as_slice())
}
Command::CreateBucket { config } => {
if let Some(payload) = config.location_constraint_payload() {
let mut sha = Sha256::default();
sha.update(payload.as_bytes());
hex::encode(sha.finalize().as_slice())
} else {
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(),
Command::GetObjectAttributes { .. } => EMPTY_PAYLOAD_SHA.into(),
};
Ok(result)
}
}