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 upload_id_marker: Option<String>,
109 max_uploads: Option<usize>,
110 },
111 ListObjects {
112 prefix: String,
113 delimiter: Option<String>,
114 marker: Option<String>,
115 max_keys: Option<usize>,
116 },
117 ListObjectsV2 {
118 prefix: String,
119 delimiter: Option<String>,
120 continuation_token: Option<String>,
121 start_after: Option<String>,
122 max_keys: Option<usize>,
123 },
124 GetBucketLocation,
125 PresignGet {
126 expiry_secs: u32,
127 custom_queries: Option<HashMap<String, String>>,
128 },
129 PresignPut {
130 expiry_secs: u32,
131 custom_headers: Option<HeaderMap>,
132 custom_queries: Option<HashMap<String, String>>,
133 },
134 PresignDelete {
135 expiry_secs: u32,
136 },
137 InitiateMultipartUpload {
138 content_type: &'a str,
139 },
140 UploadPart {
141 part_number: u32,
142 content: &'a [u8],
143 upload_id: &'a str,
144 },
145 AbortMultipartUpload {
146 upload_id: &'a str,
147 },
148 CompleteMultipartUpload {
149 upload_id: &'a str,
150 data: CompleteMultipartUploadData,
151 },
152 CreateBucket {
153 config: BucketConfiguration,
154 },
155 DeleteBucket,
156 ListBuckets,
157 GetBucketCors {
158 expected_bucket_owner: String,
159 },
160 PutBucketCors {
161 expected_bucket_owner: String,
162 configuration: CorsConfiguration,
163 },
164 DeleteBucketCors {
165 expected_bucket_owner: String,
166 },
167 GetBucketLifecycle,
168 PutBucketLifecycle {
169 configuration: BucketLifecycleConfiguration,
170 },
171 DeleteBucketLifecycle,
172 GetBucketPolicy,
173 PutBucketPolicy {
174 policy: String,
175 },
176 DeleteBucketPolicy,
177 GetObjectAttributes {
178 expected_bucket_owner: String,
179 version_id: Option<String>,
180 },
181 DeleteObjects {
182 data: DeleteObjectsRequest,
183 },
184}
185
186impl<'a> Command<'a> {
187 pub(crate) fn is_retry_safe(&self) -> bool {
192 matches!(
193 self,
194 Command::HeadObject
195 | Command::GetObject
196 | Command::GetObjectTorrent
197 | Command::GetObjectRange { .. }
198 | Command::GetObjectTagging
199 | Command::ListMultipartUploads { .. }
200 | Command::ListObjects { .. }
201 | Command::ListObjectsV2 { .. }
202 | Command::GetBucketLocation
203 | Command::GetBucketCors { .. }
204 | Command::GetBucketLifecycle
205 | Command::GetBucketPolicy
206 | Command::ListBuckets
207 | Command::GetObjectAttributes { .. }
208 ) || matches!(
209 self,
210 Command::PutObject {
211 multipart: Some(_),
212 ..
213 }
214 )
215 }
216
217 pub fn http_verb(&self) -> HttpMethod {
218 match *self {
219 Command::GetObject
220 | Command::GetObjectTorrent
221 | Command::GetBucketCors { .. }
222 | Command::GetObjectRange { .. }
223 | Command::ListBuckets
224 | Command::ListObjects { .. }
225 | Command::ListObjectsV2 { .. }
226 | Command::GetBucketLocation
227 | Command::GetObjectTagging
228 | Command::GetBucketLifecycle
229 | Command::GetBucketPolicy
230 | Command::ListMultipartUploads { .. }
231 | Command::PresignGet { .. } => HttpMethod::Get,
232 Command::PutObject { .. }
233 | Command::CopyObject { from: _ }
234 | Command::PutObjectTagging { .. }
235 | Command::PresignPut { .. }
236 | Command::UploadPart { .. }
237 | Command::PutBucketCors { .. }
238 | Command::CreateBucket { .. }
239 | Command::PutBucketLifecycle { .. }
240 | Command::PutBucketPolicy { .. } => HttpMethod::Put,
241 Command::DeleteObject
242 | Command::DeleteObjectTagging
243 | Command::AbortMultipartUpload { .. }
244 | Command::PresignDelete { .. }
245 | Command::DeleteBucket
246 | Command::DeleteBucketCors { .. }
247 | Command::DeleteBucketLifecycle
248 | Command::DeleteBucketPolicy => HttpMethod::Delete,
249 Command::InitiateMultipartUpload { .. }
250 | Command::CompleteMultipartUpload { .. }
251 | Command::DeleteObjects { .. } => HttpMethod::Post,
252 Command::HeadObject => HttpMethod::Head,
253 Command::GetObjectAttributes { .. } => HttpMethod::Get,
254 }
255 }
256
257 pub fn content_length(&self) -> Result<usize, S3Error> {
258 let result = match &self {
259 Command::CopyObject { from: _ } => 0,
260 Command::PutObject { content, .. } => content.len(),
261 Command::PutObjectTagging { tags } => tags.len(),
262 Command::UploadPart { content, .. } => content.len(),
263 Command::CompleteMultipartUpload { data, .. } => data.len(),
264 Command::CreateBucket { config } => {
265 if let Some(payload) = config.location_constraint_payload() {
266 Vec::from(payload).len()
267 } else {
268 0
269 }
270 }
271 Command::PutBucketLifecycle { configuration } => {
272 quick_xml::se::to_string(configuration)?.len()
273 }
274 Command::PutBucketCors { configuration, .. } => configuration.to_string().len(),
275 Command::HeadObject => 0,
276 Command::DeleteObject => 0,
277 Command::DeleteObjectTagging => 0,
278 Command::GetObject => 0,
279 Command::GetObjectTorrent => 0,
280 Command::GetObjectRange { .. } => 0,
281 Command::GetObjectTagging => 0,
282 Command::ListMultipartUploads { .. } => 0,
283 Command::ListObjects { .. } => 0,
284 Command::ListObjectsV2 { .. } => 0,
285 Command::GetBucketLocation => 0,
286 Command::PresignGet { .. } => 0,
287 Command::PresignPut { .. } => 0,
288 Command::PresignDelete { .. } => 0,
289 Command::InitiateMultipartUpload { .. } => 0,
290 Command::AbortMultipartUpload { .. } => 0,
291 Command::DeleteBucket => 0,
292 Command::ListBuckets => 0,
293 Command::GetBucketCors { .. } => 0,
294 Command::DeleteBucketCors { .. } => 0,
295 Command::GetBucketLifecycle => 0,
296 Command::DeleteBucketLifecycle { .. } => 0,
297 Command::GetBucketPolicy => 0,
298 Command::PutBucketPolicy { policy } => policy.len(),
299 Command::DeleteBucketPolicy => 0,
300 Command::GetObjectAttributes { .. } => 0,
301 Command::DeleteObjects { data } => data.len(),
302 };
303 Ok(result)
304 }
305
306 pub fn content_type(&self) -> String {
307 match self {
308 Command::InitiateMultipartUpload { content_type } => content_type.to_string(),
309 Command::PutObject { content_type, .. } => content_type.to_string(),
310 Command::CompleteMultipartUpload { .. }
311 | Command::PutBucketLifecycle { .. }
312 | Command::PutBucketCors { .. } => "application/xml".into(),
313 Command::HeadObject => "text/plain".into(),
314 Command::DeleteObject => "text/plain".into(),
315 Command::DeleteObjectTagging => "text/plain".into(),
316 Command::GetObject => "text/plain".into(),
317 Command::GetObjectTorrent => "text/plain".into(),
318 Command::GetObjectRange { .. } => "text/plain".into(),
319 Command::GetObjectTagging => "text/plain".into(),
320 Command::ListMultipartUploads { .. } => "text/plain".into(),
321 Command::ListObjects { .. } => "text/plain".into(),
322 Command::ListObjectsV2 { .. } => "text/plain".into(),
323 Command::GetBucketLocation => "text/plain".into(),
324 Command::PresignGet { .. } => "text/plain".into(),
325 Command::PresignPut { .. } => "text/plain".into(),
326 Command::PresignDelete { .. } => "text/plain".into(),
327 Command::AbortMultipartUpload { .. } => "text/plain".into(),
328 Command::DeleteBucket => "text/plain".into(),
329 Command::ListBuckets => "text/plain".into(),
330 Command::GetBucketCors { .. } => "text/plain".into(),
331 Command::DeleteBucketCors { .. } => "text/plain".into(),
332 Command::GetBucketLifecycle => "text/plain".into(),
333 Command::DeleteBucketLifecycle { .. } => "text/plain".into(),
334 Command::GetBucketPolicy => "text/plain".into(),
335 Command::PutBucketPolicy { .. } => "application/json".into(),
336 Command::DeleteBucketPolicy => "text/plain".into(),
337 Command::CopyObject { .. } => "text/plain".into(),
338 Command::PutObjectTagging { .. } => "text/plain".into(),
339 Command::UploadPart { .. } => "text/plain".into(),
340 Command::CreateBucket { .. } => "text/plain".into(),
341 Command::GetObjectAttributes { .. } => "text/plain".into(),
342 Command::DeleteObjects { .. } => "application/xml".into(),
343 }
344 }
345
346 pub fn sha256(&self) -> Result<String, S3Error> {
347 let result = match &self {
348 Command::PutObject { content, .. } => {
349 let mut sha = Sha256::default();
350 sha.update(content);
351 hex::encode(sha.finalize().as_slice())
352 }
353 Command::PutObjectTagging { tags } => {
354 let mut sha = Sha256::default();
355 sha.update(tags.as_bytes());
356 hex::encode(sha.finalize().as_slice())
357 }
358 Command::CompleteMultipartUpload { data, .. } => {
359 let mut sha = Sha256::default();
360 sha.update(data.to_string().as_bytes());
361 hex::encode(sha.finalize().as_slice())
362 }
363 Command::CreateBucket { config } => {
364 if let Some(payload) = config.location_constraint_payload() {
365 let mut sha = Sha256::default();
366 sha.update(payload.as_bytes());
367 hex::encode(sha.finalize().as_slice())
368 } else {
369 EMPTY_PAYLOAD_SHA.into()
370 }
371 }
372 Command::PutBucketLifecycle { configuration } => {
373 let mut sha = Sha256::default();
374 sha.update(quick_xml::se::to_string(configuration)?.as_bytes());
375 hex::encode(sha.finalize().as_slice())
376 }
377 Command::PutBucketCors { configuration, .. } => {
378 let mut sha = Sha256::default();
379 sha.update(configuration.to_string().as_bytes());
380 hex::encode(sha.finalize().as_slice())
381 }
382 Command::HeadObject => EMPTY_PAYLOAD_SHA.into(),
383 Command::DeleteObject => EMPTY_PAYLOAD_SHA.into(),
384 Command::DeleteObjectTagging => EMPTY_PAYLOAD_SHA.into(),
385 Command::GetObject => EMPTY_PAYLOAD_SHA.into(),
386 Command::GetObjectTorrent => EMPTY_PAYLOAD_SHA.into(),
387 Command::GetObjectRange { .. } => EMPTY_PAYLOAD_SHA.into(),
388 Command::GetObjectTagging => EMPTY_PAYLOAD_SHA.into(),
389 Command::ListMultipartUploads { .. } => EMPTY_PAYLOAD_SHA.into(),
390 Command::ListObjects { .. } => EMPTY_PAYLOAD_SHA.into(),
391 Command::ListObjectsV2 { .. } => EMPTY_PAYLOAD_SHA.into(),
392 Command::GetBucketLocation => EMPTY_PAYLOAD_SHA.into(),
393 Command::PresignGet { .. } => EMPTY_PAYLOAD_SHA.into(),
394 Command::PresignPut { .. } => EMPTY_PAYLOAD_SHA.into(),
395 Command::PresignDelete { .. } => EMPTY_PAYLOAD_SHA.into(),
396 Command::AbortMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(),
397 Command::DeleteBucket => EMPTY_PAYLOAD_SHA.into(),
398 Command::ListBuckets => EMPTY_PAYLOAD_SHA.into(),
399 Command::GetBucketCors { .. } => EMPTY_PAYLOAD_SHA.into(),
400 Command::DeleteBucketCors { .. } => EMPTY_PAYLOAD_SHA.into(),
401 Command::GetBucketLifecycle => EMPTY_PAYLOAD_SHA.into(),
402 Command::DeleteBucketLifecycle { .. } => EMPTY_PAYLOAD_SHA.into(),
403 Command::GetBucketPolicy => EMPTY_PAYLOAD_SHA.into(),
404 Command::PutBucketPolicy { policy } => {
405 let mut sha = Sha256::default();
406 sha.update(policy.as_bytes());
407 hex::encode(sha.finalize().as_slice())
408 }
409 Command::DeleteBucketPolicy => EMPTY_PAYLOAD_SHA.into(),
410 Command::CopyObject { .. } => EMPTY_PAYLOAD_SHA.into(),
411 Command::UploadPart { .. } => EMPTY_PAYLOAD_SHA.into(),
412 Command::InitiateMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(),
413 Command::GetObjectAttributes { .. } => EMPTY_PAYLOAD_SHA.into(),
414 Command::DeleteObjects { data } => {
415 let mut sha = Sha256::default();
416 sha.update(data.to_string().as_bytes());
417 hex::encode(sha.finalize().as_slice())
418 }
419 };
420 Ok(result)
421 }
422}