1use std::collections::HashMap;
23
24use crate::error::S3Error;
25use crate::serde_types::{
26 BucketLifecycleConfiguration, CompleteMultipartUploadData, CorsConfiguration,
27 DeleteObjectsRequest,
28};
29
30use crate::EMPTY_PAYLOAD_SHA;
31use sha2::{Digest, Sha256};
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub enum HttpMethod {
35 Delete,
36 Get,
37 Put,
38 Post,
39 Head,
40}
41
42use std::fmt;
43
44impl fmt::Display for HttpMethod {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 match self {
47 HttpMethod::Delete => write!(f, "DELETE"),
48 HttpMethod::Get => write!(f, "GET"),
49 HttpMethod::Post => write!(f, "POST"),
50 HttpMethod::Put => write!(f, "PUT"),
51 HttpMethod::Head => write!(f, "HEAD"),
52 }
53 }
54}
55use crate::bucket_ops::BucketConfiguration;
56use http::HeaderMap;
57
58#[derive(Clone, Debug)]
59pub struct Multipart<'a> {
60 part_number: u32,
61 upload_id: &'a str,
62}
63
64impl<'a> Multipart<'a> {
65 pub fn query_string(&self) -> String {
66 format!(
67 "?partNumber={}&uploadId={}",
68 self.part_number, self.upload_id
69 )
70 }
71
72 pub fn new(part_number: u32, upload_id: &'a str) -> Self {
73 Multipart {
74 part_number,
75 upload_id,
76 }
77 }
78}
79
80#[derive(Clone, Debug)]
81pub enum Command<'a> {
82 HeadObject,
83 CopyObject {
84 from: &'a str,
85 },
86 DeleteObject,
87 DeleteObjectTagging,
88 GetObject,
89 GetObjectTorrent,
90 GetObjectRange {
91 start: u64,
92 end: Option<u64>,
93 },
94 GetObjectTagging,
95 PutObject {
96 content: &'a [u8],
97 content_type: &'a str,
98 custom_headers: Option<HeaderMap>,
99 multipart: Option<Multipart<'a>>,
100 },
101 PutObjectTagging {
102 tags: &'a str,
103 },
104 ListMultipartUploads {
105 prefix: Option<&'a str>,
106 delimiter: Option<&'a str>,
107 key_marker: Option<String>,
108 max_uploads: Option<usize>,
109 },
110 ListObjects {
111 prefix: String,
112 delimiter: Option<String>,
113 marker: Option<String>,
114 max_keys: Option<usize>,
115 },
116 ListObjectsV2 {
117 prefix: String,
118 delimiter: Option<String>,
119 continuation_token: Option<String>,
120 start_after: Option<String>,
121 max_keys: Option<usize>,
122 },
123 GetBucketLocation,
124 PresignGet {
125 expiry_secs: u32,
126 custom_queries: Option<HashMap<String, String>>,
127 },
128 PresignPut {
129 expiry_secs: u32,
130 custom_headers: Option<HeaderMap>,
131 custom_queries: Option<HashMap<String, String>>,
132 },
133 PresignDelete {
134 expiry_secs: u32,
135 },
136 InitiateMultipartUpload {
137 content_type: &'a str,
138 },
139 UploadPart {
140 part_number: u32,
141 content: &'a [u8],
142 upload_id: &'a str,
143 },
144 AbortMultipartUpload {
145 upload_id: &'a str,
146 },
147 CompleteMultipartUpload {
148 upload_id: &'a str,
149 data: CompleteMultipartUploadData,
150 },
151 CreateBucket {
152 config: BucketConfiguration,
153 },
154 DeleteBucket,
155 ListBuckets,
156 GetBucketCors {
157 expected_bucket_owner: String,
158 },
159 PutBucketCors {
160 expected_bucket_owner: String,
161 configuration: CorsConfiguration,
162 },
163 DeleteBucketCors {
164 expected_bucket_owner: String,
165 },
166 GetBucketLifecycle,
167 PutBucketLifecycle {
168 configuration: BucketLifecycleConfiguration,
169 },
170 DeleteBucketLifecycle,
171 GetObjectAttributes {
172 expected_bucket_owner: String,
173 version_id: Option<String>,
174 },
175 DeleteObjects {
176 data: DeleteObjectsRequest,
177 },
178}
179
180impl<'a> Command<'a> {
181 pub fn http_verb(&self) -> HttpMethod {
182 match *self {
183 Command::GetObject
184 | Command::GetObjectTorrent
185 | Command::GetBucketCors { .. }
186 | Command::GetObjectRange { .. }
187 | Command::ListBuckets
188 | Command::ListObjects { .. }
189 | Command::ListObjectsV2 { .. }
190 | Command::GetBucketLocation
191 | Command::GetObjectTagging
192 | Command::GetBucketLifecycle
193 | Command::ListMultipartUploads { .. }
194 | Command::PresignGet { .. } => HttpMethod::Get,
195 Command::PutObject { .. }
196 | Command::CopyObject { from: _ }
197 | Command::PutObjectTagging { .. }
198 | Command::PresignPut { .. }
199 | Command::UploadPart { .. }
200 | Command::PutBucketCors { .. }
201 | Command::CreateBucket { .. }
202 | Command::PutBucketLifecycle { .. } => HttpMethod::Put,
203 Command::DeleteObject
204 | Command::DeleteObjectTagging
205 | Command::AbortMultipartUpload { .. }
206 | Command::PresignDelete { .. }
207 | Command::DeleteBucket
208 | Command::DeleteBucketCors { .. }
209 | Command::DeleteBucketLifecycle => HttpMethod::Delete,
210 Command::InitiateMultipartUpload { .. }
211 | Command::CompleteMultipartUpload { .. }
212 | Command::DeleteObjects { .. } => HttpMethod::Post,
213 Command::HeadObject => HttpMethod::Head,
214 Command::GetObjectAttributes { .. } => HttpMethod::Get,
215 }
216 }
217
218 pub fn content_length(&self) -> Result<usize, S3Error> {
219 let result = match &self {
220 Command::CopyObject { from: _ } => 0,
221 Command::PutObject { content, .. } => content.len(),
222 Command::PutObjectTagging { tags } => tags.len(),
223 Command::UploadPart { content, .. } => content.len(),
224 Command::CompleteMultipartUpload { data, .. } => data.len(),
225 Command::CreateBucket { config } => {
226 if let Some(payload) = config.location_constraint_payload() {
227 Vec::from(payload).len()
228 } else {
229 0
230 }
231 }
232 Command::PutBucketLifecycle { configuration } => {
233 quick_xml::se::to_string(configuration)?.len()
234 }
235 Command::PutBucketCors { configuration, .. } => configuration.to_string().len(),
236 Command::HeadObject => 0,
237 Command::DeleteObject => 0,
238 Command::DeleteObjectTagging => 0,
239 Command::GetObject => 0,
240 Command::GetObjectTorrent => 0,
241 Command::GetObjectRange { .. } => 0,
242 Command::GetObjectTagging => 0,
243 Command::ListMultipartUploads { .. } => 0,
244 Command::ListObjects { .. } => 0,
245 Command::ListObjectsV2 { .. } => 0,
246 Command::GetBucketLocation => 0,
247 Command::PresignGet { .. } => 0,
248 Command::PresignPut { .. } => 0,
249 Command::PresignDelete { .. } => 0,
250 Command::InitiateMultipartUpload { .. } => 0,
251 Command::AbortMultipartUpload { .. } => 0,
252 Command::DeleteBucket => 0,
253 Command::ListBuckets => 0,
254 Command::GetBucketCors { .. } => 0,
255 Command::DeleteBucketCors { .. } => 0,
256 Command::GetBucketLifecycle => 0,
257 Command::DeleteBucketLifecycle { .. } => 0,
258 Command::GetObjectAttributes { .. } => 0,
259 Command::DeleteObjects { data } => data.len(),
260 };
261 Ok(result)
262 }
263
264 pub fn content_type(&self) -> String {
265 match self {
266 Command::InitiateMultipartUpload { content_type } => content_type.to_string(),
267 Command::PutObject { content_type, .. } => content_type.to_string(),
268 Command::CompleteMultipartUpload { .. }
269 | Command::PutBucketLifecycle { .. }
270 | Command::PutBucketCors { .. } => "application/xml".into(),
271 Command::HeadObject => "text/plain".into(),
272 Command::DeleteObject => "text/plain".into(),
273 Command::DeleteObjectTagging => "text/plain".into(),
274 Command::GetObject => "text/plain".into(),
275 Command::GetObjectTorrent => "text/plain".into(),
276 Command::GetObjectRange { .. } => "text/plain".into(),
277 Command::GetObjectTagging => "text/plain".into(),
278 Command::ListMultipartUploads { .. } => "text/plain".into(),
279 Command::ListObjects { .. } => "text/plain".into(),
280 Command::ListObjectsV2 { .. } => "text/plain".into(),
281 Command::GetBucketLocation => "text/plain".into(),
282 Command::PresignGet { .. } => "text/plain".into(),
283 Command::PresignPut { .. } => "text/plain".into(),
284 Command::PresignDelete { .. } => "text/plain".into(),
285 Command::AbortMultipartUpload { .. } => "text/plain".into(),
286 Command::DeleteBucket => "text/plain".into(),
287 Command::ListBuckets => "text/plain".into(),
288 Command::GetBucketCors { .. } => "text/plain".into(),
289 Command::DeleteBucketCors { .. } => "text/plain".into(),
290 Command::GetBucketLifecycle => "text/plain".into(),
291 Command::DeleteBucketLifecycle { .. } => "text/plain".into(),
292 Command::CopyObject { .. } => "text/plain".into(),
293 Command::PutObjectTagging { .. } => "text/plain".into(),
294 Command::UploadPart { .. } => "text/plain".into(),
295 Command::CreateBucket { .. } => "text/plain".into(),
296 Command::GetObjectAttributes { .. } => "text/plain".into(),
297 Command::DeleteObjects { .. } => "application/xml".into(),
298 }
299 }
300
301 pub fn sha256(&self) -> Result<String, S3Error> {
302 let result = match &self {
303 Command::PutObject { content, .. } => {
304 let mut sha = Sha256::default();
305 sha.update(content);
306 hex::encode(sha.finalize().as_slice())
307 }
308 Command::PutObjectTagging { tags } => {
309 let mut sha = Sha256::default();
310 sha.update(tags.as_bytes());
311 hex::encode(sha.finalize().as_slice())
312 }
313 Command::CompleteMultipartUpload { data, .. } => {
314 let mut sha = Sha256::default();
315 sha.update(data.to_string().as_bytes());
316 hex::encode(sha.finalize().as_slice())
317 }
318 Command::CreateBucket { config } => {
319 if let Some(payload) = config.location_constraint_payload() {
320 let mut sha = Sha256::default();
321 sha.update(payload.as_bytes());
322 hex::encode(sha.finalize().as_slice())
323 } else {
324 EMPTY_PAYLOAD_SHA.into()
325 }
326 }
327 Command::PutBucketLifecycle { configuration } => {
328 let mut sha = Sha256::default();
329 sha.update(quick_xml::se::to_string(configuration)?.as_bytes());
330 hex::encode(sha.finalize().as_slice())
331 }
332 Command::PutBucketCors { configuration, .. } => {
333 let mut sha = Sha256::default();
334 sha.update(configuration.to_string().as_bytes());
335 hex::encode(sha.finalize().as_slice())
336 }
337 Command::HeadObject => EMPTY_PAYLOAD_SHA.into(),
338 Command::DeleteObject => EMPTY_PAYLOAD_SHA.into(),
339 Command::DeleteObjectTagging => EMPTY_PAYLOAD_SHA.into(),
340 Command::GetObject => EMPTY_PAYLOAD_SHA.into(),
341 Command::GetObjectTorrent => EMPTY_PAYLOAD_SHA.into(),
342 Command::GetObjectRange { .. } => EMPTY_PAYLOAD_SHA.into(),
343 Command::GetObjectTagging => EMPTY_PAYLOAD_SHA.into(),
344 Command::ListMultipartUploads { .. } => EMPTY_PAYLOAD_SHA.into(),
345 Command::ListObjects { .. } => EMPTY_PAYLOAD_SHA.into(),
346 Command::ListObjectsV2 { .. } => EMPTY_PAYLOAD_SHA.into(),
347 Command::GetBucketLocation => EMPTY_PAYLOAD_SHA.into(),
348 Command::PresignGet { .. } => EMPTY_PAYLOAD_SHA.into(),
349 Command::PresignPut { .. } => EMPTY_PAYLOAD_SHA.into(),
350 Command::PresignDelete { .. } => EMPTY_PAYLOAD_SHA.into(),
351 Command::AbortMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(),
352 Command::DeleteBucket => EMPTY_PAYLOAD_SHA.into(),
353 Command::ListBuckets => EMPTY_PAYLOAD_SHA.into(),
354 Command::GetBucketCors { .. } => EMPTY_PAYLOAD_SHA.into(),
355 Command::DeleteBucketCors { .. } => EMPTY_PAYLOAD_SHA.into(),
356 Command::GetBucketLifecycle => EMPTY_PAYLOAD_SHA.into(),
357 Command::DeleteBucketLifecycle { .. } => EMPTY_PAYLOAD_SHA.into(),
358 Command::CopyObject { .. } => EMPTY_PAYLOAD_SHA.into(),
359 Command::UploadPart { .. } => EMPTY_PAYLOAD_SHA.into(),
360 Command::InitiateMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(),
361 Command::GetObjectAttributes { .. } => EMPTY_PAYLOAD_SHA.into(),
362 Command::DeleteObjects { data } => {
363 let mut sha = Sha256::default();
364 sha.update(data.to_string().as_bytes());
365 hex::encode(sha.finalize().as_slice())
366 }
367 };
368 Ok(result)
369 }
370}