Skip to main content

dav_server/
fs.rs

1//! Contains the structs and traits that define a filesystem backend.
2//!
3//! You only need this if you are going to implement your own
4//! filesystem backend. Otherwise, just use 'LocalFs' or 'MemFs'.
5//!
6use std::fmt::Debug;
7use std::future::{self, Future};
8use std::io::SeekFrom;
9use std::pin::Pin;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12use dyn_clone::{DynClone, clone_trait_object};
13use futures_util::{FutureExt, Stream, TryFutureExt};
14use http::StatusCode;
15
16use crate::davpath::DavPath;
17
18macro_rules! notimplemented {
19    ($method:expr_2021) => {
20        Err(FsError::NotImplemented)
21    };
22}
23
24macro_rules! notimplemented_fut {
25    ($method:expr_2021) => {
26        Box::pin(future::ready(Err(FsError::NotImplemented)))
27    };
28}
29
30/// Errors generated by a filesystem implementation.
31///
32/// These are more result-codes than errors, really.
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum FsError {
35    /// Operation not implemented (501)
36    NotImplemented,
37    /// Something went wrong (500)
38    GeneralFailure,
39    /// tried to create something, but it existed (405 / 412) (yes, 405. RFC4918 says so)
40    Exists,
41    /// File / Directory not found (404)
42    NotFound,
43    /// Not allowed (403)
44    Forbidden,
45    /// Out of space (507)
46    InsufficientStorage,
47    /// Symbolic link loop detected (ELOOP) (508)
48    LoopDetected,
49    /// The path is too long (ENAMETOOLONG) (414)
50    PathTooLong,
51    /// The file being PUT is too large (413)
52    TooLarge,
53    /// Trying to MOVE over a mount boundary (EXDEV) (502)
54    IsRemote,
55}
56/// The Result type.
57pub type FsResult<T> = std::result::Result<T, FsError>;
58
59#[cfg(any(feature = "memfs", feature = "localfs"))]
60impl From<&std::io::Error> for FsError {
61    fn from(e: &std::io::Error) -> Self {
62        use std::io::ErrorKind;
63
64        if let Some(errno) = e.raw_os_error() {
65            // specific errors.
66            match errno {
67                #[cfg(unix)]
68                libc::EMLINK | libc::ENOSPC | libc::EDQUOT => return FsError::InsufficientStorage,
69                #[cfg(windows)]
70                libc::EMLINK | libc::ENOSPC => return FsError::InsufficientStorage,
71                libc::EFBIG => return FsError::TooLarge,
72                libc::EACCES | libc::EPERM => return FsError::Forbidden,
73                libc::ENOTEMPTY | libc::EEXIST => return FsError::Exists,
74                libc::ELOOP => return FsError::LoopDetected,
75                libc::ENAMETOOLONG => return FsError::PathTooLong,
76                libc::ENOTDIR => return FsError::Forbidden,
77                libc::EISDIR => return FsError::Forbidden,
78                libc::EROFS => return FsError::Forbidden,
79                libc::ENOENT => return FsError::NotFound,
80                libc::ENOSYS => return FsError::NotImplemented,
81                libc::EXDEV => return FsError::IsRemote,
82                _ => {}
83            }
84        } else {
85            // not an OS error - must be "not implemented"
86            // (e.g. metadata().created() on systems without st_crtime)
87            return FsError::NotImplemented;
88        }
89        // generic mappings for-whatever is left.
90        match e.kind() {
91            ErrorKind::NotFound => FsError::NotFound,
92            ErrorKind::PermissionDenied => FsError::Forbidden,
93            _ => FsError::GeneralFailure,
94        }
95    }
96}
97
98#[cfg(any(feature = "memfs", feature = "localfs"))]
99impl From<std::io::Error> for FsError {
100    fn from(e: std::io::Error) -> Self {
101        (&e).into()
102    }
103}
104/// A webdav property.
105#[derive(Debug, Clone)]
106pub struct DavProp {
107    /// Name of the property.
108    pub name: String,
109    /// XML prefix.
110    pub prefix: Option<String>,
111    /// XML namespace.
112    pub namespace: Option<String>,
113    /// Value of the property as raw XML. Use DavProp::new() to create your custom props
114    pub xml: Option<Vec<u8>>,
115}
116
117impl DavProp {
118    /// Create XML property with name, prefix, namespace and value
119    pub fn new(name: String, prefix: String, namespace: String, value: String) -> DavProp {
120        DavProp {
121            name: name.clone(),
122            prefix: Some(prefix.clone()),
123            namespace: Some(namespace.clone()),
124            xml: Some(
125                format!(
126                    "<{prefix}:{name} xmlns:{prefix}=\"{namespace}\">{value}</{prefix}:{name}>"
127                )
128                .into_bytes(),
129            ),
130        }
131    }
132}
133
134/// Future returned by almost all of the DavFileSystem methods.
135pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
136/// Convenience alias for a boxed Stream.
137pub type FsStream<T> = Pin<Box<dyn Stream<Item = FsResult<T>> + Send>>;
138
139/// Used as argument to the read_dir() method.
140/// It is:
141///
142/// - an optimization hint (the implementation may call metadata() and
143///   store the result in the returned directory entry)
144/// - a way to get metadata instead of symlink_metadata from
145///   the directory entry.
146///
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum ReadDirMeta {
149    /// DavDirEntry.metadata() behaves as metadata()
150    Data,
151    /// DavDirEntry.metadata() behaves as symlink_metadata()
152    DataSymlink,
153    /// No optimizations, otherwise like DataSymlink.
154    None,
155}
156
157/// File system without access control.
158pub trait DavFileSystem {
159    /// Open a file.
160    fn open<'a>(
161        &'a self,
162        path: &'a DavPath,
163        options: OpenOptions,
164    ) -> FsFuture<'a, Box<dyn DavFile>>;
165
166    /// Lists entries within a directory.
167    fn read_dir<'a>(
168        &'a self,
169        path: &'a DavPath,
170        meta: ReadDirMeta,
171    ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
172
173    /// Return the metadata of a file or directory.
174    fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>>;
175
176    /// Return the metadata of a file, directory or symbolic link.
177    ///
178    /// Differs from [`metadata`][Self::metadata] that if the path is a symbolic link,
179    /// it return the metadata for the link itself, not for the thing
180    /// it points to.
181    ///
182    /// The default implementation returns [`FsError::NotImplemented`].
183    #[allow(unused_variables)]
184    fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
185        self.metadata(path)
186    }
187
188    /// Create a directory.
189    ///
190    /// The default implementation returns [`FsError::NotImplemented`].
191    #[allow(unused_variables)]
192    fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
193        notimplemented_fut!("create_dir")
194    }
195
196    /// Remove a directory.
197    ///
198    /// The default implementation returns [`FsError::NotImplemented`].
199    #[allow(unused_variables)]
200    fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
201        notimplemented_fut!("remove_dir")
202    }
203
204    /// Remove a file.
205    ///
206    /// The default implementation returns [`FsError::NotImplemented`].
207    #[allow(unused_variables)]
208    fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
209        notimplemented_fut!("remove_file")
210    }
211
212    /// Rename a file or directory.
213    ///
214    /// Source and destination must be the same type (file/dir).
215    /// If the destination already exists and is a file, it
216    /// should be replaced. If it is a directory it should give
217    /// an error.
218    ///
219    /// The default implementation returns [`FsError::NotImplemented`].
220    #[allow(unused_variables)]
221    fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
222        notimplemented_fut!("rename")
223    }
224
225    /// Copy a file.
226    ///
227    /// Should also copy the DAV properties, if properties
228    /// are implemented.
229    ///
230    /// The default implementation returns [`FsError::NotImplemented`].
231    #[allow(unused_variables)]
232    fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
233        notimplemented_fut!("copy")
234    }
235
236    /// Set the access time of a file / directory.
237    ///
238    /// The default implementation returns [`FsError::NotImplemented`].
239    #[doc(hidden)]
240    #[allow(unused_variables)]
241    fn set_accessed<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
242        notimplemented_fut!("set_accessed")
243    }
244
245    /// Set the modified time of a file / directory.
246    ///
247    /// The default implementation returns [`FsError::NotImplemented`].
248    #[doc(hidden)]
249    #[allow(unused_variables)]
250    fn set_modified<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
251        notimplemented_fut!("set_modified")
252    }
253
254    /// Indicator that tells if this filesystem driver supports DAV properties.
255    ///
256    /// The default implementation returns `false`.
257    #[allow(unused_variables)]
258    fn have_props<'a>(
259        &'a self,
260        path: &'a DavPath,
261    ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
262        Box::pin(future::ready(false))
263    }
264
265    /// Patch the DAV properties of a node (add/remove props).
266    ///
267    /// The default implementation returns [`FsError::NotImplemented`].
268    #[allow(unused_variables)]
269    fn patch_props<'a>(
270        &'a self,
271        path: &'a DavPath,
272        patch: Vec<(bool, DavProp)>,
273    ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
274        notimplemented_fut!("patch_props")
275    }
276
277    /// List/get the DAV properties of a node.
278    ///
279    /// The default implementation returns [`FsError::NotImplemented`].
280    #[allow(unused_variables)]
281    fn get_props<'a>(&'a self, path: &'a DavPath, do_content: bool) -> FsFuture<'a, Vec<DavProp>> {
282        notimplemented_fut!("get_props")
283    }
284
285    /// Get one specific named property of a node.
286    ///
287    /// The default implementation returns [`FsError::NotImplemented`].
288    #[allow(unused_variables)]
289    fn get_prop<'a>(&'a self, path: &'a DavPath, prop: DavProp) -> FsFuture<'a, Vec<u8>> {
290        notimplemented_fut!("get_prop")
291    }
292
293    /// Get quota of this filesystem (used/total space).
294    ///
295    /// The first value returned is the amount of space used,
296    /// the second optional value is the total amount of space
297    /// (used + available).
298    ///
299    /// The default implementation returns [`FsError::NotImplemented`].
300    #[allow(unused_variables)]
301    fn get_quota(&'_ self) -> FsFuture<'_, (u64, Option<u64>)> {
302        notimplemented_fut!("get_quota")
303    }
304}
305
306/// File system with access control. Type parameter `C` (credentials) represents
307/// the authentication and authorization information required for accessing the file system.
308/// This can include various forms of credentials such as:
309///
310/// - auth tokens,
311/// - login and password hash combinations,
312/// - encryption keys.
313///
314/// Additionally, it may encapsulate context, scope or metadata related to the request,
315/// such as:
316///
317/// - a set of file system permissions granted to a user,
318/// - regionally-defined access scope.
319///
320/// All [HTTP security considerations][HTTP security] also [apply][WebDAV security]
321/// to WebDAV, so you should follow the same authorization best practices as you would
322/// for regular HTTP server.
323///
324/// If credentials `C` include a username, you may want
325/// to [specify it as principal][crate::DavConfig::principal]
326/// to the [`DavHandler`][crate::DavHandler] builder so that the locks user requests
327/// have a known owner.
328///
329/// For file systems without access control, implement simpler [`DavFileSystem`] trait,
330/// for which there's a blanket implementation of `GuardedFileSystem<()>`.
331///
332/// See also the [auth example].
333///
334/// [HTTP security]: https://datatracker.ietf.org/doc/html/rfc2616#section-15
335/// [WebDAV security]: https://datatracker.ietf.org/doc/html/rfc4918#section-20
336/// [auth example]: https://github.com/messense/dav-server-rs/blob/main/examples/auth.rs
337pub trait GuardedFileSystem<C>: Send + Sync + DynClone
338where
339    C: Clone + Send + Sync + 'static,
340{
341    /// Open a file.
342    fn open<'a>(
343        &'a self,
344        path: &'a DavPath,
345        options: OpenOptions,
346        credentials: &'a C,
347    ) -> FsFuture<'a, Box<dyn DavFile>>;
348
349    /// Lists entries within a directory.
350    fn read_dir<'a>(
351        &'a self,
352        path: &'a DavPath,
353        meta: ReadDirMeta,
354        credentials: &'a C,
355    ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
356
357    /// Return the metadata of a file or directory.
358    fn metadata<'a>(
359        &'a self,
360        path: &'a DavPath,
361        credentials: &'a C,
362    ) -> FsFuture<'a, Box<dyn DavMetaData>>;
363
364    /// Return the metadata of a file, directory or symbolic link.
365    ///
366    /// Differs from [`metadata`][Self::metadata] that if the path is a symbolic link,
367    /// it return the metadata for the link itself, not for the thing
368    /// it points to.
369    ///
370    /// The default implementation returns [`FsError::NotImplemented`].
371    #[allow(unused_variables)]
372    fn symlink_metadata<'a>(
373        &'a self,
374        path: &'a DavPath,
375        credentials: &'a C,
376    ) -> FsFuture<'a, Box<dyn DavMetaData>> {
377        self.metadata(path, credentials)
378    }
379
380    /// Create a directory.
381    ///
382    /// The default implementation returns [`FsError::NotImplemented`].
383    #[allow(unused_variables)]
384    fn create_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
385        notimplemented_fut!("create_dir")
386    }
387
388    /// Remove a directory.
389    ///
390    /// The default implementation returns [`FsError::NotImplemented`].
391    #[allow(unused_variables)]
392    fn remove_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
393        notimplemented_fut!("remove_dir")
394    }
395
396    /// Remove a file.
397    ///
398    /// The default implementation returns [`FsError::NotImplemented`].
399    #[allow(unused_variables)]
400    fn remove_file<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
401        notimplemented_fut!("remove_file")
402    }
403
404    /// Rename a file or directory.
405    ///
406    /// Source and destination must be the same type (file/dir).
407    /// If the destination already exists and is a file, it
408    /// should be replaced. If it is a directory it should give
409    /// an error.
410    ///
411    /// The default implementation returns [`FsError::NotImplemented`].
412    #[allow(unused_variables)]
413    fn rename<'a>(
414        &'a self,
415        from: &'a DavPath,
416        to: &'a DavPath,
417        credentials: &'a C,
418    ) -> FsFuture<'a, ()> {
419        notimplemented_fut!("rename")
420    }
421
422    /// Copy a file.
423    ///
424    /// Should also copy the DAV properties, if properties
425    /// are implemented.
426    ///
427    /// The default implementation returns [`FsError::NotImplemented`].
428    #[allow(unused_variables)]
429    fn copy<'a>(
430        &'a self,
431        from: &'a DavPath,
432        to: &'a DavPath,
433        credentials: &'a C,
434    ) -> FsFuture<'a, ()> {
435        notimplemented_fut!("copy")
436    }
437
438    /// Set the access time of a file / directory.
439    ///
440    /// The default implementation returns [`FsError::NotImplemented`].
441    #[doc(hidden)]
442    #[allow(unused_variables)]
443    fn set_accessed<'a>(
444        &'a self,
445        path: &'a DavPath,
446        tm: SystemTime,
447        credentials: &C,
448    ) -> FsFuture<'a, ()> {
449        notimplemented_fut!("set_accessed")
450    }
451
452    /// Set the modified time of a file / directory.
453    ///
454    /// The default implementation returns [`FsError::NotImplemented`].
455    #[doc(hidden)]
456    #[allow(unused_variables)]
457    fn set_modified<'a>(
458        &'a self,
459        path: &'a DavPath,
460        tm: SystemTime,
461        credentials: &'a C,
462    ) -> FsFuture<'a, ()> {
463        notimplemented_fut!("set_mofified")
464    }
465
466    /// Indicator that tells if this filesystem driver supports DAV properties.
467    ///
468    /// The default implementation returns `false`.
469    #[allow(unused_variables)]
470    fn have_props<'a>(
471        &'a self,
472        path: &'a DavPath,
473        credentials: &'a C,
474    ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
475        Box::pin(future::ready(false))
476    }
477
478    /// Patch the DAV properties of a node (add/remove props).
479    ///
480    /// The default implementation returns [`FsError::NotImplemented`].
481    #[allow(unused_variables)]
482    fn patch_props<'a>(
483        &'a self,
484        path: &'a DavPath,
485        patch: Vec<(bool, DavProp)>,
486        credentials: &'a C,
487    ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
488        notimplemented_fut!("patch_props")
489    }
490
491    /// List/get the DAV properties of a node.
492    ///
493    /// The default implementation returns [`FsError::NotImplemented`].
494    #[allow(unused_variables)]
495    fn get_props<'a>(
496        &'a self,
497        path: &'a DavPath,
498        do_content: bool,
499        credentials: &'a C,
500    ) -> FsFuture<'a, Vec<DavProp>> {
501        notimplemented_fut!("get_props")
502    }
503
504    /// Get one specific named property of a node.
505    ///
506    /// The default implementation returns [`FsError::NotImplemented`].
507    #[allow(unused_variables)]
508    fn get_prop<'a>(
509        &'a self,
510        path: &'a DavPath,
511        prop: DavProp,
512        credentials: &'a C,
513    ) -> FsFuture<'a, Vec<u8>> {
514        notimplemented_fut!("get_prop")
515    }
516
517    /// Get quota of this filesystem (used/total space).
518    ///
519    /// The first value returned is the amount of space used,
520    /// the second optional value is the total amount of space
521    /// (used + available).
522    ///
523    /// The default implementation returns [`FsError::NotImplemented`].
524    #[allow(unused_variables)]
525    fn get_quota<'a>(&'a self, credentials: &'a C) -> FsFuture<'a, (u64, Option<u64>)> {
526        notimplemented_fut!("get_quota")
527    }
528}
529
530clone_trait_object! {<C> GuardedFileSystem<C>}
531
532impl<Fs: DavFileSystem + Clone + Send + Sync> GuardedFileSystem<()> for Fs {
533    fn open<'a>(
534        &'a self,
535        path: &'a DavPath,
536        options: OpenOptions,
537        _credentials: &(),
538    ) -> FsFuture<'a, Box<dyn DavFile>> {
539        DavFileSystem::open(self, path, options)
540    }
541
542    fn read_dir<'a>(
543        &'a self,
544        path: &'a DavPath,
545        meta: ReadDirMeta,
546        _credentials: &(),
547    ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
548        DavFileSystem::read_dir(self, path, meta)
549    }
550
551    fn metadata<'a>(
552        &'a self,
553        path: &'a DavPath,
554        _credentials: &(),
555    ) -> FsFuture<'a, Box<dyn DavMetaData>> {
556        DavFileSystem::metadata(self, path)
557    }
558
559    fn symlink_metadata<'a>(
560        &'a self,
561        path: &'a DavPath,
562        _credentials: &(),
563    ) -> FsFuture<'a, Box<dyn DavMetaData>> {
564        DavFileSystem::symlink_metadata(self, path)
565    }
566
567    fn create_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
568        DavFileSystem::create_dir(self, path)
569    }
570
571    fn remove_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
572        DavFileSystem::remove_dir(self, path)
573    }
574
575    fn remove_file<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
576        DavFileSystem::remove_file(self, path)
577    }
578
579    fn rename<'a>(
580        &'a self,
581        from: &'a DavPath,
582        to: &'a DavPath,
583        _credentials: &(),
584    ) -> FsFuture<'a, ()> {
585        DavFileSystem::rename(self, from, to)
586    }
587
588    fn copy<'a>(
589        &'a self,
590        from: &'a DavPath,
591        to: &'a DavPath,
592        _credentials: &(),
593    ) -> FsFuture<'a, ()> {
594        DavFileSystem::copy(self, from, to)
595    }
596
597    fn set_accessed<'a>(
598        &'a self,
599        path: &'a DavPath,
600        tm: SystemTime,
601        _credentials: &(),
602    ) -> FsFuture<'a, ()> {
603        DavFileSystem::set_accessed(self, path, tm)
604    }
605
606    fn set_modified<'a>(
607        &'a self,
608        path: &'a DavPath,
609        tm: SystemTime,
610        _credentials: &(),
611    ) -> FsFuture<'a, ()> {
612        DavFileSystem::set_modified(self, path, tm)
613    }
614
615    fn have_props<'a>(
616        &'a self,
617        path: &'a DavPath,
618        _credentials: &(),
619    ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
620        DavFileSystem::have_props(self, path)
621    }
622
623    fn patch_props<'a>(
624        &'a self,
625        path: &'a DavPath,
626        patch: Vec<(bool, DavProp)>,
627        _credentials: &(),
628    ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
629        DavFileSystem::patch_props(self, path, patch)
630    }
631
632    fn get_props<'a>(
633        &'a self,
634        path: &'a DavPath,
635        do_content: bool,
636        _credentials: &(),
637    ) -> FsFuture<'a, Vec<DavProp>> {
638        DavFileSystem::get_props(self, path, do_content)
639    }
640
641    fn get_prop<'a>(
642        &'a self,
643        path: &'a DavPath,
644        prop: DavProp,
645        _credentials: &(),
646    ) -> FsFuture<'a, Vec<u8>> {
647        DavFileSystem::get_prop(self, path, prop)
648    }
649
650    fn get_quota(&'_ self, _credentials: &()) -> FsFuture<'_, (u64, Option<u64>)> {
651        DavFileSystem::get_quota(self)
652    }
653}
654
655/// One directory entry (or child node).
656pub trait DavDirEntry: Send + Sync {
657    /// Name of the entry.
658    fn name(&self) -> Vec<u8>;
659
660    /// Metadata of the entry.
661    fn metadata(&'_ self) -> FsFuture<'_, Box<dyn DavMetaData>>;
662
663    /// Default implementation of `is_dir` just returns `metadata()?.is_dir()`.
664    /// Implementations can override this if their `metadata()` method is
665    /// expensive and there is a cheaper way to provide the same info
666    /// (e.g. dirent.d_type in unix filesystems).
667    fn is_dir(&'_ self) -> FsFuture<'_, bool> {
668        Box::pin(
669            self.metadata()
670                .and_then(|meta| future::ready(Ok(meta.is_dir()))),
671        )
672    }
673
674    /// Likewise. Default: `!is_dir()`.
675    fn is_file(&'_ self) -> FsFuture<'_, bool> {
676        Box::pin(
677            self.metadata()
678                .and_then(|meta| future::ready(Ok(meta.is_file()))),
679        )
680    }
681
682    /// Likewise. Default: `false`.
683    fn is_symlink(&'_ self) -> FsFuture<'_, bool> {
684        Box::pin(
685            self.metadata()
686                .and_then(|meta| future::ready(Ok(meta.is_symlink()))),
687        )
688    }
689}
690
691/// A `DavFile` is the equivalent of `std::fs::File`, should be
692/// readable/writeable/seekable, and be able to return its metadata.
693pub trait DavFile: Debug + Send + Sync {
694    fn metadata(&'_ mut self) -> FsFuture<'_, Box<dyn DavMetaData>>;
695    fn write_buf(&'_ mut self, buf: Box<dyn bytes::Buf + Send>) -> FsFuture<'_, ()>;
696    fn write_bytes(&'_ mut self, buf: bytes::Bytes) -> FsFuture<'_, ()>;
697    fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, bytes::Bytes>;
698    fn seek(&'_ mut self, pos: SeekFrom) -> FsFuture<'_, u64>;
699    fn flush(&'_ mut self) -> FsFuture<'_, ()>;
700    fn redirect_url(&'_ mut self) -> FsFuture<'_, Option<String>> {
701        future::ready(Ok(None)).boxed()
702    }
703}
704
705/// File metadata. Basically type, length, and some timestamps.
706pub trait DavMetaData: Debug + Send + Sync + DynClone {
707    /// Size of the file.
708    fn len(&self) -> u64;
709    /// `Modified` timestamp.
710    fn modified(&self) -> FsResult<SystemTime>;
711    /// File or directory (aka collection).
712    fn is_dir(&self) -> bool;
713    /// Is Calendar collection?
714    #[cfg(feature = "caldav")]
715    fn is_calendar(&self, path: &DavPath) -> bool;
716    /// Is Address Book collection?
717    #[cfg(feature = "carddav")]
718    fn is_addressbook(&self, path: &DavPath) -> bool;
719
720    /// Simplistic implementation of `etag()`
721    ///
722    /// Returns a simple etag that basically is `\<length\>-\<timestamp_in_ms\>`
723    /// with the numbers in hex. Enough for most implementations.
724    fn etag(&self) -> Option<String> {
725        if let Ok(t) = self.modified()
726            && let Ok(t) = t.duration_since(UNIX_EPOCH)
727        {
728            let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
729            let tag = if self.is_file() && self.len() > 0 {
730                format!("{:x}-{:x}", self.len(), t)
731            } else {
732                format!("{t:x}")
733            };
734            return Some(tag);
735        }
736        None
737    }
738
739    /// Is this a file and not a directory. Default: `!is_dir()`.
740    fn is_file(&self) -> bool {
741        !self.is_dir()
742    }
743
744    /// Is this a symbolic link. Default: false.
745    fn is_symlink(&self) -> bool {
746        false
747    }
748
749    /// Last access time. Default: `FsError::NotImplemented`.
750    fn accessed(&self) -> FsResult<SystemTime> {
751        notimplemented!("access time")
752    }
753
754    /// Creation time. Default: `FsError::NotImplemented`.
755    fn created(&self) -> FsResult<SystemTime> {
756        notimplemented!("creation time")
757    }
758
759    /// Inode change time (ctime). Default: `FsError::NotImplemented`.
760    fn status_changed(&self) -> FsResult<SystemTime> {
761        notimplemented!("status change time")
762    }
763
764    /// Is file executable (unix: has "x" mode bit). Default: `FsError::NotImplemented`.
765    fn executable(&self) -> FsResult<bool> {
766        notimplemented!("executable")
767    }
768
769    // Is empty file
770    fn is_empty(&self) -> bool {
771        self.len() == 0
772    }
773}
774
775clone_trait_object! {DavMetaData}
776
777/// OpenOptions for `open()`.
778#[derive(Debug, Clone, Default)]
779pub struct OpenOptions {
780    /// open for reading
781    pub read: bool,
782    /// open for writing
783    pub write: bool,
784    /// open in write-append mode
785    pub append: bool,
786    /// truncate file first when writing
787    pub truncate: bool,
788    /// create file if it doesn't exist
789    pub create: bool,
790    /// must create new file, fail if it already exists.
791    pub create_new: bool,
792    /// write file total size
793    pub size: Option<u64>,
794    /// checksum, owncloud extension
795    pub checksum: Option<String>,
796}
797
798impl OpenOptions {
799    #[allow(dead_code)]
800    pub(crate) fn new() -> OpenOptions {
801        OpenOptions {
802            read: false,
803            write: false,
804            append: false,
805            truncate: false,
806            create: false,
807            create_new: false,
808            size: None,
809            checksum: None,
810        }
811    }
812
813    pub(crate) fn read() -> OpenOptions {
814        OpenOptions {
815            read: true,
816            write: false,
817            append: false,
818            truncate: false,
819            create: false,
820            create_new: false,
821            size: None,
822            checksum: None,
823        }
824    }
825
826    pub(crate) fn write() -> OpenOptions {
827        OpenOptions {
828            read: false,
829            write: true,
830            append: false,
831            truncate: false,
832            create: false,
833            create_new: false,
834            size: None,
835            checksum: None,
836        }
837    }
838}
839
840impl std::error::Error for FsError {
841    fn description(&self) -> &str {
842        "DavFileSystem error"
843    }
844    fn cause(&self) -> Option<&dyn std::error::Error> {
845        None
846    }
847}
848
849impl std::fmt::Display for FsError {
850    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
851        write!(f, "{self:?}")
852    }
853}