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