1use std::marker::PhantomData;
4
5use aster_forge_utils::url::parse_absolute_url;
6use aster_forge_xml::is_valid_xml_local_name;
7use headers::Mime;
8use http::HeaderValue;
9
10use crate::extension::{
11 DavExtensionBodyKind, DavExtensionPackage, DavExtensionSet, DavLiveProperty, DavPreferenceSet,
12 DavReportType, extension_body_kind, extension_methods,
13};
14use crate::request::{DavBodyPolicy, DavMethod, DavMethodSet};
15use crate::{DavBackendError, DavPath};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum DavResourceState {
20 Unmapped,
21 File,
22 Collection,
23 MountRoot,
24 Principal,
25 RedirectReference,
26 AddMemberEndpoint,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
31pub enum DavVersioningState {
32 #[default]
34 Unsupported,
35 Versionable,
37 CheckedIn,
39 CheckedOut,
41 Version,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
47pub enum DavAutoVersion {
48 #[default]
50 None,
51 CheckoutCheckin,
53 CheckoutUnlockedCheckin,
55 Checkout,
57 LockedCheckout,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
63pub struct DavVersioningCapabilities {
64 pub state: DavVersioningState,
65 pub auto_version: DavAutoVersion,
66 pub write_locked: bool,
68 pub auto_checkout_lock: bool,
70 pub allow_version_delete: bool,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct DavCapabilityTarget {
77 pub path: DavPath,
78 pub is_mount_root: bool,
79}
80
81impl DavCapabilityTarget {
82 #[must_use]
83 pub fn new(path: DavPath, is_mount_root: bool) -> Self {
84 Self {
85 path,
86 is_mount_root,
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Default)]
93pub struct DavCapabilityContext {
94 pub principal: Option<String>,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum DavLockingCapability {
100 Disabled,
101 Class2,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
106pub struct DavCompatibilityCapabilities {
107 pub ms_author_via: bool,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum DavWritePrecondition {
113 Optional,
114 RequireStrongIfMatch,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
119pub enum DavPartialPutCapability {
120 #[default]
121 Disabled,
122 ContentRangeBytes {
123 precondition: DavWritePrecondition,
124 },
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum DavPatchBodyPolicy {
130 Bounded { maximum: usize },
131 Stream,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub struct DavPatchFormat {
137 pub media_type: &'static str,
139 pub body_policy: DavPatchBodyPolicy,
140 pub precondition: DavWritePrecondition,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
145pub enum DavPatchCapability {
146 #[default]
147 Disabled,
148 Formats(&'static [DavPatchFormat]),
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
153pub enum DavPrivateUpdateRangeCapability {
154 #[default]
155 Disabled,
156 XUpdateRange {
157 precondition: DavWritePrecondition,
158 },
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
163pub struct DavWriteCapabilities {
164 pub partial_put: DavPartialPutCapability,
165 pub patch: DavPatchCapability,
166 pub private_update_range: DavPrivateUpdateRangeCapability,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
171pub struct DavComplianceClasses {
172 pub class1: bool,
173 pub class3: bool,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
179pub struct DavSearchCapabilities {
180 pub grammars: &'static [DavSearchGrammar],
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub struct DavSearchGrammar {
191 pub coded_url: &'static str,
193 pub xml_namespace: &'static str,
195 pub xml_local_name: &'static str,
197}
198
199impl DavSearchGrammar {
200 pub const BASICSEARCH: Self = Self {
202 coded_url: "DAV:basicsearch",
203 xml_namespace: "DAV:",
204 xml_local_name: "basicsearch",
205 };
206
207 #[must_use]
209 pub const fn new(
210 coded_url: &'static str,
211 xml_namespace: &'static str,
212 xml_local_name: &'static str,
213 ) -> Self {
214 Self {
215 coded_url,
216 xml_namespace,
217 xml_local_name,
218 }
219 }
220}
221
222pub trait DavClass1Support: Send + Sync {}
224pub trait DavClass2Support: DavClass1Support {}
226pub trait DavClass3Support: DavClass1Support {}
234
235pub trait DavAccessControlSupport: DavClass1Support {}
237pub trait DavVersionControlSupport: DavClass1Support {}
239pub trait DavCheckoutInPlaceSupport: DavVersionControlSupport {}
240pub trait DavVersionHistorySupport: DavVersionControlSupport {}
241pub trait DavWorkspaceSupport: DavCheckoutInPlaceSupport + DavVersionHistorySupport {}
251pub trait DavUpdateSupport: DavVersionControlSupport {}
252pub trait DavLabelSupport: DavVersionControlSupport {}
253pub trait DavWorkingResourceSupport: DavVersionControlSupport {}
254pub trait DavMergeSupport: DavVersionControlSupport {}
255pub trait DavBaselineSupport: DavVersionControlSupport {}
256pub trait DavActivitySupport: DavVersionControlSupport {}
257pub trait DavVersionControlledCollectionSupport: DavVersionControlSupport {}
258pub trait DavSearchSupport: DavClass1Support {}
259pub trait DavQuotaSupport: DavClass1Support {}
260pub trait DavCollectionSyncSupport: DavClass1Support {}
261pub trait DavExtendedMkcolSupport: DavClass1Support {}
262pub trait DavCurrentPrincipalSupport: DavClass1Support {}
263pub trait DavOrderedCollectionsSupport: DavClass1Support {}
264pub trait DavRedirectReferencesSupport: DavClass1Support {}
265pub trait DavBindingsSupport: DavClass1Support {}
266pub trait DavAddMemberSupport: DavClass1Support {}
267pub trait DavPreferSupport: DavClass1Support {}
268
269pub trait DavPartialPutSupport: Send + Sync {}
270pub trait DavPatchSupport: Send + Sync {}
271pub trait DavPrivateUpdateRangeSupport: Send + Sync {}
272
273mod sealed {
274 pub trait Sealed {}
275 pub trait ExtensionSealed {}
276 pub trait Class1Profile<Provider: ?Sized> {}
277}
278
279pub trait DavCapabilityProfile<Provider: ?Sized>: sealed::Sealed {
281 const CLASS1: bool;
282 const CLASS2: bool;
283 const CLASS3: bool;
284 const EXTENSIONS: DavExtensionSet;
285 const PARTIAL_PUT: bool;
286 const PATCH: bool;
287 const PRIVATE_UPDATE_RANGE: bool;
288}
289
290pub struct DavNonDavProfile;
291pub struct DavClass1Profile;
292pub struct DavClass2Profile;
293pub struct DavClass3Profile;
295pub struct DavClass2And3Profile;
297
298impl sealed::Sealed for DavNonDavProfile {}
299impl sealed::Sealed for DavClass1Profile {}
300impl sealed::Sealed for DavClass2Profile {}
301impl sealed::Sealed for DavClass3Profile {}
302impl sealed::Sealed for DavClass2And3Profile {}
303
304macro_rules! base_profile {
305 ($profile:ty, $provider:path, $class1:expr, $class2:expr, $class3:expr) => {
306 impl<Provider: $provider + ?Sized> DavCapabilityProfile<Provider> for $profile {
307 const CLASS1: bool = $class1;
308 const CLASS2: bool = $class2;
309 const CLASS3: bool = $class3;
310 const EXTENSIONS: DavExtensionSet = DavExtensionSet::empty();
311 const PARTIAL_PUT: bool = false;
312 const PATCH: bool = false;
313 const PRIVATE_UPDATE_RANGE: bool = false;
314 }
315 };
316}
317
318impl<Provider: ?Sized> DavCapabilityProfile<Provider> for DavNonDavProfile {
319 const CLASS1: bool = false;
320 const CLASS2: bool = false;
321 const CLASS3: bool = false;
322 const EXTENSIONS: DavExtensionSet = DavExtensionSet::empty();
323 const PARTIAL_PUT: bool = false;
324 const PATCH: bool = false;
325 const PRIVATE_UPDATE_RANGE: bool = false;
326}
327base_profile!(DavClass1Profile, DavClass1Support, true, false, false);
328base_profile!(DavClass2Profile, DavClass2Support, true, true, false);
329base_profile!(DavClass3Profile, DavClass3Support, true, false, true);
330
331impl<Provider: DavClass1Support + ?Sized> sealed::Class1Profile<Provider> for DavClass1Profile {}
332impl<Provider: DavClass2Support + ?Sized> sealed::Class1Profile<Provider> for DavClass2Profile {}
333impl<Provider: DavClass3Support + ?Sized> sealed::Class1Profile<Provider> for DavClass3Profile {}
334
335impl<Provider: DavClass2Support + DavClass3Support + ?Sized> DavCapabilityProfile<Provider>
336 for DavClass2And3Profile
337{
338 const CLASS1: bool = true;
339 const CLASS2: bool = true;
340 const CLASS3: bool = true;
341 const EXTENSIONS: DavExtensionSet = DavExtensionSet::empty();
342 const PARTIAL_PUT: bool = false;
343 const PATCH: bool = false;
344 const PRIVATE_UPDATE_RANGE: bool = false;
345}
346
347impl<Provider: DavClass2Support + DavClass3Support + ?Sized> sealed::Class1Profile<Provider>
348 for DavClass2And3Profile
349{
350}
351
352pub struct DavWithExtension<Base, Extension>(PhantomData<fn() -> (Base, Extension)>);
354
355impl<Base: sealed::Sealed, Extension> sealed::Sealed for DavWithExtension<Base, Extension> {}
356
357pub trait DavExtensionMarker<Provider: ?Sized>: sealed::ExtensionSealed {
359 const PACKAGES: DavExtensionSet;
360}
361
362macro_rules! extension_marker {
363 ($marker:ident, $support:path, $package:ident $(, $required:ident)*) => {
364 pub struct $marker;
365 impl sealed::ExtensionSealed for $marker {}
366 impl<Provider: $support + ?Sized> DavExtensionMarker<Provider> for $marker {
367 const PACKAGES: DavExtensionSet = DavExtensionSet::from_packages(&[
368 $(DavExtensionPackage::$required,)*
369 DavExtensionPackage::$package,
370 ]);
371 }
372 };
373}
374
375extension_marker!(
376 DavAccessControlExtension,
377 DavAccessControlSupport,
378 AccessControl
379);
380extension_marker!(
381 DavVersionControlExtension,
382 DavVersionControlSupport,
383 VersionControl
384);
385extension_marker!(
386 DavCheckoutInPlaceExtension,
387 DavCheckoutInPlaceSupport,
388 CheckoutInPlace,
389 VersionControl
390);
391extension_marker!(
392 DavVersionHistoryExtension,
393 DavVersionHistorySupport,
394 VersionHistory,
395 VersionControl
396);
397extension_marker!(
398 DavWorkspaceExtension,
399 DavWorkspaceSupport,
400 Workspace,
401 VersionControl,
402 CheckoutInPlace,
403 VersionHistory
404);
405extension_marker!(DavUpdateExtension, DavUpdateSupport, Update, VersionControl);
406extension_marker!(DavLabelExtension, DavLabelSupport, Label, VersionControl);
407extension_marker!(
408 DavWorkingResourceExtension,
409 DavWorkingResourceSupport,
410 WorkingResource,
411 VersionControl
412);
413extension_marker!(DavMergeExtension, DavMergeSupport, Merge, VersionControl);
414extension_marker!(
415 DavBaselineExtension,
416 DavBaselineSupport,
417 Baseline,
418 VersionControl
419);
420extension_marker!(
421 DavActivityExtension,
422 DavActivitySupport,
423 Activity,
424 VersionControl
425);
426extension_marker!(
427 DavVersionControlledCollectionExtension,
428 DavVersionControlledCollectionSupport,
429 VersionControlledCollection,
430 VersionControl
431);
432extension_marker!(DavSearchExtension, DavSearchSupport, Search);
433extension_marker!(DavQuotaExtension, DavQuotaSupport, Quota);
434extension_marker!(
435 DavCollectionSyncExtension,
436 DavCollectionSyncSupport,
437 CollectionSync
438);
439extension_marker!(
440 DavExtendedMkcolExtension,
441 DavExtendedMkcolSupport,
442 ExtendedMkcol
443);
444extension_marker!(
445 DavCurrentPrincipalExtension,
446 DavCurrentPrincipalSupport,
447 CurrentPrincipal
448);
449extension_marker!(
450 DavOrderedCollectionsExtension,
451 DavOrderedCollectionsSupport,
452 OrderedCollections
453);
454extension_marker!(
455 DavRedirectReferencesExtension,
456 DavRedirectReferencesSupport,
457 RedirectReferences
458);
459extension_marker!(DavBindingsExtension, DavBindingsSupport, Bindings);
460extension_marker!(DavAddMemberExtension, DavAddMemberSupport, AddMember);
461extension_marker!(DavPreferExtension, DavPreferSupport, Prefer);
462
463impl<Provider, Base, Extension> DavCapabilityProfile<Provider> for DavWithExtension<Base, Extension>
464where
465 Provider: ?Sized,
466 Base: DavCapabilityProfile<Provider> + sealed::Class1Profile<Provider>,
467 Extension: DavExtensionMarker<Provider>,
468{
469 const CLASS1: bool = Base::CLASS1;
470 const CLASS2: bool = Base::CLASS2;
471 const CLASS3: bool = Base::CLASS3;
472 const EXTENSIONS: DavExtensionSet = Base::EXTENSIONS.union(Extension::PACKAGES);
473 const PARTIAL_PUT: bool = Base::PARTIAL_PUT;
474 const PATCH: bool = Base::PATCH;
475 const PRIVATE_UPDATE_RANGE: bool = Base::PRIVATE_UPDATE_RANGE;
476}
477
478impl<Provider, Base, Extension> sealed::Class1Profile<Provider>
479 for DavWithExtension<Base, Extension>
480where
481 Provider: ?Sized,
482 Base: sealed::Class1Profile<Provider>,
483 Extension: DavExtensionMarker<Provider>,
484{
485}
486
487#[macro_export]
534macro_rules! dav_capability_profile {
535 ($base:ty $(,)?) => { $base };
536 ($base:ty; $extension:ty $(,)?) => {
537 $crate::DavWithExtension<$base, $extension>
538 };
539 ($base:ty; $extension:ty, $($remaining:ty),+ $(,)?) => {
540 $crate::dav_capability_profile!(
541 $crate::DavWithExtension<$base, $extension>;
542 $($remaining),+
543 )
544 };
545}
546
547pub struct DavWithPartialPut<Base>(PhantomData<fn() -> Base>);
548pub struct DavWithPatch<Base>(PhantomData<fn() -> Base>);
549pub struct DavWithPrivateUpdateRange<Base>(PhantomData<fn() -> Base>);
550
551impl<Provider: ?Sized, Base: sealed::Class1Profile<Provider>> sealed::Class1Profile<Provider>
552 for DavWithPartialPut<Base>
553{
554}
555impl<Provider: ?Sized, Base: sealed::Class1Profile<Provider>> sealed::Class1Profile<Provider>
556 for DavWithPatch<Base>
557{
558}
559impl<Provider: ?Sized, Base: sealed::Class1Profile<Provider>> sealed::Class1Profile<Provider>
560 for DavWithPrivateUpdateRange<Base>
561{
562}
563
564impl<Base: sealed::Sealed> sealed::Sealed for DavWithPartialPut<Base> {}
565impl<Base: sealed::Sealed> sealed::Sealed for DavWithPatch<Base> {}
566impl<Base: sealed::Sealed> sealed::Sealed for DavWithPrivateUpdateRange<Base> {}
567
568macro_rules! write_profile {
569 ($wrapper:ident, $support:path, $partial:expr, $patch:expr, $private:expr) => {
570 impl<Provider, Base> DavCapabilityProfile<Provider> for $wrapper<Base>
571 where
572 Provider: $support + ?Sized,
573 Base: DavCapabilityProfile<Provider>,
574 {
575 const CLASS1: bool = Base::CLASS1;
576 const CLASS2: bool = Base::CLASS2;
577 const CLASS3: bool = Base::CLASS3;
578 const EXTENSIONS: DavExtensionSet = Base::EXTENSIONS;
579 const PARTIAL_PUT: bool = $partial || Base::PARTIAL_PUT;
580 const PATCH: bool = $patch || Base::PATCH;
581 const PRIVATE_UPDATE_RANGE: bool = $private || Base::PRIVATE_UPDATE_RANGE;
582 }
583 };
584}
585
586write_profile!(DavWithPartialPut, DavPartialPutSupport, true, false, false);
587write_profile!(DavWithPatch, DavPatchSupport, false, true, false);
588write_profile!(
589 DavWithPrivateUpdateRange,
590 DavPrivateUpdateRangeSupport,
591 false,
592 false,
593 true
594);
595
596#[derive(Debug, Clone, PartialEq, Eq)]
598pub struct DavCapabilityDeclaration {
599 pub resource: DavResourceState,
600 pub versioning: DavVersioningCapabilities,
601 pub methods: DavMethodSet,
603 pub locking: DavLockingCapability,
604 pub extensions: DavExtensionSet,
605 pub search: DavSearchCapabilities,
606 pub compatibility: DavCompatibilityCapabilities,
607 pub writes: DavWriteCapabilities,
608 pub compliance: DavComplianceClasses,
609}
610
611impl DavCapabilityDeclaration {
612 #[must_use]
613 pub const fn new(resource: DavResourceState, methods: DavMethodSet) -> Self {
614 Self {
615 resource,
616 versioning: DavVersioningCapabilities {
617 state: DavVersioningState::Unsupported,
618 auto_version: DavAutoVersion::None,
619 write_locked: false,
620 auto_checkout_lock: false,
621 allow_version_delete: false,
622 },
623 methods,
624 locking: DavLockingCapability::Disabled,
625 extensions: DavExtensionSet::empty(),
626 search: DavSearchCapabilities { grammars: &[] },
627 compatibility: DavCompatibilityCapabilities {
628 ms_author_via: false,
629 },
630 writes: DavWriteCapabilities {
631 partial_put: DavPartialPutCapability::Disabled,
632 patch: DavPatchCapability::Disabled,
633 private_update_range: DavPrivateUpdateRangeCapability::Disabled,
634 },
635 compliance: DavComplianceClasses {
636 class1: false,
637 class3: false,
638 },
639 }
640 }
641}
642
643#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
645pub enum DavCapabilityPlanError {
646 #[error("OPTIONS must be advertised for every WebDAV target")]
647 OptionsMissing,
648 #[error("HEAD requires GET in a WebDAV capability declaration")]
649 HeadWithoutGet,
650 #[error("DAV locking compliance class 2 requires class 1")]
651 Class2WithoutClass1,
652 #[error("DAV compliance class 2 requires LOCK and UNLOCK methods")]
653 Class2WithoutLockMethods,
654 #[error("LOCK and UNLOCK require DAV compliance class 2")]
655 LockMethodsWithoutClass2,
656 #[error("DAV compliance class 3 requires class 1")]
657 Class3WithoutClass1,
658 #[error("RFC extension {package:?} requires class 1")]
659 ExtensionWithoutClass1 { package: DavExtensionPackage },
660 #[error("RFC extension {package:?} requires {required:?}")]
661 ExtensionMissingPrerequisite {
662 package: DavExtensionPackage,
663 required: DavExtensionPackage,
664 },
665 #[error("RFC extension {package:?} does not apply to {resource:?}")]
666 ExtensionNotApplicable {
667 package: DavExtensionPackage,
668 resource: DavResourceState,
669 },
670 #[error("the RFC 3253 version-control package requires explicit target versioning facts")]
671 VersionControlWithoutTarget,
672 #[error("versioning target facts require the RFC 3253 version-control package")]
673 VersioningTargetWithoutPackage,
674 #[error("automatic versioning is only valid for a checked-in or checked-out resource")]
675 AutoVersionNotApplicable,
676 #[error("a write-locked versioning target requires DAV locking compliance class 2")]
677 WriteLockWithoutClass2,
678 #[error("an automatic checkout lock is only valid for a write-locked checked-out resource")]
679 AutoCheckoutLockNotApplicable,
680 #[error(
681 "an automatic checkout lock requires an auto-version mode that can leave a locked resource checked out"
682 )]
683 AutoCheckoutLockWithoutApplicableMode,
684 #[error("version DELETE policy is only applicable to immutable version resources")]
685 VersionDeletePolicyNotApplicable,
686 #[error("VERSION-CONTROL is only applicable to versionable or version-controlled resources")]
687 VersionControlMethodNotApplicable,
688 #[error("method {method:?} requires an applicable RFC extension package")]
689 ExtensionMethodWithoutPackage { method: DavMethod },
690 #[error("SEARCH requires at least DAV:basicsearch")]
691 SearchWithoutBasicSearch,
692 #[error("SEARCH grammars require the SEARCH extension package")]
693 SearchGrammarsWithoutPackage,
694 #[error("SEARCH grammar {index} has invalid coded URL {coded_url:?}")]
695 InvalidSearchGrammarCodedUrl {
696 index: usize,
697 coded_url: &'static str,
698 },
699 #[error("SEARCH grammar {index} has invalid XML local name {xml_local_name:?}")]
700 InvalidSearchGrammarXmlLocalName {
701 index: usize,
702 xml_local_name: &'static str,
703 },
704 #[error("SEARCH grammar {index} has invalid XML namespace {xml_namespace:?}")]
705 InvalidSearchGrammarXmlNamespace {
706 index: usize,
707 xml_namespace: &'static str,
708 },
709 #[error("SEARCH grammar {index} duplicates grammar {previous_index}")]
710 DuplicateSearchGrammar {
711 index: usize,
712 previous_index: usize,
713 coded_url: &'static str,
714 xml_namespace: &'static str,
715 xml_local_name: &'static str,
716 },
717 #[error("partial PUT requires the PUT method")]
718 PartialPutWithoutPut,
719 #[error("PATCH requires at least one declared patch document format")]
720 PatchWithoutFormats,
721 #[error("declared patch document formats require the PATCH method")]
722 PatchFormatsWithoutMethod,
723 #[error("a patch document media type is invalid")]
724 InvalidPatchMediaType,
725 #[error("patch document media types must be unique")]
726 DuplicatePatchMediaType,
727 #[error("private X-Update-Range support requires the PUT method")]
728 PrivateUpdateRangeWithoutPut,
729 #[error("runtime class 1 capability exceeds the provider's static profile")]
730 Class1ExceedsProfile,
731 #[error("runtime class 2 capability exceeds the provider's static profile")]
732 Class2ExceedsProfile,
733 #[error("runtime class 3 capability exceeds the provider's static profile")]
734 Class3ExceedsProfile,
735 #[error("runtime extension {package:?} exceeds the provider's static profile")]
736 ExtensionExceedsProfile { package: DavExtensionPackage },
737 #[error("runtime partial PUT capability exceeds the provider's static profile")]
738 PartialPutExceedsProfile,
739 #[error("runtime PATCH capability exceeds the provider's static profile")]
740 PatchExceedsProfile,
741 #[error("runtime private update-range capability exceeds the provider's static profile")]
742 PrivateUpdateRangeExceedsProfile,
743 #[error("capability header representation is invalid")]
744 InvalidHeaderRepresentation,
745}
746
747#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
748pub enum DavCapabilityEvaluationError {
749 #[error(transparent)]
750 Backend(#[from] DavBackendError),
751 #[error(transparent)]
752 Plan(#[from] DavCapabilityPlanError),
753}
754
755#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
756pub enum DavMethodGateError {
757 #[error("WebDAV method is not allowed for this resource")]
758 MethodNotAllowed,
759}
760
761#[derive(Debug, Clone, PartialEq, Eq)]
763pub struct DavCapabilitySnapshot {
764 declaration: DavCapabilityDeclaration,
765 supported_methods: DavMethodSet,
766 dispatch_methods: DavMethodSet,
767 live_properties: DavLivePropertySet,
768 reports: DavReportSet,
769 preferences: DavPreferenceSet,
770 allow: HeaderValue,
771 dav: Option<HeaderValue>,
772 dasl: Option<HeaderValue>,
773 accept_patch: Option<HeaderValue>,
774 ms_author_via: bool,
775}
776
777impl DavCapabilitySnapshot {
778 #[must_use]
779 pub fn declaration(&self) -> &DavCapabilityDeclaration {
780 &self.declaration
781 }
782
783 #[must_use]
784 pub const fn methods(&self) -> DavMethodSet {
785 self.declaration.methods
786 }
787
788 #[must_use]
789 pub const fn supported_methods(&self) -> DavMethodSet {
790 self.supported_methods
791 }
792
793 #[must_use]
794 pub const fn allows(&self, method: DavMethod) -> bool {
795 self.methods().contains(method)
796 }
797
798 #[must_use]
799 pub const fn supports(&self, method: DavMethod) -> bool {
800 self.supported_methods.contains(method)
801 }
802
803 #[must_use]
805 pub const fn dispatches(&self, method: DavMethod) -> bool {
806 self.dispatch_methods.contains(method)
807 }
808
809 #[must_use]
810 pub const fn extensions(&self) -> DavExtensionSet {
811 self.declaration.extensions
812 }
813
814 #[must_use]
815 pub const fn supports_extension(&self, package: DavExtensionPackage) -> bool {
816 self.extensions().contains(package)
817 }
818
819 #[must_use]
820 pub const fn supports_live_property(&self, property: DavLiveProperty) -> bool {
821 self.live_properties.contains(property)
822 }
823
824 #[must_use]
825 pub const fn supports_report(&self, report: DavReportType) -> bool {
826 self.reports.contains(report)
827 }
828
829 #[must_use]
830 pub const fn preferences(&self) -> DavPreferenceSet {
831 self.preferences
832 }
833
834 #[must_use]
835 pub fn allow_header(&self) -> &HeaderValue {
836 &self.allow
837 }
838
839 #[must_use]
840 pub fn dav_header(&self) -> Option<&HeaderValue> {
841 self.dav.as_ref()
842 }
843
844 #[must_use]
845 pub fn dasl_header(&self) -> Option<&HeaderValue> {
846 self.dasl.as_ref()
847 }
848
849 #[must_use]
850 pub fn accept_patch_header(&self) -> Option<&HeaderValue> {
851 self.accept_patch.as_ref()
852 }
853
854 #[must_use]
855 pub const fn writes(&self) -> DavWriteCapabilities {
856 self.declaration.writes
857 }
858
859 pub(crate) const fn patch_formats(&self) -> Option<&'static [DavPatchFormat]> {
860 match self.declaration.writes.patch {
861 DavPatchCapability::Disabled => None,
862 DavPatchCapability::Formats(formats) => Some(formats),
863 }
864 }
865
866 #[must_use]
867 pub const fn has_ms_author_via(&self) -> bool {
868 self.ms_author_via
869 }
870
871 pub fn body_policy(
877 &self,
878 method: DavMethod,
879 xml_limit: usize,
880 ) -> Result<Option<DavBodyPolicy>, DavMethodGateError> {
881 if !self.dispatches(method) {
882 return Err(DavMethodGateError::MethodNotAllowed);
883 }
884 let extension_kind = || {
885 extension_body_kind(
886 self.declaration.extensions,
887 self.declaration.resource,
888 method,
889 )
890 };
891 Ok(match method {
892 DavMethod::Patch => None,
893 DavMethod::Mkcol => Some(extension_kind().map_or(DavBodyPolicy::Empty, |kind| {
894 extension_body_policy(kind, xml_limit)
895 })),
896 DavMethod::Options
897 | DavMethod::Delete
898 | DavMethod::Copy
899 | DavMethod::Move
900 | DavMethod::Unlock => Some(DavBodyPolicy::Empty),
901 DavMethod::Propfind | DavMethod::Proppatch | DavMethod::Lock => {
902 Some(DavBodyPolicy::BoundedXml { maximum: xml_limit })
903 }
904 DavMethod::Post => Some(extension_kind().map_or(DavBodyPolicy::Stream, |kind| {
905 extension_body_policy(kind, xml_limit)
906 })),
907 DavMethod::Put => Some(DavBodyPolicy::Stream),
908 DavMethod::Get | DavMethod::Head => Some(DavBodyPolicy::Unused),
909 DavMethod::Acl
910 | DavMethod::Report
911 | DavMethod::VersionControl
912 | DavMethod::Checkout
913 | DavMethod::Checkin
914 | DavMethod::Uncheckout
915 | DavMethod::Mkworkspace
916 | DavMethod::Update
917 | DavMethod::Label
918 | DavMethod::Merge
919 | DavMethod::BaselineControl
920 | DavMethod::Mkactivity
921 | DavMethod::Search
922 | DavMethod::Orderpatch
923 | DavMethod::Mkredirectref
924 | DavMethod::Updateredirectref
925 | DavMethod::Bind
926 | DavMethod::Unbind
927 | DavMethod::Rebind => {
928 extension_kind().map(|kind| extension_body_policy(kind, xml_limit))
929 }
930 })
931 }
932}
933
934pub fn plan_capabilities(
940 mut declaration: DavCapabilityDeclaration,
941) -> Result<DavCapabilitySnapshot, DavCapabilityPlanError> {
942 validate_base_methods(&mut declaration)?;
943 validate_compliance(&declaration)?;
944 validate_extensions(&declaration)?;
945 validate_extension_methods(&declaration)?;
946 validate_writes(&declaration)?;
947
948 let package_methods = extension_methods_for_declaration(&declaration);
949 let supported_methods = declaration.methods.union(package_methods);
950 let dispatch_methods = declaration
951 .methods
952 .union(unmapped_dispatch_methods(declaration.resource));
953 let allow = header_value(&declaration.methods.render())?;
954 let dav = render_dav_header(&declaration)?;
955 let dasl = render_dasl_header(&declaration)?;
956 let accept_patch = match declaration.writes.patch {
957 DavPatchCapability::Disabled => None,
958 DavPatchCapability::Formats(formats) => Some(header_value(&render_patch_formats(formats))?),
959 };
960 let mut live_properties = DavLivePropertySet::empty();
961 let mut reports = DavReportSet::empty();
962 let mut preferences = DavPreferenceSet::empty();
963 for package in declaration.extensions.iter() {
964 let descriptor = package.descriptor();
965 for property in descriptor.live_properties {
966 if package != DavExtensionPackage::VersionControl
967 || versioning_live_property(declaration.versioning.state, *property)
968 {
969 live_properties.insert(*property);
970 }
971 }
972 for report in descriptor.reports {
973 if package != DavExtensionPackage::VersionControl
974 || versioning_report(declaration.versioning.state, *report)
975 {
976 reports.insert(*report);
977 }
978 }
979 preferences = preferences.union(descriptor.preferences);
980 }
981 let ms_author_via = declaration.compatibility.ms_author_via;
982 Ok(DavCapabilitySnapshot {
983 declaration,
984 supported_methods,
985 dispatch_methods,
986 live_properties,
987 reports,
988 preferences,
989 allow,
990 dav,
991 dasl,
992 accept_patch,
993 ms_author_via,
994 })
995}
996
997const fn unmapped_dispatch_methods(resource: DavResourceState) -> DavMethodSet {
998 if matches!(resource, DavResourceState::Unmapped) {
999 DavMethodSet::from_methods(&[
1000 DavMethod::Get,
1001 DavMethod::Head,
1002 DavMethod::Delete,
1003 DavMethod::Copy,
1004 DavMethod::Move,
1005 DavMethod::Propfind,
1006 DavMethod::Proppatch,
1007 ])
1008 } else {
1009 DavMethodSet::empty()
1010 }
1011}
1012
1013#[expect(
1014 async_fn_in_trait,
1015 reason = "The provider is generic and intentionally preserves native async futures."
1016)]
1017pub trait DavCapabilityProvider: Send + Sync {
1018 type Profile: DavCapabilityProfile<Self>;
1019
1020 async fn capabilities(
1021 &self,
1022 target: &DavCapabilityTarget,
1023 context: &DavCapabilityContext,
1024 ) -> Result<DavCapabilityDeclaration, DavBackendError>;
1025}
1026
1027pub async fn plan_capabilities_with_provider<Provider: DavCapabilityProvider>(
1033 provider: &Provider,
1034 target: &DavCapabilityTarget,
1035 context: &DavCapabilityContext,
1036) -> Result<DavCapabilitySnapshot, DavCapabilityEvaluationError> {
1037 let declaration = provider.capabilities(target, context).await?;
1038 if declaration.compliance.class1 && !Provider::Profile::CLASS1 {
1039 return Err(DavCapabilityPlanError::Class1ExceedsProfile.into());
1040 }
1041 if declaration.locking == DavLockingCapability::Class2 && !Provider::Profile::CLASS2 {
1042 return Err(DavCapabilityPlanError::Class2ExceedsProfile.into());
1043 }
1044 if declaration.compliance.class3 && !Provider::Profile::CLASS3 {
1045 return Err(DavCapabilityPlanError::Class3ExceedsProfile.into());
1046 }
1047 for package in declaration.extensions.iter() {
1048 if !Provider::Profile::EXTENSIONS.contains(package) {
1049 return Err(DavCapabilityPlanError::ExtensionExceedsProfile { package }.into());
1050 }
1051 }
1052 if declaration.writes.partial_put != DavPartialPutCapability::Disabled
1053 && !Provider::Profile::PARTIAL_PUT
1054 {
1055 return Err(DavCapabilityPlanError::PartialPutExceedsProfile.into());
1056 }
1057 if declaration.writes.patch != DavPatchCapability::Disabled && !Provider::Profile::PATCH {
1058 return Err(DavCapabilityPlanError::PatchExceedsProfile.into());
1059 }
1060 if declaration.writes.private_update_range != DavPrivateUpdateRangeCapability::Disabled
1061 && !Provider::Profile::PRIVATE_UPDATE_RANGE
1062 {
1063 return Err(DavCapabilityPlanError::PrivateUpdateRangeExceedsProfile.into());
1064 }
1065 plan_capabilities(declaration).map_err(Into::into)
1066}
1067
1068fn validate_base_methods(
1069 declaration: &mut DavCapabilityDeclaration,
1070) -> Result<(), DavCapabilityPlanError> {
1071 if !declaration.methods.contains(DavMethod::Options) {
1072 return Err(DavCapabilityPlanError::OptionsMissing);
1073 }
1074 if declaration.methods.contains(DavMethod::Get) {
1075 declaration.methods = declaration.methods.with(DavMethod::Head);
1076 }
1077 if declaration.methods.contains(DavMethod::Head)
1078 && !declaration.methods.contains(DavMethod::Get)
1079 {
1080 return Err(DavCapabilityPlanError::HeadWithoutGet);
1081 }
1082 Ok(())
1083}
1084
1085fn validate_compliance(
1086 declaration: &DavCapabilityDeclaration,
1087) -> Result<(), DavCapabilityPlanError> {
1088 let locking_enabled = declaration.locking == DavLockingCapability::Class2;
1089 if locking_enabled && !declaration.compliance.class1 {
1090 return Err(DavCapabilityPlanError::Class2WithoutClass1);
1091 }
1092 if declaration.compliance.class3 && !declaration.compliance.class1 {
1093 return Err(DavCapabilityPlanError::Class3WithoutClass1);
1094 }
1095 let has_lock_method = declaration.methods.contains(DavMethod::Lock)
1096 || declaration.methods.contains(DavMethod::Unlock);
1097 let has_lock_methods = declaration.methods.contains(DavMethod::Lock)
1098 && declaration.methods.contains(DavMethod::Unlock);
1099 if (has_lock_method || locking_enabled) && !has_lock_methods {
1100 return Err(DavCapabilityPlanError::Class2WithoutLockMethods);
1101 }
1102 if has_lock_methods && !locking_enabled {
1103 return Err(DavCapabilityPlanError::LockMethodsWithoutClass2);
1104 }
1105 Ok(())
1106}
1107
1108fn validate_extensions(
1109 declaration: &DavCapabilityDeclaration,
1110) -> Result<(), DavCapabilityPlanError> {
1111 if !declaration.compliance.class1
1112 && let Some(package) = declaration.extensions.iter().next()
1113 {
1114 return Err(DavCapabilityPlanError::ExtensionWithoutClass1 { package });
1115 }
1116 for package in declaration.extensions.iter() {
1117 let descriptor = package.descriptor();
1118 for required in descriptor.prerequisites.iter() {
1119 if !declaration.extensions.contains(required) {
1120 return Err(DavCapabilityPlanError::ExtensionMissingPrerequisite {
1121 package,
1122 required,
1123 });
1124 }
1125 }
1126 if !descriptor.resources.contains(declaration.resource) {
1127 return Err(DavCapabilityPlanError::ExtensionNotApplicable {
1128 package,
1129 resource: declaration.resource,
1130 });
1131 }
1132 }
1133 let has_version_control = declaration
1134 .extensions
1135 .contains(DavExtensionPackage::VersionControl);
1136 if has_version_control && declaration.versioning.state == DavVersioningState::Unsupported {
1137 return Err(DavCapabilityPlanError::VersionControlWithoutTarget);
1138 }
1139 if !has_version_control && declaration.versioning.state != DavVersioningState::Unsupported {
1140 return Err(DavCapabilityPlanError::VersioningTargetWithoutPackage);
1141 }
1142 if declaration.versioning.auto_version != DavAutoVersion::None
1143 && !matches!(
1144 declaration.versioning.state,
1145 DavVersioningState::CheckedIn | DavVersioningState::CheckedOut
1146 )
1147 {
1148 return Err(DavCapabilityPlanError::AutoVersionNotApplicable);
1149 }
1150 if declaration.versioning.write_locked && declaration.locking != DavLockingCapability::Class2 {
1151 return Err(DavCapabilityPlanError::WriteLockWithoutClass2);
1152 }
1153 if declaration.versioning.auto_checkout_lock
1154 && (!declaration.versioning.write_locked
1155 || declaration.versioning.state != DavVersioningState::CheckedOut)
1156 {
1157 return Err(DavCapabilityPlanError::AutoCheckoutLockNotApplicable);
1158 }
1159 if declaration.versioning.auto_checkout_lock
1160 && !matches!(
1161 declaration.versioning.auto_version,
1162 DavAutoVersion::CheckoutUnlockedCheckin
1163 | DavAutoVersion::Checkout
1164 | DavAutoVersion::LockedCheckout
1165 )
1166 {
1167 return Err(DavCapabilityPlanError::AutoCheckoutLockWithoutApplicableMode);
1168 }
1169 if declaration.versioning.allow_version_delete
1170 && declaration.versioning.state != DavVersioningState::Version
1171 {
1172 return Err(DavCapabilityPlanError::VersionDeletePolicyNotApplicable);
1173 }
1174 let search_enabled = declaration.extensions.contains(DavExtensionPackage::Search);
1175 if search_enabled {
1176 if !declaration
1177 .search
1178 .grammars
1179 .contains(&DavSearchGrammar::BASICSEARCH)
1180 {
1181 return Err(DavCapabilityPlanError::SearchWithoutBasicSearch);
1182 }
1183 validate_search_grammars(declaration.search.grammars)?;
1184 } else if !declaration.search.grammars.is_empty() {
1185 return Err(DavCapabilityPlanError::SearchGrammarsWithoutPackage);
1186 }
1187 Ok(())
1188}
1189
1190fn validate_extension_methods(
1191 declaration: &DavCapabilityDeclaration,
1192) -> Result<(), DavCapabilityPlanError> {
1193 if declaration
1194 .extensions
1195 .contains(DavExtensionPackage::VersionControl)
1196 && declaration.methods.contains(DavMethod::VersionControl)
1197 && !matches!(
1198 declaration.versioning.state,
1199 DavVersioningState::Versionable
1200 | DavVersioningState::CheckedIn
1201 | DavVersioningState::CheckedOut
1202 )
1203 {
1204 return Err(DavCapabilityPlanError::VersionControlMethodNotApplicable);
1205 }
1206 let package_methods = extension_methods_for_declaration(declaration);
1207 for method in declaration.methods.iter() {
1208 if is_extension_only_method(method) && !package_methods.contains(method) {
1209 return Err(DavCapabilityPlanError::ExtensionMethodWithoutPackage { method });
1210 }
1211 }
1212 Ok(())
1213}
1214
1215const fn versioning_methods(methods: DavMethodSet, state: DavVersioningState) -> DavMethodSet {
1216 match state {
1217 DavVersioningState::Versionable
1218 | DavVersioningState::CheckedIn
1219 | DavVersioningState::CheckedOut => methods,
1220 DavVersioningState::Version => methods.without(DavMethod::VersionControl),
1221 DavVersioningState::Unsupported => methods
1222 .without(DavMethod::VersionControl)
1223 .without(DavMethod::Report),
1224 }
1225}
1226
1227fn extension_methods_for_declaration(declaration: &DavCapabilityDeclaration) -> DavMethodSet {
1228 let methods = extension_methods(declaration.extensions, declaration.resource);
1229 if declaration
1230 .extensions
1231 .contains(DavExtensionPackage::VersionControl)
1232 {
1233 versioning_methods(methods, declaration.versioning.state)
1234 } else {
1235 methods
1236 }
1237}
1238
1239const fn versioning_report(state: DavVersioningState, report: DavReportType) -> bool {
1240 match report {
1241 DavReportType::ExpandProperty => !matches!(state, DavVersioningState::Unsupported),
1242 DavReportType::VersionTree => matches!(
1243 state,
1244 DavVersioningState::CheckedIn
1245 | DavVersioningState::CheckedOut
1246 | DavVersioningState::Version
1247 ),
1248 _ => true,
1249 }
1250}
1251
1252const fn versioning_live_property(state: DavVersioningState, property: DavLiveProperty) -> bool {
1253 match property {
1254 DavLiveProperty::CheckedIn => matches!(state, DavVersioningState::CheckedIn),
1255 DavLiveProperty::CheckedOut => matches!(state, DavVersioningState::CheckedOut),
1256 DavLiveProperty::AutoVersion => matches!(
1257 state,
1258 DavVersioningState::CheckedIn | DavVersioningState::CheckedOut
1259 ),
1260 DavLiveProperty::PredecessorSet => matches!(
1261 state,
1262 DavVersioningState::CheckedOut | DavVersioningState::Version
1263 ),
1264 DavLiveProperty::SuccessorSet
1265 | DavLiveProperty::CheckoutSet
1266 | DavLiveProperty::VersionName => matches!(state, DavVersioningState::Version),
1267 _ => true,
1268 }
1269}
1270
1271const fn is_extension_only_method(method: DavMethod) -> bool {
1272 matches!(
1273 method,
1274 DavMethod::Acl
1275 | DavMethod::Report
1276 | DavMethod::VersionControl
1277 | DavMethod::Checkout
1278 | DavMethod::Checkin
1279 | DavMethod::Uncheckout
1280 | DavMethod::Mkworkspace
1281 | DavMethod::Update
1282 | DavMethod::Label
1283 | DavMethod::Merge
1284 | DavMethod::BaselineControl
1285 | DavMethod::Mkactivity
1286 | DavMethod::Search
1287 | DavMethod::Orderpatch
1288 | DavMethod::Mkredirectref
1289 | DavMethod::Updateredirectref
1290 | DavMethod::Bind
1291 | DavMethod::Unbind
1292 | DavMethod::Rebind
1293 )
1294}
1295
1296fn validate_writes(declaration: &DavCapabilityDeclaration) -> Result<(), DavCapabilityPlanError> {
1297 let put_enabled = declaration.methods.contains(DavMethod::Put);
1298 if declaration.writes.partial_put != DavPartialPutCapability::Disabled && !put_enabled {
1299 return Err(DavCapabilityPlanError::PartialPutWithoutPut);
1300 }
1301 if declaration.writes.private_update_range != DavPrivateUpdateRangeCapability::Disabled
1302 && !put_enabled
1303 {
1304 return Err(DavCapabilityPlanError::PrivateUpdateRangeWithoutPut);
1305 }
1306 let patch_enabled = declaration.methods.contains(DavMethod::Patch);
1307 match declaration.writes.patch {
1308 DavPatchCapability::Disabled if patch_enabled => {
1309 Err(DavCapabilityPlanError::PatchWithoutFormats)
1310 }
1311 DavPatchCapability::Disabled => Ok(()),
1312 DavPatchCapability::Formats(_) if !patch_enabled => {
1313 Err(DavCapabilityPlanError::PatchFormatsWithoutMethod)
1314 }
1315 DavPatchCapability::Formats([]) => Err(DavCapabilityPlanError::PatchWithoutFormats),
1316 DavPatchCapability::Formats(formats) => validate_patch_formats(formats),
1317 }
1318}
1319
1320fn render_dav_header(
1321 declaration: &DavCapabilityDeclaration,
1322) -> Result<Option<HeaderValue>, DavCapabilityPlanError> {
1323 let mut rendered = String::new();
1324 if declaration.compliance.class1 {
1325 push_token(&mut rendered, "1");
1326 }
1327 if declaration.locking == DavLockingCapability::Class2 {
1328 push_token(&mut rendered, "2");
1329 }
1330 if declaration.compliance.class3 {
1331 push_token(&mut rendered, "3");
1332 }
1333 for package in declaration.extensions.iter() {
1334 if let Some(token) = package.descriptor().dav_token {
1335 push_token(&mut rendered, token);
1336 }
1337 }
1338 if rendered.is_empty() {
1339 Ok(None)
1340 } else {
1341 header_value(&rendered).map(Some)
1342 }
1343}
1344
1345fn render_dasl_header(
1346 declaration: &DavCapabilityDeclaration,
1347) -> Result<Option<HeaderValue>, DavCapabilityPlanError> {
1348 if declaration.search.grammars.is_empty() {
1349 return Ok(None);
1350 }
1351 let mut rendered = String::new();
1352 for grammar in declaration.search.grammars {
1353 if !rendered.is_empty() {
1354 rendered.push_str(", ");
1355 }
1356 rendered.push('<');
1357 rendered.push_str(grammar.coded_url);
1358 rendered.push('>');
1359 }
1360 header_value(&rendered).map(Some)
1361}
1362
1363fn validate_search_grammars(grammars: &[DavSearchGrammar]) -> Result<(), DavCapabilityPlanError> {
1364 for (index, grammar) in grammars.iter().enumerate() {
1365 if grammar.coded_url.is_empty()
1366 || grammar.coded_url.bytes().any(|byte| {
1367 byte <= b' ' || byte == b'<' || byte == b'>' || byte == b',' || byte == 0x7f
1368 })
1369 || parse_absolute_url(grammar.coded_url, "SEARCH grammar coded-URL").is_err()
1370 {
1371 return Err(DavCapabilityPlanError::InvalidSearchGrammarCodedUrl {
1372 index,
1373 coded_url: grammar.coded_url,
1374 });
1375 }
1376 if !is_valid_xml_local_name(grammar.xml_local_name) {
1377 return Err(DavCapabilityPlanError::InvalidSearchGrammarXmlLocalName {
1378 index,
1379 xml_local_name: grammar.xml_local_name,
1380 });
1381 }
1382 if !grammar.xml_namespace.is_empty()
1383 && (grammar.xml_namespace.trim() != grammar.xml_namespace
1384 || parse_absolute_url(grammar.xml_namespace, "SEARCH grammar namespace").is_err())
1385 {
1386 return Err(DavCapabilityPlanError::InvalidSearchGrammarXmlNamespace {
1387 index,
1388 xml_namespace: grammar.xml_namespace,
1389 });
1390 }
1391 if let Some(previous_index) = grammars[..index].iter().position(|previous| {
1392 previous.coded_url == grammar.coded_url
1393 || (previous.xml_namespace == grammar.xml_namespace
1394 && previous.xml_local_name == grammar.xml_local_name)
1395 }) {
1396 return Err(DavCapabilityPlanError::DuplicateSearchGrammar {
1397 index,
1398 previous_index,
1399 coded_url: grammar.coded_url,
1400 xml_namespace: grammar.xml_namespace,
1401 xml_local_name: grammar.xml_local_name,
1402 });
1403 }
1404 }
1405 Ok(())
1406}
1407
1408const LIVE_PROPERTY_SET_WORDS: usize = 2;
1409const LIVE_PROPERTY_SET_CAPACITY: usize = LIVE_PROPERTY_SET_WORDS * u64::BITS as usize;
1410const REPORT_SET_CAPACITY: usize = u16::BITS as usize;
1411
1412const _: () = assert!(DavLiveProperty::COUNT <= LIVE_PROPERTY_SET_CAPACITY);
1413const _: () = assert!(DavReportType::COUNT <= REPORT_SET_CAPACITY);
1414
1415#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1416struct DavLivePropertySet([u64; LIVE_PROPERTY_SET_WORDS]);
1417
1418impl DavLivePropertySet {
1419 const fn empty() -> Self {
1420 Self([0; LIVE_PROPERTY_SET_WORDS])
1421 }
1422
1423 fn insert(&mut self, property: DavLiveProperty) {
1424 let index = property.index();
1425 self.0[index / 64] |= 1u64 << (index % 64);
1426 }
1427
1428 const fn contains(self, property: DavLiveProperty) -> bool {
1429 let index = property.index();
1430 self.0[index / 64] & (1u64 << (index % 64)) != 0
1431 }
1432}
1433
1434#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1435struct DavReportSet(u16);
1436
1437impl DavReportSet {
1438 const fn empty() -> Self {
1439 Self(0)
1440 }
1441
1442 fn insert(&mut self, report: DavReportType) {
1443 self.0 |= 1u16 << report.index();
1444 }
1445
1446 const fn contains(self, report: DavReportType) -> bool {
1447 self.0 & (1u16 << report.index()) != 0
1448 }
1449}
1450
1451fn validate_patch_formats(formats: &[DavPatchFormat]) -> Result<(), DavCapabilityPlanError> {
1452 for (index, format) in formats.iter().enumerate() {
1453 let media_type = format
1454 .media_type
1455 .parse::<Mime>()
1456 .map_err(|_| DavCapabilityPlanError::InvalidPatchMediaType)?;
1457 for duplicate in &formats[..index] {
1458 let other = duplicate
1459 .media_type
1460 .parse::<Mime>()
1461 .map_err(|_| DavCapabilityPlanError::InvalidPatchMediaType)?;
1462 if media_type == other {
1463 return Err(DavCapabilityPlanError::DuplicatePatchMediaType);
1464 }
1465 }
1466 }
1467 Ok(())
1468}
1469
1470fn render_patch_formats(formats: &[DavPatchFormat]) -> String {
1471 let capacity = formats
1472 .iter()
1473 .map(|format| format.media_type.len())
1474 .sum::<usize>()
1475 + formats.len().saturating_sub(1) * 2;
1476 let mut rendered = String::with_capacity(capacity);
1477 for (index, format) in formats.iter().enumerate() {
1478 if index != 0 {
1479 rendered.push_str(", ");
1480 }
1481 rendered.push_str(format.media_type);
1482 }
1483 rendered
1484}
1485
1486fn header_value(value: &str) -> Result<HeaderValue, DavCapabilityPlanError> {
1487 HeaderValue::from_str(value).map_err(|_| DavCapabilityPlanError::InvalidHeaderRepresentation)
1488}
1489
1490fn push_token(rendered: &mut String, token: &str) {
1491 if !rendered.is_empty() {
1492 rendered.push_str(", ");
1493 }
1494 rendered.push_str(token);
1495}
1496
1497const fn extension_body_policy(kind: DavExtensionBodyKind, xml_limit: usize) -> DavBodyPolicy {
1498 match kind {
1499 DavExtensionBodyKind::Xml => DavBodyPolicy::BoundedXml { maximum: xml_limit },
1500 DavExtensionBodyKind::Stream => DavBodyPolicy::Stream,
1501 }
1502}