1use rustack_s3_model::{
9 error::S3Error,
10 input::{
11 DeleteObjectTaggingInput, GetObjectAclInput, GetObjectAttributesInput,
12 GetObjectLegalHoldInput, GetObjectRetentionInput, GetObjectTaggingInput, PutObjectAclInput,
13 PutObjectLegalHoldInput, PutObjectRetentionInput, PutObjectTaggingInput,
14 },
15 output::{
16 DeleteObjectTaggingOutput, GetObjectAclOutput, GetObjectAttributesOutput,
17 GetObjectLegalHoldOutput, GetObjectRetentionOutput, GetObjectTaggingOutput,
18 PutObjectAclOutput, PutObjectLegalHoldOutput, PutObjectRetentionOutput,
19 PutObjectTaggingOutput,
20 },
21 types::{
22 Checksum, ChecksumType, GetObjectAttributesParts, Grant, Grantee, ObjectLockLegalHold,
23 ObjectLockLegalHoldStatus, ObjectLockRetention, ObjectLockRetentionMode, Permission,
24 StorageClass, Tag, Type,
25 },
26};
27use tracing::debug;
28
29use super::bucket::to_model_owner;
30use crate::{error::S3ServiceError, provider::RustackS3, state::object::CannedAcl};
31
32#[allow(
35 clippy::cast_possible_wrap,
36 clippy::cast_possible_truncation,
37 clippy::cast_sign_loss,
38 clippy::unused_async
39)]
40impl RustackS3 {
41 pub async fn handle_get_object_tagging(
47 &self,
48 input: GetObjectTaggingInput,
49 ) -> Result<GetObjectTaggingOutput, S3Error> {
50 let bucket_name = input.bucket;
51 let key = input.key;
52
53 let bucket = self
54 .state
55 .get_bucket(&bucket_name)
56 .map_err(S3ServiceError::into_s3_error)?;
57
58 let store = bucket.objects.read();
59 let obj = if let Some(version_id) = &input.version_id {
60 store.get_version(&key, version_id).ok_or_else(|| {
61 S3ServiceError::NoSuchVersion {
62 key: key.clone(),
63 version_id: version_id.clone(),
64 }
65 .into_s3_error()
66 })?
67 } else {
68 store
69 .get(&key)
70 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
71 };
72
73 let tag_set: Vec<Tag> = obj
74 .metadata
75 .tagging
76 .iter()
77 .map(|(k, v)| Tag {
78 key: k.clone(),
79 value: v.clone(),
80 })
81 .collect();
82
83 let version_id = if obj.version_id == "null" {
84 None
85 } else {
86 Some(obj.version_id.clone())
87 };
88
89 Ok(GetObjectTaggingOutput {
90 tag_set,
91 version_id,
92 })
93 }
94
95 pub async fn handle_put_object_tagging(
97 &self,
98 input: PutObjectTaggingInput,
99 ) -> Result<PutObjectTaggingOutput, S3Error> {
100 let bucket_name = input.bucket;
101 let key = input.key;
102
103 let bucket = self
104 .state
105 .get_bucket(&bucket_name)
106 .map_err(S3ServiceError::into_s3_error)?;
107
108 let tagging = input.tagging;
109
110 let tags: Vec<(String, String)> = tagging
111 .tag_set
112 .into_iter()
113 .map(|t| (t.key, t.value))
114 .collect();
115
116 crate::validation::validate_tags(&tags).map_err(S3ServiceError::into_s3_error)?;
117
118 let mut store = bucket.objects.write();
121 let obj = if let Some(version_id) = &input.version_id {
122 store.get_version(&key, version_id).ok_or_else(|| {
123 S3ServiceError::NoSuchVersion {
124 key: key.clone(),
125 version_id: version_id.clone(),
126 }
127 .into_s3_error()
128 })?
129 } else {
130 store
131 .get(&key)
132 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
133 };
134
135 let mut updated = obj.clone();
136 updated.metadata.tagging = tags;
137 store.put(updated);
138
139 debug!(bucket = %bucket_name, key = %key, "put_object_tagging completed");
140
141 let version_id_out = input.version_id;
142 Ok(PutObjectTaggingOutput {
143 version_id: version_id_out,
144 })
145 }
146
147 pub async fn handle_delete_object_tagging(
149 &self,
150 input: DeleteObjectTaggingInput,
151 ) -> Result<DeleteObjectTaggingOutput, S3Error> {
152 let bucket_name = input.bucket;
153 let key = input.key;
154
155 let bucket = self
156 .state
157 .get_bucket(&bucket_name)
158 .map_err(S3ServiceError::into_s3_error)?;
159
160 let mut store = bucket.objects.write();
161 let obj = if let Some(version_id) = &input.version_id {
162 store.get_version(&key, version_id).ok_or_else(|| {
163 S3ServiceError::NoSuchVersion {
164 key: key.clone(),
165 version_id: version_id.clone(),
166 }
167 .into_s3_error()
168 })?
169 } else {
170 store
171 .get(&key)
172 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
173 };
174
175 let mut updated = obj.clone();
176 updated.metadata.tagging = Vec::new();
177 store.put(updated);
178
179 debug!(bucket = %bucket_name, key = %key, "delete_object_tagging completed");
180
181 let version_id_out = input.version_id;
182 Ok(DeleteObjectTaggingOutput {
183 version_id: version_id_out,
184 })
185 }
186
187 pub async fn handle_get_object_acl(
193 &self,
194 input: GetObjectAclInput,
195 ) -> Result<GetObjectAclOutput, S3Error> {
196 let bucket_name = input.bucket;
197 let key = input.key;
198
199 let bucket = self
200 .state
201 .get_bucket(&bucket_name)
202 .map_err(S3ServiceError::into_s3_error)?;
203
204 let store = bucket.objects.read();
205 let obj = if let Some(version_id) = &input.version_id {
206 store.get_version(&key, version_id).ok_or_else(|| {
207 S3ServiceError::NoSuchVersion {
208 key: key.clone(),
209 version_id: version_id.clone(),
210 }
211 .into_s3_error()
212 })?
213 } else {
214 store
215 .get(&key)
216 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
217 };
218
219 let owner = to_model_owner(&obj.owner);
220
221 let grant = Grant {
222 grantee: Some(Grantee {
223 display_name: Some(obj.owner.display_name.clone()),
224 email_address: None,
225 id: Some(obj.owner.id.clone()),
226 r#type: Type::CanonicalUser,
227 uri: None,
228 }),
229 permission: Some(Permission::FullControl),
230 };
231
232 Ok(GetObjectAclOutput {
233 grants: vec![grant],
234 owner: Some(owner),
235 request_charged: None,
236 })
237 }
238
239 pub async fn handle_put_object_acl(
241 &self,
242 input: PutObjectAclInput,
243 ) -> Result<PutObjectAclOutput, S3Error> {
244 let bucket_name = input.bucket;
245 let key = input.key;
246
247 let bucket = self
248 .state
249 .get_bucket(&bucket_name)
250 .map_err(S3ServiceError::into_s3_error)?;
251
252 if let Some(acl_enum) = input.acl {
253 let acl: CannedAcl = acl_enum
254 .as_str()
255 .parse()
256 .map_err(|_| S3Error::invalid_argument("Invalid canned ACL"))?;
257
258 let mut store = bucket.objects.write();
259 let obj = if let Some(version_id) = &input.version_id {
260 store.get_version(&key, version_id).ok_or_else(|| {
261 S3ServiceError::NoSuchVersion {
262 key: key.clone(),
263 version_id: version_id.clone(),
264 }
265 .into_s3_error()
266 })?
267 } else {
268 store
269 .get(&key)
270 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
271 };
272
273 let mut updated = obj.clone();
274 updated.metadata.acl = acl;
275 store.put(updated);
276 }
277
278 debug!(bucket = %bucket_name, key = %key, "put_object_acl completed");
279
280 Ok(PutObjectAclOutput {
281 request_charged: None,
282 })
283 }
284
285 pub async fn handle_get_object_retention(
291 &self,
292 input: GetObjectRetentionInput,
293 ) -> Result<GetObjectRetentionOutput, S3Error> {
294 let bucket_name = input.bucket;
295 let key = input.key;
296
297 let bucket = self
298 .state
299 .get_bucket(&bucket_name)
300 .map_err(S3ServiceError::into_s3_error)?;
301
302 let store = bucket.objects.read();
303 let obj = if let Some(version_id) = &input.version_id {
304 store.get_version(&key, version_id).ok_or_else(|| {
305 S3ServiceError::NoSuchVersion {
306 key: key.clone(),
307 version_id: version_id.clone(),
308 }
309 .into_s3_error()
310 })?
311 } else {
312 store
313 .get(&key)
314 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
315 };
316
317 let retention = match (
318 &obj.metadata.object_lock_mode,
319 obj.metadata.object_lock_retain_until,
320 ) {
321 (Some(mode), Some(until)) => Some(ObjectLockRetention {
322 mode: Some(ObjectLockRetentionMode::from(mode.as_str())),
323 retain_until_date: Some(until),
324 }),
325 _ => None,
326 };
327
328 if retention.is_none() {
329 return Err(S3Error::invalid_argument(
330 "No retention configuration found",
331 ));
332 }
333
334 Ok(GetObjectRetentionOutput { retention })
335 }
336
337 pub async fn handle_put_object_retention(
339 &self,
340 input: PutObjectRetentionInput,
341 ) -> Result<PutObjectRetentionOutput, S3Error> {
342 let bucket_name = input.bucket;
343 let key = input.key;
344
345 let bucket = self
346 .state
347 .get_bucket(&bucket_name)
348 .map_err(S3ServiceError::into_s3_error)?;
349
350 let retention = input.retention;
351
352 let mut store = bucket.objects.write();
353
354 {
356 let obj = if let Some(version_id) = &input.version_id {
357 store.get_version(&key, version_id).ok_or_else(|| {
358 S3ServiceError::NoSuchVersion {
359 key: key.clone(),
360 version_id: version_id.clone(),
361 }
362 .into_s3_error()
363 })?
364 } else {
365 store
366 .get(&key)
367 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
368 };
369
370 let existing_until = obj.metadata.object_lock_retain_until;
372 let existing_mode = obj.metadata.object_lock_mode.as_deref();
373 let bypass = input.bypass_governance_retention.unwrap_or(false);
374
375 if let Some(current_until) = existing_until {
376 let now = chrono::Utc::now();
377 if current_until > now {
378 let new_mode = retention.as_ref().and_then(|r| r.mode.as_ref());
379 let new_until = retention.as_ref().and_then(|r| r.retain_until_date);
380
381 if existing_mode == Some("COMPLIANCE") {
383 let mode_changed = new_mode.is_none_or(|m| m.as_str() != "COMPLIANCE");
384 let is_shortening = match new_until {
385 Some(new) => new < current_until,
386 None => true,
387 };
388 if mode_changed || is_shortening {
389 return Err(S3ServiceError::AccessDenied.into_s3_error());
390 }
391 } else {
392 let is_shortening = match new_until {
394 Some(new) => new < current_until,
395 None => true,
396 };
397 if is_shortening && !bypass {
398 return Err(S3ServiceError::AccessDenied.into_s3_error());
399 }
400 }
401 }
402 }
403 }
404
405 let obj = if let Some(version_id) = &input.version_id {
407 store.get_version_mut(&key, version_id).ok_or_else(|| {
408 S3ServiceError::NoSuchVersion {
409 key: key.clone(),
410 version_id: version_id.clone(),
411 }
412 .into_s3_error()
413 })?
414 } else {
415 store
416 .get_mut(&key)
417 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
418 };
419
420 if let Some(ret) = retention {
421 obj.metadata.object_lock_mode = ret.mode.as_ref().map(|m| m.as_str().to_owned());
422 obj.metadata.object_lock_retain_until = ret.retain_until_date;
423 } else {
424 obj.metadata.object_lock_mode = None;
425 obj.metadata.object_lock_retain_until = None;
426 }
427
428 debug!(bucket = %bucket_name, key = %key, "put_object_retention completed");
429
430 Ok(PutObjectRetentionOutput {
431 request_charged: None,
432 })
433 }
434
435 pub async fn handle_get_object_legal_hold(
441 &self,
442 input: GetObjectLegalHoldInput,
443 ) -> Result<GetObjectLegalHoldOutput, S3Error> {
444 let bucket_name = input.bucket;
445 let key = input.key;
446
447 let bucket = self
448 .state
449 .get_bucket(&bucket_name)
450 .map_err(S3ServiceError::into_s3_error)?;
451
452 if !*bucket.object_lock_enabled.read() {
453 return Err(S3ServiceError::ObjectLockConfigurationNotFoundError.into_s3_error());
454 }
455
456 let store = bucket.objects.read();
457 let obj = if let Some(version_id) = &input.version_id {
458 store.get_version(&key, version_id).ok_or_else(|| {
459 S3ServiceError::NoSuchVersion {
460 key: key.clone(),
461 version_id: version_id.clone(),
462 }
463 .into_s3_error()
464 })?
465 } else {
466 store
467 .get(&key)
468 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
469 };
470
471 let is_on = obj.metadata.object_lock_legal_hold.unwrap_or(false);
472 let status = if is_on {
473 ObjectLockLegalHoldStatus::On
474 } else {
475 ObjectLockLegalHoldStatus::Off
476 };
477
478 Ok(GetObjectLegalHoldOutput {
479 legal_hold: Some(ObjectLockLegalHold {
480 status: Some(status),
481 }),
482 })
483 }
484
485 pub async fn handle_put_object_legal_hold(
487 &self,
488 input: PutObjectLegalHoldInput,
489 ) -> Result<PutObjectLegalHoldOutput, S3Error> {
490 let bucket_name = input.bucket;
491 let key = input.key;
492
493 let bucket = self
494 .state
495 .get_bucket(&bucket_name)
496 .map_err(S3ServiceError::into_s3_error)?;
497
498 if !*bucket.object_lock_enabled.read() {
499 return Err(S3ServiceError::ObjectLockConfigurationNotFoundError.into_s3_error());
500 }
501
502 let legal_hold = input.legal_hold;
503
504 let status = legal_hold.as_ref().and_then(|lh| lh.status.as_ref());
506 if status.is_none() {
507 return Err(S3Error::malformed_xml("Missing LegalHold status"));
508 }
509
510 let mut store = bucket.objects.write();
511
512 if let Some(version_id) = &input.version_id {
514 store.get_version(&key, version_id).ok_or_else(|| {
515 S3ServiceError::NoSuchVersion {
516 key: key.clone(),
517 version_id: version_id.clone(),
518 }
519 .into_s3_error()
520 })?;
521 } else {
522 store
523 .get(&key)
524 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?;
525 }
526
527 let new_hold = legal_hold
529 .and_then(|lh| lh.status)
530 .map(|s| s.as_str() == "ON");
531
532 let obj = if let Some(version_id) = &input.version_id {
533 store.get_version_mut(&key, version_id)
534 } else {
535 store.get_mut(&key)
536 }
537 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?;
538
539 obj.metadata.object_lock_legal_hold = new_hold;
540
541 debug!(bucket = %bucket_name, key = %key, "put_object_legal_hold completed");
542
543 Ok(PutObjectLegalHoldOutput {
544 request_charged: None,
545 })
546 }
547
548 pub async fn handle_get_object_attributes(
554 &self,
555 input: GetObjectAttributesInput,
556 ) -> Result<GetObjectAttributesOutput, S3Error> {
557 let bucket_name = input.bucket;
558 let key = input.key;
559
560 let bucket = self
561 .state
562 .get_bucket(&bucket_name)
563 .map_err(S3ServiceError::into_s3_error)?;
564
565 let store = bucket.objects.read();
566 let obj = if let Some(version_id) = &input.version_id {
567 store.get_version(&key, version_id).ok_or_else(|| {
568 S3ServiceError::NoSuchVersion {
569 key: key.clone(),
570 version_id: version_id.clone(),
571 }
572 .into_s3_error()
573 })?
574 } else {
575 store
576 .get(&key)
577 .ok_or_else(|| S3ServiceError::NoSuchKey { key: key.clone() }.into_s3_error())?
578 };
579
580 let version_id = if obj.version_id == "null" {
581 None
582 } else {
583 Some(obj.version_id.clone())
584 };
585
586 let checksum = obj.checksum.as_ref().map(|c| {
587 let mut cksum = Checksum::default();
588 match c.algorithm.as_str() {
589 "CRC32" => cksum.checksum_crc32 = Some(c.value.clone()),
590 "CRC32C" => cksum.checksum_crc32c = Some(c.value.clone()),
591 "CRC64NVME" => cksum.checksum_crc64nvme = Some(c.value.clone()),
592 "SHA1" => cksum.checksum_sha1 = Some(c.value.clone()),
593 "SHA256" => cksum.checksum_sha256 = Some(c.value.clone()),
594 _ => {}
595 }
596 cksum.checksum_type = Some(match c.checksum_type.as_str() {
597 "COMPOSITE" => ChecksumType::Composite,
598 _ => ChecksumType::FullObject,
599 });
600 cksum
601 });
602
603 Ok(GetObjectAttributesOutput {
604 checksum,
605 delete_marker: None,
606 e_tag: Some(obj.etag.clone()),
607 last_modified: Some(obj.last_modified),
608 object_parts: obj.parts_count.map(|n| GetObjectAttributesParts {
609 is_truncated: None,
610 max_parts: None,
611 next_part_number_marker: None,
612 part_number_marker: None,
613 parts: Vec::new(),
614 total_parts_count: Some(n as i32),
615 }),
616 object_size: Some(obj.size as i64),
617 request_charged: None,
618 storage_class: Some(StorageClass::from(obj.storage_class.as_str())),
619 version_id,
620 })
621 }
622}