s3compat/
command.rs

1use crate::serde_types::CompleteMultipartUploadData;
2
3use crate::EMPTY_PAYLOAD_SHA;
4use sha2::{Digest, Sha256};
5
6pub enum HttpMethod {
7    Delete,
8    Get,
9    Put,
10    Post,
11    Head,
12}
13
14use std::fmt;
15
16impl fmt::Display for HttpMethod {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            HttpMethod::Delete => write!(f, "DELETE"),
20            HttpMethod::Get => write!(f, "GET"),
21            HttpMethod::Post => write!(f, "POST"),
22            HttpMethod::Put => write!(f, "PUT"),
23            HttpMethod::Head => write!(f, "HEAD"),
24        }
25    }
26}
27use crate::bucket_ops::BucketConfiguration;
28use http::HeaderMap;
29
30#[derive(Clone, Debug)]
31pub struct Multipart<'a> {
32    part_number: u32,
33    upload_id: &'a str,
34}
35
36impl<'a> Multipart<'a> {
37    pub fn query_string(&self) -> String {
38        format!(
39            "?partNumber={}&uploadId={}",
40            self.part_number, self.upload_id
41        )
42    }
43
44    pub fn new(part_number: u32, upload_id: &'a str) -> Self {
45        Multipart {
46            part_number,
47            upload_id,
48        }
49    }
50}
51
52#[derive(Clone, Debug)]
53pub enum Command<'a> {
54    HeadObject,
55    DeleteObject,
56    DeleteObjectTagging,
57    GetObject,
58    GetObjectTorrent,
59    GetObjectRange {
60        start: u64,
61        end: Option<u64>,
62    },
63    GetObjectTagging,
64    PutObject {
65        content: &'a [u8],
66        content_type: &'a str,
67        multipart: Option<Multipart<'a>>,
68    },
69    PutObjectTagging {
70        tags: &'a str,
71    },
72    ListMultipartUploads {
73        prefix: Option<&'a str>,
74        delimiter: Option<&'a str>,
75        key_marker: Option<String>,
76        max_uploads: Option<usize>,
77    },
78    ListBucket {
79        prefix: String,
80        delimiter: Option<String>,
81        continuation_token: Option<String>,
82        start_after: Option<String>,
83        max_keys: Option<usize>,
84    },
85    GetBucketLocation,
86    PresignGet {
87        expiry_secs: u32,
88    },
89    PresignPut {
90        expiry_secs: u32,
91        custom_headers: Option<HeaderMap>,
92    },
93    InitiateMultipartUpload,
94    UploadPart {
95        part_number: u32,
96        content: &'a [u8],
97        upload_id: &'a str,
98    },
99    AbortMultipartUpload {
100        upload_id: &'a str,
101    },
102    CompleteMultipartUpload {
103        upload_id: &'a str,
104        data: CompleteMultipartUploadData,
105    },
106    CreateBucket {
107        config: BucketConfiguration,
108    },
109    DeleteBucket,
110}
111
112impl<'a> Command<'a> {
113    pub fn http_verb(&self) -> HttpMethod {
114        match *self {
115            Command::GetObject
116            | Command::GetObjectTorrent
117            | Command::GetObjectRange { .. }
118            | Command::ListBucket { .. }
119            | Command::GetBucketLocation
120            | Command::GetObjectTagging
121            | Command::ListMultipartUploads { .. }
122            | Command::PresignGet { .. } => HttpMethod::Get,
123            Command::PutObject { .. }
124            | Command::PutObjectTagging { .. }
125            | Command::PresignPut { .. }
126            | Command::UploadPart { .. }
127            | Command::CreateBucket { .. } => HttpMethod::Put,
128            Command::DeleteObject
129            | Command::DeleteObjectTagging
130            | Command::AbortMultipartUpload { .. }
131            | Command::DeleteBucket => HttpMethod::Delete,
132            Command::InitiateMultipartUpload | Command::CompleteMultipartUpload { .. } => {
133                HttpMethod::Post
134            }
135            Command::HeadObject => HttpMethod::Head,
136        }
137    }
138
139    pub fn content_length(&self) -> usize {
140        match &self {
141            Command::PutObject { content, .. } => content.len(),
142            Command::PutObjectTagging { tags } => tags.len(),
143            Command::UploadPart { content, .. } => content.len(),
144            Command::CompleteMultipartUpload { data, .. } => data.len(),
145            Command::CreateBucket { config } => {
146                if let Some(payload) = config.location_constraint_payload() {
147                    Vec::from(payload).len()
148                } else {
149                    0
150                }
151            }
152            _ => 0,
153        }
154    }
155
156    pub fn content_type(&self) -> String {
157        match self {
158            Command::PutObject { content_type, .. } => content_type.to_string(),
159            Command::CompleteMultipartUpload { .. } => "application/xml".into(),
160            _ => "text/plain".into(),
161        }
162    }
163
164    pub fn sha256(&self) -> String {
165        match &self {
166            Command::PutObject { content, .. } => {
167                let mut sha = Sha256::default();
168                sha.update(content);
169                hex::encode(sha.finalize().as_slice())
170            }
171            Command::PutObjectTagging { tags } => {
172                let mut sha = Sha256::default();
173                sha.update(tags.as_bytes());
174                hex::encode(sha.finalize().as_slice())
175            }
176            Command::CompleteMultipartUpload { data, .. } => {
177                let mut sha = Sha256::default();
178                sha.update(data.to_string().as_bytes());
179                hex::encode(sha.finalize().as_slice())
180            }
181            Command::CreateBucket { config } => {
182                if let Some(payload) = config.location_constraint_payload() {
183                    let mut sha = Sha256::default();
184                    sha.update(payload.as_bytes());
185                    hex::encode(sha.finalize().as_slice())
186                } else {
187                    EMPTY_PAYLOAD_SHA.into()
188                }
189            }
190            _ => EMPTY_PAYLOAD_SHA.into(),
191        }
192    }
193}