1use http::StatusCode;
4
5use crate::response::{no_store_empty_response, xml_document_response};
6use crate::{
7 DavBackendErrorKind, DavCancellation, DavDirectoryEntry, DavDirectoryEnumerator,
8 DavDirectoryPageLimits, DavDirectoryPageState, DavDirectoryReadError, DavErrorCondition,
9 DavMetaData, DavMultiStatusError, DavMutationFailure, DavPath, DavPathError, DavResourceKind,
10 DavResponse, DavTraversalBudget, DavTraversalError, DavTraversalErrorKind, DavTraversalLimits,
11 DavXmlError, dav_error_element, delete_success_response,
12 mutation_multistatus_response_with_limits, mutation_success_response, read_next_directory_page,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum DavMutationOperation {
18 Copy,
19 Move,
20 Delete,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum DavMutationStepKind {
26 CopyFile,
27 MoveFile,
28 PrepareCollection,
29 DeleteFile,
30 DeleteCollection,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum DavMutationTargetRole {
36 Source,
37 Destination,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct DavMutationCommand {
46 pub operation: DavMutationOperation,
47 pub step: DavMutationStepKind,
48 pub role: DavMutationTargetRole,
49 pub source: DavPath,
50 pub destination: Option<DavPath>,
51}
52
53impl DavMutationCommand {
54 #[must_use]
55 pub fn affected_path(&self) -> &DavPath {
56 self.destination.as_ref().unwrap_or(&self.source)
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum DavMutationStepError {
63 Locked {
64 affected_path: DavPath,
65 lock_root: DavPath,
66 },
67 Status {
68 affected_path: DavPath,
69 status: u16,
70 },
71}
72
73impl DavMutationStepError {
74 #[must_use]
75 pub fn locked(affected_path: DavPath, lock_root: DavPath) -> Self {
76 Self::Locked {
77 affected_path,
78 lock_root,
79 }
80 }
81
82 #[must_use]
83 pub fn status(affected_path: DavPath, status: u16) -> Self {
84 Self::Status {
85 affected_path,
86 status,
87 }
88 }
89
90 #[must_use]
91 pub fn backend(affected_path: DavPath) -> Self {
92 Self::status(affected_path, StatusCode::INTERNAL_SERVER_ERROR.as_u16())
93 }
94
95 #[must_use]
97 pub fn from_backend(affected_path: DavPath, error: &crate::DavBackendError) -> Self {
98 let status = match error.kind {
99 DavBackendErrorKind::NotFound => StatusCode::NOT_FOUND,
100 DavBackendErrorKind::Forbidden => StatusCode::FORBIDDEN,
101 DavBackendErrorKind::Conflict | DavBackendErrorKind::AlreadyExists => {
102 StatusCode::CONFLICT
103 }
104 DavBackendErrorKind::InsufficientStorage => StatusCode::INSUFFICIENT_STORAGE,
105 DavBackendErrorKind::PayloadTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
106 DavBackendErrorKind::Locked => StatusCode::LOCKED,
107 DavBackendErrorKind::InvalidInput => StatusCode::BAD_REQUEST,
108 DavBackendErrorKind::Unsupported => StatusCode::METHOD_NOT_ALLOWED,
109 DavBackendErrorKind::Internal => StatusCode::INTERNAL_SERVER_ERROR,
110 };
111 Self::status(affected_path, status.as_u16())
112 }
113
114 fn into_failure(self) -> DavMutationFailure {
115 match self {
116 Self::Locked {
117 affected_path,
118 lock_root,
119 } => DavMutationFailure::locked(affected_path, lock_root),
120 Self::Status {
121 affected_path,
122 status,
123 } => DavMutationFailure::status(affected_path, status),
124 }
125 }
126}
127
128pub trait DavMutationPort: Send + Sync {
133 fn execute(
134 &self,
135 command: DavMutationCommand,
136 ) -> impl Future<Output = Result<(), DavMutationStepError>> + Send;
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct DavMutationExecutorLimits {
142 pub traversal: DavTraversalLimits,
143 pub directory_pages: DavDirectoryPageLimits,
144 pub directory_page_entries: usize,
145 pub maximum_directory_entries: usize,
146}
147
148impl DavMutationExecutorLimits {
149 #[must_use]
150 pub const fn new(
151 traversal: DavTraversalLimits,
152 directory_pages: DavDirectoryPageLimits,
153 directory_page_entries: usize,
154 maximum_directory_entries: usize,
155 ) -> Self {
156 Self {
157 traversal,
158 directory_pages,
159 directory_page_entries,
160 maximum_directory_entries,
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct DavMutationRequest {
168 pub operation: DavMutationOperation,
169 pub source: DavPath,
170 pub source_kind: DavResourceKind,
171 pub destination: Option<DavPath>,
172 pub destination_kind: Option<DavResourceKind>,
173 pub destination_existed: bool,
174 pub recurse_collections: bool,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum DavMutationStop {
180 Cancelled,
181 InsufficientStorage,
182 Backend,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct DavMutationOutcome {
188 pub operation: DavMutationOperation,
189 pub request_target: DavPath,
190 pub destination_existed: bool,
191 pub failures: Vec<DavMutationFailure>,
192 pub stop: Option<DavMutationStop>,
193 pub progress: crate::DavTraversalProgress,
194}
195
196impl DavMutationOutcome {
197 #[must_use]
198 pub fn is_complete_success(&self) -> bool {
199 self.failures.is_empty() && self.stop.is_none()
200 }
201}
202
203#[derive(Debug, Clone)]
204struct Child {
205 name: bytes::Bytes,
206 key: bytes::Bytes,
207 path: DavPath,
208 is_collection: bool,
209}
210
211impl Child {
212 fn namespace_name(&self) -> &[u8] {
213 &self.name
214 }
215}
216
217#[derive(Debug, Clone)]
218enum Work {
219 TransferDirectory {
220 source: DavPath,
221 destination: DavPath,
222 depth: usize,
223 parent: Option<usize>,
224 },
225 TransferFile {
226 source: DavPath,
227 destination: DavPath,
228 depth: usize,
229 parent: Option<usize>,
230 },
231 ReplaceWithFile {
232 source: DavPath,
233 destination: DavPath,
234 depth: usize,
235 parent: Option<usize>,
236 },
237 DeleteNode {
238 path: DavPath,
239 is_collection: bool,
240 role: DavMutationTargetRole,
241 depth: usize,
242 parent: Option<usize>,
243 },
244 FinalizeTransfer {
245 source: DavPath,
246 frame: usize,
247 },
248 FinalizeFileReplacement {
249 source: DavPath,
250 destination: DavPath,
251 depth: usize,
252 frame: usize,
253 },
254 FinalizeDelete {
255 path: DavPath,
256 role: DavMutationTargetRole,
257 frame: usize,
258 },
259}
260
261#[derive(Debug, Clone, Copy)]
262struct Frame {
263 parent: Option<usize>,
264 failed: bool,
265}
266
267#[expect(
277 clippy::too_many_lines,
278 reason = "the iterative state machine keeps queue accounting, frame propagation, and cancellation order visible in one loop"
279)]
280pub async fn execute_recursive_mutation<E, P, C>(
281 request: DavMutationRequest,
282 enumerator: &E,
283 port: &P,
284 cancellation: &C,
285 limits: DavMutationExecutorLimits,
286) -> DavMutationOutcome
287where
288 E: DavDirectoryEnumerator,
289 P: DavMutationPort,
290 C: DavCancellation,
291{
292 let operation = request.operation;
293 let destination_existed = request.destination_existed;
294 let recurse_collections = request.recurse_collections;
295 let request_target = request
296 .destination
297 .as_ref()
298 .unwrap_or(&request.source)
299 .clone();
300 let mut failures = Vec::new();
301 let mut stop = None;
302 let mut budget = match DavTraversalBudget::new(limits.traversal) {
303 Ok(budget)
304 if limits.directory_page_entries != 0 && limits.maximum_directory_entries != 0 =>
305 {
306 budget
307 }
308 Ok(budget) => {
309 return DavMutationOutcome {
310 operation,
311 request_target,
312 destination_existed,
313 failures,
314 stop: Some(DavMutationStop::InsufficientStorage),
315 progress: budget.progress(),
316 };
317 }
318 Err(error) => {
319 return DavMutationOutcome {
320 operation,
321 request_target,
322 destination_existed,
323 failures,
324 stop: Some(stop_from_traversal(error)),
325 progress: error.progress,
326 };
327 }
328 };
329
330 let root = match (
331 operation,
332 request.source_kind,
333 request.destination,
334 request.destination_kind,
335 ) {
336 (DavMutationOperation::Move, DavResourceKind::Collection, Some(_), _)
337 if !recurse_collections =>
338 {
339 return DavMutationOutcome {
340 operation,
341 request_target,
342 destination_existed,
343 failures,
344 stop: Some(DavMutationStop::Backend),
345 progress: budget.progress(),
346 };
347 }
348 (DavMutationOperation::Delete, kind, _, _) => Work::DeleteNode {
349 path: request.source,
350 is_collection: kind == DavResourceKind::Collection,
351 role: DavMutationTargetRole::Source,
352 depth: 0,
353 parent: None,
354 },
355 (
356 DavMutationOperation::Copy | DavMutationOperation::Move,
357 DavResourceKind::File,
358 Some(destination),
359 Some(DavResourceKind::Collection),
360 ) => Work::ReplaceWithFile {
361 source: request.source,
362 destination,
363 depth: 0,
364 parent: None,
365 },
366 (
367 DavMutationOperation::Copy | DavMutationOperation::Move,
368 DavResourceKind::File,
369 Some(destination),
370 _,
371 ) => Work::TransferFile {
372 source: request.source,
373 destination,
374 depth: 0,
375 parent: None,
376 },
377 (
378 DavMutationOperation::Copy | DavMutationOperation::Move,
379 DavResourceKind::Collection,
380 Some(destination),
381 _,
382 ) => Work::TransferDirectory {
383 source: request.source,
384 destination,
385 depth: 0,
386 parent: None,
387 },
388 _ => {
389 return DavMutationOutcome {
390 operation,
391 request_target,
392 destination_existed,
393 failures,
394 stop: Some(DavMutationStop::Backend),
395 progress: budget.progress(),
396 };
397 }
398 };
399
400 let mut work = vec![root];
401 if let Err(error) = budget.reserve_work(1) {
402 stop = Some(stop_from_traversal(error));
403 }
404 let mut frames = Vec::new();
405
406 while stop.is_none() {
407 if let Err(error) = budget.checkpoint(cancellation) {
408 stop = Some(stop_from_traversal(error));
409 break;
410 }
411 let Some(item) = work.pop() else {
412 break;
413 };
414 budget.complete_work();
415
416 match item {
417 Work::TransferFile {
418 source,
419 destination,
420 depth,
421 parent,
422 } => {
423 if let Err(error) = budget.visit(depth) {
424 stop = Some(stop_from_traversal(error));
425 continue;
426 }
427 let frame = parent.unwrap_or_else(|| {
428 let frame = frames.len();
429 frames.push(Frame {
430 parent: None,
431 failed: false,
432 });
433 frame
434 });
435 let step_kind = if operation == DavMutationOperation::Move {
436 DavMutationStepKind::MoveFile
437 } else {
438 DavMutationStepKind::CopyFile
439 };
440 let command = DavMutationCommand {
441 operation,
442 step: step_kind,
443 role: DavMutationTargetRole::Destination,
444 source,
445 destination: Some(destination),
446 };
447 if let Err(error) = apply_step(
448 port,
449 command,
450 frame,
451 &mut frames,
452 &mut failures,
453 &mut budget,
454 )
455 .await
456 {
457 stop = Some(stop_from_traversal(error));
458 }
459 }
460 Work::ReplaceWithFile {
461 source,
462 destination,
463 depth,
464 parent,
465 } => {
466 let frame = frames.len();
467 frames.push(Frame {
468 parent,
469 failed: false,
470 });
471 let next = [
472 Work::DeleteNode {
473 path: destination.clone(),
474 is_collection: true,
475 role: DavMutationTargetRole::Destination,
476 depth,
477 parent: Some(frame),
478 },
479 Work::FinalizeFileReplacement {
480 source,
481 destination,
482 depth,
483 frame,
484 },
485 ];
486 if let Err(error) = budget.reserve_work(next.len()) {
487 stop = Some(stop_from_traversal(error));
488 continue;
489 }
490 work.extend(next.into_iter().rev());
491 }
492 Work::TransferDirectory {
493 source,
494 destination,
495 depth,
496 parent,
497 } => {
498 if let Err(error) = budget.visit(depth) {
499 stop = Some(stop_from_traversal(error));
500 continue;
501 }
502 let frame = frames.len();
503 frames.push(Frame {
504 parent,
505 failed: false,
506 });
507 let command = DavMutationCommand {
508 operation,
509 step: DavMutationStepKind::PrepareCollection,
510 role: DavMutationTargetRole::Destination,
511 source: source.clone(),
512 destination: Some(destination.clone()),
513 };
514 let applied = match apply_step(
515 port,
516 command,
517 frame,
518 &mut frames,
519 &mut failures,
520 &mut budget,
521 )
522 .await
523 {
524 Ok(applied) => applied,
525 Err(error) => {
526 stop = Some(stop_from_traversal(error));
527 false
528 }
529 };
530 if !applied {
531 propagate_frame_failure(frame, &mut frames);
532 continue;
533 }
534 if !recurse_collections {
535 propagate_frame_failure(frame, &mut frames);
536 continue;
537 }
538
539 let source_children =
540 match read_children(enumerator, &source, cancellation, limits).await {
541 Ok(children) => children,
542 Err(error) => {
543 record_directory_read_failure(
544 error,
545 source,
546 frame,
547 &mut frames,
548 &mut failures,
549 &mut budget,
550 &mut stop,
551 );
552 propagate_frame_failure(frame, &mut frames);
553 continue;
554 }
555 };
556 let destination_children =
557 match read_children(enumerator, &destination, cancellation, limits).await {
558 Ok(children) => children,
559 Err(error) => {
560 record_directory_read_failure(
561 error,
562 destination,
563 frame,
564 &mut frames,
565 &mut failures,
566 &mut budget,
567 &mut stop,
568 );
569 propagate_frame_failure(frame, &mut frames);
570 continue;
571 }
572 };
573 let Some(child_depth) = depth.checked_add(1) else {
574 stop = Some(DavMutationStop::InsufficientStorage);
575 continue;
576 };
577 let Some(mut next) = merge_transfer_children_or_stop(
578 &destination,
579 &source_children,
580 &destination_children,
581 child_depth,
582 frame,
583 &mut frames,
584 &mut stop,
585 ) else {
586 continue;
587 };
588 next.push(Work::FinalizeTransfer { source, frame });
589 if let Err(error) = budget.reserve_work(next.len()) {
590 stop = Some(stop_from_traversal(error));
591 continue;
592 }
593 work.extend(next.into_iter().rev());
594 }
595 Work::DeleteNode {
596 path,
597 is_collection: false,
598 role,
599 depth,
600 parent,
601 } => {
602 if let Err(error) = budget.visit(depth) {
603 stop = Some(stop_from_traversal(error));
604 continue;
605 }
606 let frame = parent.unwrap_or_else(|| {
607 let frame = frames.len();
608 frames.push(Frame {
609 parent: None,
610 failed: false,
611 });
612 frame
613 });
614 let command = DavMutationCommand {
615 operation,
616 step: DavMutationStepKind::DeleteFile,
617 role,
618 source: path,
619 destination: None,
620 };
621 if let Err(error) = apply_step(
622 port,
623 command,
624 frame,
625 &mut frames,
626 &mut failures,
627 &mut budget,
628 )
629 .await
630 {
631 stop = Some(stop_from_traversal(error));
632 }
633 }
634 Work::DeleteNode {
635 path,
636 is_collection: true,
637 role,
638 depth,
639 parent,
640 } => {
641 if let Err(error) = budget.visit(depth) {
642 stop = Some(stop_from_traversal(error));
643 continue;
644 }
645 let frame = frames.len();
646 frames.push(Frame {
647 parent,
648 failed: false,
649 });
650 let children = match read_children(enumerator, &path, cancellation, limits).await {
651 Ok(children) => children,
652 Err(error) => {
653 record_directory_read_failure(
654 error,
655 path.clone(),
656 frame,
657 &mut frames,
658 &mut failures,
659 &mut budget,
660 &mut stop,
661 );
662 propagate_frame_failure(frame, &mut frames);
663 continue;
664 }
665 };
666 let Some(child_depth) = depth.checked_add(1) else {
667 stop = Some(DavMutationStop::InsufficientStorage);
668 continue;
669 };
670 let mut next = Vec::with_capacity(children.len().saturating_add(1));
671 for child in children {
672 next.push(Work::DeleteNode {
673 path: child.path,
674 is_collection: child.is_collection,
675 role,
676 depth: child_depth,
677 parent: Some(frame),
678 });
679 }
680 next.push(Work::FinalizeDelete { path, role, frame });
681 if let Err(error) = budget.reserve_work(next.len()) {
682 stop = Some(stop_from_traversal(error));
683 continue;
684 }
685 work.extend(next.into_iter().rev());
686 }
687 Work::FinalizeTransfer { source, frame } => {
688 if operation != DavMutationOperation::Move || frames[frame].failed {
689 propagate_frame_failure(frame, &mut frames);
690 continue;
691 }
692 let command = DavMutationCommand {
693 operation,
694 step: DavMutationStepKind::DeleteCollection,
695 role: DavMutationTargetRole::Source,
696 source,
697 destination: None,
698 };
699 if let Err(error) = apply_step(
700 port,
701 command,
702 frame,
703 &mut frames,
704 &mut failures,
705 &mut budget,
706 )
707 .await
708 {
709 stop = Some(stop_from_traversal(error));
710 }
711 propagate_frame_failure(frame, &mut frames);
712 }
713 Work::FinalizeFileReplacement {
714 source,
715 destination,
716 depth,
717 frame,
718 } => {
719 if frames[frame].failed {
720 propagate_frame_failure(frame, &mut frames);
721 continue;
722 }
723 if let Err(error) = budget.visit(depth) {
724 stop = Some(stop_from_traversal(error));
725 continue;
726 }
727 let step_kind = if operation == DavMutationOperation::Move {
728 DavMutationStepKind::MoveFile
729 } else {
730 DavMutationStepKind::CopyFile
731 };
732 let command = DavMutationCommand {
733 operation,
734 step: step_kind,
735 role: DavMutationTargetRole::Destination,
736 source,
737 destination: Some(destination),
738 };
739 if let Err(error) = apply_step(
740 port,
741 command,
742 frame,
743 &mut frames,
744 &mut failures,
745 &mut budget,
746 )
747 .await
748 {
749 stop = Some(stop_from_traversal(error));
750 }
751 propagate_frame_failure(frame, &mut frames);
752 }
753 Work::FinalizeDelete { path, role, frame } => {
754 if frames[frame].failed {
755 propagate_frame_failure(frame, &mut frames);
756 continue;
757 }
758 let command = DavMutationCommand {
759 operation,
760 step: DavMutationStepKind::DeleteCollection,
761 role,
762 source: path,
763 destination: None,
764 };
765 if let Err(error) = apply_step(
766 port,
767 command,
768 frame,
769 &mut frames,
770 &mut failures,
771 &mut budget,
772 )
773 .await
774 {
775 stop = Some(stop_from_traversal(error));
776 }
777 propagate_frame_failure(frame, &mut frames);
778 }
779 }
780 }
781
782 DavMutationOutcome {
783 operation,
784 request_target,
785 destination_existed,
786 failures,
787 stop,
788 progress: budget.progress(),
789 }
790}
791
792async fn apply_step<P: DavMutationPort>(
793 port: &P,
794 command: DavMutationCommand,
795 frame: usize,
796 frames: &mut [Frame],
797 failures: &mut Vec<DavMutationFailure>,
798 budget: &mut DavTraversalBudget,
799) -> Result<bool, DavTraversalError> {
800 match port.execute(command).await {
801 Ok(()) => {
802 budget.record_completed_mutation();
803 Ok(true)
804 }
805 Err(error) => {
806 frames[frame].failed = true;
807 budget.record_failure()?;
808 failures.push(error.into_failure());
809 Ok(false)
810 }
811 }
812}
813
814fn propagate_frame_failure(frame: usize, frames: &mut [Frame]) {
815 if !frames[frame].failed {
816 return;
817 }
818 if let Some(parent) = frames[frame].parent {
819 frames[parent].failed = true;
820 }
821}
822
823async fn read_children<E: DavDirectoryEnumerator, C: DavCancellation>(
824 enumerator: &E,
825 path: &DavPath,
826 cancellation: &C,
827 limits: DavMutationExecutorLimits,
828) -> Result<Vec<Child>, DavDirectoryReadError> {
829 let mut state = DavDirectoryPageState::new();
830 let mut children = Vec::new();
831 while let Some(page) = read_next_directory_page(
832 enumerator,
833 path,
834 &mut state,
835 limits.directory_page_entries,
836 limits.directory_pages,
837 cancellation,
838 )
839 .await?
840 {
841 if children
842 .len()
843 .checked_add(page.entries.len())
844 .is_none_or(|count| count > limits.maximum_directory_entries)
845 {
846 return Err(DavDirectoryReadError::Backend(crate::DavBackendError::new(
847 DavBackendErrorKind::InsufficientStorage,
848 )));
849 }
850 for entry in page.entries {
851 let is_collection = entry.metadata().is_dir();
852 let name = bytes::Bytes::copy_from_slice(entry.name());
853 let child_path = path.join_child(&name, is_collection).map_err(|_| {
854 DavDirectoryReadError::Backend(crate::DavBackendError::new(
855 DavBackendErrorKind::Internal,
856 ))
857 })?;
858 children.push(Child {
859 name,
860 key: bytes::Bytes::copy_from_slice(entry.stable_key()),
861 path: child_path,
862 is_collection,
863 });
864 }
865 }
866 Ok(children)
867}
868
869fn merge_transfer_children(
870 destination_root: &DavPath,
871 source: &[Child],
872 destination: &[Child],
873 depth: usize,
874 frame: usize,
875) -> Result<Vec<Work>, DavPathError> {
876 let mut work = Vec::with_capacity(source.len().saturating_add(destination.len()));
877 let mut source_order = (0..source.len()).collect::<Vec<_>>();
878 source_order.sort_unstable_by(|left, right| {
879 source[*left]
880 .namespace_name()
881 .cmp(source[*right].namespace_name())
882 .then_with(|| source[*left].key.cmp(&source[*right].key))
883 });
884 let mut destination_order = (0..destination.len()).collect::<Vec<_>>();
885 destination_order.sort_unstable_by(|left, right| {
886 destination[*left]
887 .namespace_name()
888 .cmp(destination[*right].namespace_name())
889 .then_with(|| destination[*left].key.cmp(&destination[*right].key))
890 });
891 let mut source_index = 0;
892 let mut destination_index = 0;
893 while source_index < source.len() || destination_index < destination.len() {
894 match (
895 source_order
896 .get(source_index)
897 .and_then(|index| source.get(*index)),
898 destination_order
899 .get(destination_index)
900 .and_then(|index| destination.get(*index)),
901 ) {
902 (Some(source_child), Some(destination_child)) => {
903 match source_child
904 .namespace_name()
905 .cmp(destination_child.namespace_name())
906 {
907 std::cmp::Ordering::Less => {
908 push_source_child(&mut work, destination_root, source_child, depth, frame)?;
909 source_index += 1;
910 }
911 std::cmp::Ordering::Equal => {
912 push_matching_children(
913 &mut work,
914 destination_root,
915 source_child,
916 destination_child,
917 depth,
918 frame,
919 )?;
920 source_index += 1;
921 destination_index += 1;
922 }
923 std::cmp::Ordering::Greater => {
924 work.push(destination_delete_work(destination_child, depth, frame));
925 destination_index += 1;
926 }
927 }
928 }
929 (Some(source_child), None) => {
930 push_source_child(&mut work, destination_root, source_child, depth, frame)?;
931 source_index += 1;
932 }
933 (None, Some(destination_child)) => {
934 work.push(destination_delete_work(destination_child, depth, frame));
935 destination_index += 1;
936 }
937 (None, None) => break,
938 }
939 }
940 Ok(work)
941}
942
943fn merge_transfer_children_or_stop(
944 destination_root: &DavPath,
945 source: &[Child],
946 destination: &[Child],
947 depth: usize,
948 frame: usize,
949 frames: &mut [Frame],
950 stop: &mut Option<DavMutationStop>,
951) -> Option<Vec<Work>> {
952 if let Ok(work) = merge_transfer_children(destination_root, source, destination, depth, frame) {
953 Some(work)
954 } else {
955 *stop = Some(DavMutationStop::Backend);
956 propagate_frame_failure(frame, frames);
957 None
958 }
959}
960
961fn push_source_child(
962 work: &mut Vec<Work>,
963 destination_root: &DavPath,
964 child: &Child,
965 depth: usize,
966 frame: usize,
967) -> Result<(), DavPathError> {
968 let destination = destination_root.join_child(&child.name, child.is_collection)?;
969 if child.is_collection {
970 work.push(Work::TransferDirectory {
971 source: child.path.clone(),
972 destination,
973 depth,
974 parent: Some(frame),
975 });
976 } else {
977 work.push(Work::TransferFile {
978 source: child.path.clone(),
979 destination,
980 depth,
981 parent: Some(frame),
982 });
983 }
984 Ok(())
985}
986
987fn push_matching_children(
988 work: &mut Vec<Work>,
989 destination_root: &DavPath,
990 source: &Child,
991 destination: &Child,
992 depth: usize,
993 frame: usize,
994) -> Result<(), DavPathError> {
995 let destination_path = destination_root.join_child(&source.name, source.is_collection)?;
996 if source.is_collection {
997 work.push(Work::TransferDirectory {
998 source: source.path.clone(),
999 destination: destination_path,
1000 depth,
1001 parent: Some(frame),
1002 });
1003 } else if destination.is_collection {
1004 work.push(Work::ReplaceWithFile {
1005 source: source.path.clone(),
1006 destination: destination.path.clone(),
1007 depth,
1008 parent: Some(frame),
1009 });
1010 } else {
1011 work.push(Work::TransferFile {
1012 source: source.path.clone(),
1013 destination: destination_path,
1014 depth,
1015 parent: Some(frame),
1016 });
1017 }
1018 Ok(())
1019}
1020
1021fn destination_delete_work(child: &Child, depth: usize, frame: usize) -> Work {
1022 Work::DeleteNode {
1023 path: child.path.clone(),
1024 is_collection: child.is_collection,
1025 role: DavMutationTargetRole::Destination,
1026 depth,
1027 parent: Some(frame),
1028 }
1029}
1030
1031fn record_directory_read_failure(
1032 error: DavDirectoryReadError,
1033 path: DavPath,
1034 frame: usize,
1035 frames: &mut [Frame],
1036 failures: &mut Vec<DavMutationFailure>,
1037 budget: &mut DavTraversalBudget,
1038 stop: &mut Option<DavMutationStop>,
1039) {
1040 match error {
1041 DavDirectoryReadError::Cancelled => *stop = Some(DavMutationStop::Cancelled),
1042 DavDirectoryReadError::Backend(error)
1043 if error.kind == DavBackendErrorKind::InsufficientStorage =>
1044 {
1045 *stop = Some(DavMutationStop::InsufficientStorage);
1046 }
1047 DavDirectoryReadError::PageLimitExceeded
1048 | DavDirectoryReadError::InvalidLimit
1049 | DavDirectoryReadError::InvalidPage(_) => {
1050 *stop = Some(DavMutationStop::InsufficientStorage);
1051 }
1052 DavDirectoryReadError::Backend(error) => {
1053 frames[frame].failed = true;
1054 if let Err(error) = budget.record_failure() {
1055 *stop = Some(stop_from_traversal(error));
1056 return;
1057 }
1058 failures.push(DavMutationStepError::from_backend(path, &error).into_failure());
1059 }
1060 }
1061}
1062
1063fn stop_from_traversal(error: DavTraversalError) -> DavMutationStop {
1064 match error.kind {
1065 DavTraversalErrorKind::Cancelled => DavMutationStop::Cancelled,
1066 DavTraversalErrorKind::InvalidLimits
1067 | DavTraversalErrorKind::VisitedResourceLimitExceeded
1068 | DavTraversalErrorKind::QueuedWorkLimitExceeded
1069 | DavTraversalErrorKind::FailureLimitExceeded
1070 | DavTraversalErrorKind::DepthLimitExceeded => DavMutationStop::InsufficientStorage,
1071 }
1072}
1073
1074pub fn mutation_outcome_response(
1080 prefix: &str,
1081 outcome: &DavMutationOutcome,
1082 limits: crate::DavMultiStatusLimits,
1083) -> Result<DavResponse, DavMutationComposeError> {
1084 let stop_status = outcome.stop.map(stop_status);
1085 let partial_execution = outcome.progress.completed_mutations != 0;
1086 let request_failure = !partial_execution
1087 && outcome.stop.is_none()
1088 && outcome.failures.len() == 1
1089 && outcome.failures[0].path() == &outcome.request_target;
1090 if request_failure {
1091 return direct_failure_response(prefix, &outcome.failures[0]);
1092 }
1093 if !outcome.failures.is_empty() || stop_status.is_some_and(|_| partial_execution) {
1094 let mut failures = outcome.failures.clone();
1095 if let Some(status) = stop_status
1096 && !failures.iter().any(|failure| {
1097 failure.path() == &outcome.request_target
1098 && failure.status_code() == status.as_u16()
1099 })
1100 {
1101 failures.push(DavMutationFailure::status(
1102 outcome.request_target.clone(),
1103 status.as_u16(),
1104 ));
1105 }
1106 return mutation_multistatus_response_with_limits(prefix, &failures, limits)
1107 .map_err(Into::into);
1108 }
1109 if let Some(status) = stop_status {
1110 return Ok(DavResponse::empty(status));
1111 }
1112 Ok(match outcome.operation {
1113 DavMutationOperation::Delete => delete_success_response(),
1114 DavMutationOperation::Copy | DavMutationOperation::Move => {
1115 mutation_success_response(outcome.destination_existed)
1116 }
1117 })
1118}
1119
1120fn stop_status(stop: DavMutationStop) -> StatusCode {
1121 match stop {
1122 DavMutationStop::Cancelled => StatusCode::SERVICE_UNAVAILABLE,
1123 DavMutationStop::Backend => StatusCode::INTERNAL_SERVER_ERROR,
1124 DavMutationStop::InsufficientStorage => StatusCode::INSUFFICIENT_STORAGE,
1125 }
1126}
1127
1128fn direct_failure_response(
1129 prefix: &str,
1130 failure: &DavMutationFailure,
1131) -> Result<DavResponse, DavMutationComposeError> {
1132 let status =
1133 StatusCode::from_u16(failure.status_code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
1134 if let Some(lock_path) = failure.lock_path() {
1135 return xml_document_response(
1136 status,
1137 &dav_error_element(&DavErrorCondition::LockTokenSubmitted {
1138 href: crate::href_for_dav_path(prefix, lock_path),
1139 }),
1140 )
1141 .map_err(Into::into);
1142 }
1143 Ok(no_store_empty_response(status))
1144}
1145
1146#[derive(Debug, thiserror::Error)]
1148pub enum DavMutationComposeError {
1149 #[error("failed to compose mutation Multi-Status response")]
1150 MultiStatus(#[from] DavMultiStatusError),
1151 #[error("failed to compose mutation DAV error response")]
1152 Xml(#[from] DavXmlError),
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157 use super::*;
1158
1159 #[test]
1160 fn invalid_private_child_state_stops_transfer_merge() {
1161 let source = Child {
1162 name: bytes::Bytes::from_static(b"invalid/name"),
1163 key: bytes::Bytes::from_static(b"source-key"),
1164 path: DavPath::new("/source/item").unwrap(),
1165 is_collection: false,
1166 };
1167 let destination = Child {
1168 name: bytes::Bytes::from_static(b"invalid/name"),
1169 key: bytes::Bytes::from_static(b"destination-key"),
1170 path: DavPath::new("/destination/item").unwrap(),
1171 is_collection: false,
1172 };
1173 let mut frames = [Frame {
1174 parent: None,
1175 failed: false,
1176 }];
1177 let mut stop = None;
1178
1179 let work = merge_transfer_children_or_stop(
1180 &DavPath::new("/destination/").unwrap(),
1181 &[source],
1182 &[destination],
1183 1,
1184 0,
1185 &mut frames,
1186 &mut stop,
1187 );
1188
1189 assert!(work.is_none());
1190 assert_eq!(stop, Some(DavMutationStop::Backend));
1191 }
1192}