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