webdav_handler/
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 futures::{future, Future, Stream, TryFutureExt};
12use http::StatusCode;
13
14use crate::davpath::DavPath;
15
16macro_rules! notimplemented {
17    ($method:expr) => {
18        Err(FsError::NotImplemented)
19    };
20}
21
22macro_rules! notimplemented_fut {
23    ($method:expr) => {
24        Box::pin(future::ready(Err(FsError::NotImplemented)))
25    };
26}
27
28/// Errors generated by a filesystem implementation.
29///
30/// These are more result-codes than errors, really.
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub enum FsError {
33    /// Operation not implemented (501)
34    NotImplemented,
35    /// Something went wrong (500)
36    GeneralFailure,
37    /// tried to create something, but it existed (405 / 412) (yes, 405. RFC4918 says so)
38    Exists,
39    /// File / Directory not found (404)
40    NotFound,
41    /// Not allowed (403)
42    Forbidden,
43    /// Out of space (507)
44    InsufficientStorage,
45    /// Symbolic link loop detected (ELOOP) (508)
46    LoopDetected,
47    /// The path is too long (ENAMETOOLONG) (414)
48    PathTooLong,
49    /// The file being PUT is too large (413)
50    TooLarge,
51    /// Trying to MOVE over a mount boundary (EXDEV) (502)
52    IsRemote,
53}
54/// The Result type.
55pub type FsResult<T> = std::result::Result<T, FsError>;
56
57/// A webdav property.
58#[derive(Debug, Clone)]
59pub struct DavProp {
60    /// Name of the property.
61    pub name:      String,
62    /// XML prefix.
63    pub prefix:    Option<String>,
64    /// XML namespace.
65    pub namespace: Option<String>,
66    /// Value of the property as raw XML.
67    pub xml:       Option<Vec<u8>>,
68}
69
70/// Future returned by almost all of the DavFileSystem methods.
71pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
72/// Convenience alias for a boxed Stream.
73pub type FsStream<T> = Pin<Box<dyn Stream<Item = T> + Send>>;
74
75/// Used as argument to the read_dir() method.
76/// It is:
77///
78/// - an optimization hint (the implementation may call metadata() and
79///   store the result in the returned directory entry)
80/// - a way to get metadata instead of symlink_metadata from
81///   the directory entry.
82///
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum ReadDirMeta {
85    /// DavDirEntry.metadata() behaves as metadata()
86    Data,
87    /// DavDirEntry.metadata() behaves as symlink_metadata()
88    DataSymlink,
89    /// No optimizations, otherwise like DataSymlink.
90    None,
91}
92
93/// The trait that defines a filesystem.
94pub trait DavFileSystem: Sync + Send + BoxCloneFs {
95    /// Open a file.
96    fn open<'a>(&'a self, path: &'a DavPath, options: OpenOptions) -> FsFuture<Box<dyn DavFile>>;
97
98    /// Perform read_dir.
99    fn read_dir<'a>(
100        &'a self,
101        path: &'a DavPath,
102        meta: ReadDirMeta,
103    ) -> FsFuture<FsStream<Box<dyn DavDirEntry>>>;
104
105    /// Return the metadata of a file or directory.
106    fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<Box<dyn DavMetaData>>;
107
108    /// Return the metadata of a file, directory or symbolic link.
109    ///
110    /// Differs from metadata() that if the path is a symbolic link,
111    /// it return the metadata for the link itself, not for the thing
112    /// it points to.
113    ///
114    /// The default implementation returns FsError::NotImplemented.
115    #[allow(unused_variables)]
116    fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<Box<dyn DavMetaData>> {
117        self.metadata(path)
118    }
119
120    /// Create a directory.
121    ///
122    /// The default implementation returns FsError::NotImplemented.
123    #[allow(unused_variables)]
124    fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<()> {
125        notimplemented_fut!("create_dir")
126    }
127
128    /// Remove a directory.
129    ///
130    /// The default implementation returns FsError::NotImplemented.
131    #[allow(unused_variables)]
132    fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<()> {
133        notimplemented_fut!("remove_dir")
134    }
135
136    /// Remove a file.
137    ///
138    /// The default implementation returns FsError::NotImplemented.
139    #[allow(unused_variables)]
140    fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<()> {
141        notimplemented_fut!("remove_file")
142    }
143
144    /// Rename a file or directory.
145    ///
146    /// Source and destination must be the same type (file/dir).
147    /// If the destination already exists and is a file, it
148    /// should be replaced. If it is a directory it should give
149    /// an error.
150    ///
151    /// The default implementation returns FsError::NotImplemented.
152    #[allow(unused_variables)]
153    fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<()> {
154        notimplemented_fut!("rename")
155    }
156
157    /// Copy a file
158    ///
159    /// Should also copy the DAV properties, if properties
160    /// are implemented.
161    ///
162    /// The default implementation returns FsError::NotImplemented.
163    #[allow(unused_variables)]
164    fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<()> {
165        notimplemented_fut!("copy")
166    }
167
168    /// Set the access time of a file / directory.
169    ///
170    /// The default implementation returns FsError::NotImplemented.
171    #[doc(hidden)]
172    #[allow(unused_variables)]
173    fn set_accessed<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<()> {
174        notimplemented_fut!("set_accessed")
175    }
176
177    /// Set the modified time of a file / directory.
178    ///
179    /// The default implementation returns FsError::NotImplemented.
180    #[doc(hidden)]
181    #[allow(unused_variables)]
182    fn set_modified<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<()> {
183        notimplemented_fut!("set_mofified")
184    }
185
186    /// Indicator that tells if this filesystem driver supports DAV properties.
187    ///
188    /// The default implementation returns `false`.
189    #[allow(unused_variables)]
190    fn have_props<'a>(&'a self, path: &'a DavPath) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
191        Box::pin(future::ready(false))
192    }
193
194    /// Patch the DAV properties of a node (add/remove props)
195    ///
196    /// The default implementation returns FsError::NotImplemented.
197    #[allow(unused_variables)]
198    fn patch_props<'a>(
199        &'a self,
200        path: &'a DavPath,
201        patch: Vec<(bool, DavProp)>,
202    ) -> FsFuture<Vec<(StatusCode, DavProp)>>
203    {
204        notimplemented_fut!("patch_props")
205    }
206
207    /// List/get the DAV properties of a node.
208    ///
209    /// The default implementation returns FsError::NotImplemented.
210    #[allow(unused_variables)]
211    fn get_props<'a>(&'a self, path: &'a DavPath, do_content: bool) -> FsFuture<Vec<DavProp>> {
212        notimplemented_fut!("get_props")
213    }
214
215    /// Get one specific named property of a node.
216    ///
217    /// The default implementation returns FsError::NotImplemented.
218    #[allow(unused_variables)]
219    fn get_prop<'a>(&'a self, path: &'a DavPath, prop: DavProp) -> FsFuture<Vec<u8>> {
220        notimplemented_fut!("get_prop`")
221    }
222
223    /// Get quota of this filesystem (used/total space).
224    ///
225    /// The first value returned is the amount of space used,
226    /// the second optional value is the total amount of space
227    /// (used + available).
228    ///
229    /// The default implementation returns FsError::NotImplemented.
230    #[allow(unused_variables)]
231    fn get_quota<'a>(&'a self) -> FsFuture<(u64, Option<u64>)> {
232        notimplemented_fut!("get_quota`")
233    }
234}
235
236// BoxClone trait.
237#[doc(hidden)]
238pub trait BoxCloneFs {
239    fn box_clone(&self) -> Box<dyn DavFileSystem>;
240}
241
242// generic Clone, calls implementation-specific box_clone().
243impl Clone for Box<dyn DavFileSystem> {
244    fn clone(&self) -> Box<dyn DavFileSystem> {
245        self.box_clone()
246    }
247}
248
249// implementation-specific clone.
250#[doc(hidden)]
251impl<FS: Clone + DavFileSystem + 'static> BoxCloneFs for FS {
252    fn box_clone(&self) -> Box<dyn DavFileSystem> {
253        Box::new((*self).clone())
254    }
255}
256
257/// One directory entry (or child node).
258pub trait DavDirEntry: Send + Sync {
259    /// Name of the entry.
260    fn name(&self) -> Vec<u8>;
261
262    /// Metadata of the entry.
263    fn metadata<'a>(&'a self) -> FsFuture<Box<dyn DavMetaData>>;
264
265    /// Default implementation of `is_dir` just returns `metadata()?.is_dir()`.
266    /// Implementations can override this if their `metadata()` method is
267    /// expensive and there is a cheaper way to provide the same info
268    /// (e.g. dirent.d_type in unix filesystems).
269    fn is_dir<'a>(&'a self) -> FsFuture<bool> {
270        Box::pin(self.metadata().and_then(|meta| future::ok(meta.is_dir())))
271    }
272
273    /// Likewise. Default: `!is_dir()`.
274    fn is_file<'a>(&'a self) -> FsFuture<bool> {
275        Box::pin(self.metadata().and_then(|meta| future::ok(meta.is_file())))
276    }
277
278    /// Likewise. Default: `false`.
279    fn is_symlink<'a>(&'a self) -> FsFuture<bool> {
280        Box::pin(self.metadata().and_then(|meta| future::ok(meta.is_symlink())))
281    }
282}
283
284/// A `DavFile` is the equivalent of `std::fs::File`, should be
285/// readable/writeable/seekable, and be able to return its metadata.
286pub trait DavFile: Debug + Send + Sync {
287    fn metadata<'a>(&'a mut self) -> FsFuture<Box<dyn DavMetaData>>;
288    fn write_buf<'a>(&'a mut self, buf: Box<dyn bytes::Buf + Send>) -> FsFuture<()>;
289    fn write_bytes<'a>(&'a mut self, buf: bytes::Bytes) -> FsFuture<()>;
290    fn read_bytes<'a>(&'a mut self, count: usize) -> FsFuture<bytes::Bytes>;
291    fn seek<'a>(&'a mut self, pos: SeekFrom) -> FsFuture<u64>;
292    fn flush<'a>(&'a mut self) -> FsFuture<()>;
293}
294
295/// File metadata. Basically type, length, and some timestamps.
296pub trait DavMetaData: Debug + BoxCloneMd + Send + Sync {
297    /// Size of the file.
298    fn len(&self) -> u64;
299    /// `Modified` timestamp.
300    fn modified(&self) -> FsResult<SystemTime>;
301    /// File or directory (aka collection).
302    fn is_dir(&self) -> bool;
303
304    /// Simplistic implementation of `etag()`
305    ///
306    /// Returns a simple etag that basically is `\<length\>-\<timestamp_in_ms\>`
307    /// with the numbers in hex. Enough for most implementations.
308    fn etag(&self) -> Option<String> {
309        if let Ok(t) = self.modified() {
310            if let Ok(t) = t.duration_since(UNIX_EPOCH) {
311                let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
312                let tag = if self.is_file() && self.len() > 0 {
313                    format!("{:x}-{:x}", self.len(), t)
314                } else {
315                    format!("{:x}", t)
316                };
317                return Some(tag);
318            }
319        }
320        None
321    }
322
323    /// Is this a file and not a directory. Default: `!s_dir()`.
324    fn is_file(&self) -> bool {
325        !self.is_dir()
326    }
327
328    /// Is this a symbolic link. Default: false.
329    fn is_symlink(&self) -> bool {
330        false
331    }
332
333    /// Last access time. Default: `FsError::NotImplemented`.
334    fn accessed(&self) -> FsResult<SystemTime> {
335        notimplemented!("access time")
336    }
337
338    /// Creation time. Default: `FsError::NotImplemented`.
339    fn created(&self) -> FsResult<SystemTime> {
340        notimplemented!("creation time")
341    }
342
343    /// Inode change time (ctime). Default: `FsError::NotImplemented`.
344    fn status_changed(&self) -> FsResult<SystemTime> {
345        notimplemented!("status change time")
346    }
347
348    /// Is file executable (unix: has "x" mode bit). Default: `FsError::NotImplemented`.
349    fn executable(&self) -> FsResult<bool> {
350        notimplemented!("executable")
351    }
352}
353
354// generic Clone, calls implementation-specific box_clone().
355impl Clone for Box<dyn DavMetaData> {
356    fn clone(&self) -> Box<dyn DavMetaData> {
357        self.box_clone()
358    }
359}
360
361// BoxCloneMd trait.
362#[doc(hidden)]
363pub trait BoxCloneMd {
364    fn box_clone(&self) -> Box<dyn DavMetaData>;
365}
366
367// implementation-specific clone.
368#[doc(hidden)]
369impl<MD: Clone + DavMetaData + 'static> BoxCloneMd for MD {
370    fn box_clone(&self) -> Box<dyn DavMetaData> {
371        Box::new((*self).clone())
372    }
373}
374
375/// OpenOptions for `open()`.
376#[derive(Debug, Clone, Copy, Default)]
377pub struct OpenOptions {
378    /// open for reading
379    pub read:       bool,
380    /// open for writing
381    pub write:      bool,
382    /// open in write-append mode
383    pub append:     bool,
384    /// truncate file first when writing
385    pub truncate:   bool,
386    /// create file if it doesn't exist
387    pub create:     bool,
388    /// must create new file, fail if it already exists.
389    pub create_new: bool,
390}
391
392impl OpenOptions {
393    #[allow(dead_code)]
394    pub(crate) fn new() -> OpenOptions {
395        OpenOptions {
396            read:       false,
397            write:      false,
398            append:     false,
399            truncate:   false,
400            create:     false,
401            create_new: false,
402        }
403    }
404
405    pub(crate) fn read() -> OpenOptions {
406        OpenOptions {
407            read:       true,
408            write:      false,
409            append:     false,
410            truncate:   false,
411            create:     false,
412            create_new: false,
413        }
414    }
415
416    pub(crate) fn write() -> OpenOptions {
417        OpenOptions {
418            read:       false,
419            write:      true,
420            append:     false,
421            truncate:   false,
422            create:     false,
423            create_new: false,
424        }
425    }
426}
427
428impl std::error::Error for FsError {
429    fn description(&self) -> &str {
430        "DavFileSystem error"
431    }
432    fn cause(&self) -> Option<&dyn std::error::Error> {
433        None
434    }
435}
436
437impl std::fmt::Display for FsError {
438    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
439        write!(f, "{:?}", self)
440    }
441}