Skip to main content

rustic_rs/commands/webdav/
webdavfs.rs

1#[cfg(not(windows))]
2use std::os::unix::ffi::OsStrExt;
3use std::{
4    fmt::{Debug, Formatter},
5    io::SeekFrom,
6    sync::{Arc, OnceLock},
7    time::SystemTime,
8};
9
10use bytes::{Buf, Bytes};
11use dav_server::{
12    davpath::DavPath,
13    fs::{
14        DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsResult, FsStream,
15        OpenOptions, ReadDirMeta,
16    },
17};
18use futures::FutureExt;
19use rustic_core::{
20    repofile::Node,
21    vfs::{FilePolicy, OpenFile, Vfs},
22};
23use tokio::task::spawn_blocking;
24
25use crate::repository::IndexedRepo;
26
27fn now() -> SystemTime {
28    static NOW: OnceLock<SystemTime> = OnceLock::new();
29    *NOW.get_or_init(SystemTime::now)
30}
31
32/// The inner state of a [`WebDavFS`] instance.
33struct DavFsInner {
34    /// The [`Repository`] to use
35    repo: IndexedRepo,
36
37    /// The [`Vfs`] to use
38    vfs: Vfs,
39
40    /// The [`FilePolicy`] to use
41    file_policy: FilePolicy,
42}
43
44impl Debug for DavFsInner {
45    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
46        write!(f, "DavFS")
47    }
48}
49
50/// DAV Filesystem implementation.
51///
52/// This is the main entry point for the DAV filesystem.
53/// It implements [`DavFileSystem`] and can be used to serve a [`Repository`] via DAV.
54#[derive(Debug)]
55pub struct WebDavFS {
56    inner: Arc<DavFsInner>,
57}
58
59impl WebDavFS {
60    /// Create a new [`WebDavFS`] instance.
61    ///
62    /// # Arguments
63    ///
64    /// * `repo` - The [`Repository`] to use
65    /// * `vfs` - The [`Vfs`] to use
66    /// * `file_policy` - The [`FilePolicy`] to use
67    ///
68    /// # Returns
69    ///
70    /// A new [`WebDavFS`] instance
71    pub(crate) fn new(repo: IndexedRepo, vfs: Vfs, file_policy: FilePolicy) -> Self {
72        let inner = DavFsInner {
73            repo,
74            vfs,
75            file_policy,
76        };
77
78        Self {
79            inner: Arc::new(inner),
80        }
81    }
82
83    /// Get a [`Node`] from the specified [`DavPath`].
84    ///
85    /// # Arguments
86    ///
87    /// * `path` - The path to get the [`Tree`] at
88    ///
89    /// # Errors
90    ///
91    /// * If the [`Tree`] could not be found
92    ///
93    /// # Returns
94    ///
95    /// The [`Node`] at the specified path
96    ///
97    /// [`Tree`]: crate::repofile::Tree
98    async fn node_from_path(&self, path: &DavPath) -> Result<Node, FsError> {
99        let inner = self.inner.clone();
100        let path = path.as_pathbuf();
101        spawn_blocking(move || {
102            inner
103                .vfs
104                .node_from_path(&inner.repo, &path)
105                .map_err(|_| FsError::GeneralFailure)
106        })
107        .await
108        .map_err(|_| FsError::GeneralFailure)?
109    }
110
111    /// Get a list of [`Node`]s from the specified directory path.
112    ///
113    /// # Arguments
114    ///
115    /// * `path` - The path to get the [`Tree`] at
116    ///
117    /// # Errors
118    ///
119    /// * If the [`Tree`] could not be found
120    ///
121    /// # Returns
122    ///
123    /// The list of [`Node`]s at the specified path
124    ///
125    /// [`Tree`]: crate::repofile::Tree
126    async fn dir_entries_from_path(&self, path: &DavPath) -> Result<Vec<Node>, FsError> {
127        let inner = self.inner.clone();
128        let path = path.as_pathbuf();
129        spawn_blocking(move || {
130            inner
131                .vfs
132                .dir_entries_from_path(&inner.repo, &path)
133                .map_err(|_| FsError::GeneralFailure)
134        })
135        .await
136        .map_err(|_| FsError::GeneralFailure)?
137    }
138}
139
140impl Clone for WebDavFS {
141    fn clone(&self) -> Self {
142        Self {
143            inner: self.inner.clone(),
144        }
145    }
146}
147
148impl DavFileSystem for WebDavFS {
149    fn metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> {
150        self.symlink_metadata(davpath)
151    }
152
153    fn symlink_metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> {
154        async move {
155            let node = self.node_from_path(davpath).await?;
156            let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(node));
157            Ok(meta)
158        }
159        .boxed()
160    }
161
162    fn read_dir<'a>(
163        &'a self,
164        davpath: &'a DavPath,
165        _meta: ReadDirMeta,
166    ) -> FsFuture<'_, FsStream<Box<dyn DavDirEntry>>> {
167        async move {
168            let entries = self.dir_entries_from_path(davpath).await?;
169            let entry_iter = entries.into_iter().map(|e| {
170                let entry: Box<dyn DavDirEntry> = Box::new(DavFsDirEntry(e));
171                Ok(entry)
172            });
173            let strm: FsStream<Box<dyn DavDirEntry>> = Box::pin(futures::stream::iter(entry_iter));
174            Ok(strm)
175        }
176        .boxed()
177    }
178
179    fn open<'a>(
180        &'a self,
181        path: &'a DavPath,
182        options: OpenOptions,
183    ) -> FsFuture<'_, Box<dyn DavFile>> {
184        async move {
185            if options.write
186                || options.append
187                || options.truncate
188                || options.create
189                || options.create_new
190            {
191                return Err(FsError::Forbidden);
192            }
193
194            let node = self.node_from_path(path).await?;
195            if matches!(self.inner.file_policy, FilePolicy::Forbidden) {
196                return Err(FsError::Forbidden);
197            }
198
199            let inner = self.inner.clone();
200            let node_copy = node.clone();
201            let open = spawn_blocking(move || {
202                inner
203                    .repo
204                    .open_file(&node_copy)
205                    .map_err(|_err| FsError::GeneralFailure)
206            })
207            .await
208            .map_err(|_| FsError::GeneralFailure)??;
209
210            let file: Box<dyn DavFile> = Box::new(DavFsFile {
211                node,
212                open: Arc::new(open),
213                fs: self.inner.clone(),
214                seek: 0,
215            });
216            Ok(file)
217        }
218        .boxed()
219    }
220}
221
222/// A [`DavDirEntry`] implementation for [`Node`]s.
223#[derive(Clone, Debug)]
224struct DavFsDirEntry(Node);
225
226impl DavDirEntry for DavFsDirEntry {
227    fn metadata(&self) -> FsFuture<'_, Box<dyn DavMetaData>> {
228        async move {
229            let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.0.clone()));
230            Ok(meta)
231        }
232        .boxed()
233    }
234
235    #[cfg(not(windows))]
236    fn name(&self) -> Vec<u8> {
237        self.0.name().as_bytes().to_vec()
238    }
239
240    #[cfg(windows)]
241    fn name(&self) -> Vec<u8> {
242        self.0
243            .name()
244            .as_os_str()
245            .to_string_lossy()
246            .to_string()
247            .into_bytes()
248    }
249}
250
251/// A [`DavFile`] implementation for [`Node`]s.
252///
253/// This is a read-only file.
254struct DavFsFile {
255    /// The [`Node`] this file is for
256    node: Node,
257
258    /// The [`OpenFile`] for this file
259    open: Arc<OpenFile>,
260
261    /// The [`DavFsInner`] this file belongs to
262    fs: Arc<DavFsInner>,
263
264    /// The current seek position
265    seek: usize,
266}
267
268impl Debug for DavFsFile {
269    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
270        write!(f, "DavFile")
271    }
272}
273
274impl DavFile for DavFsFile {
275    fn metadata(&mut self) -> FsFuture<'_, Box<dyn DavMetaData>> {
276        async move {
277            let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.node.clone()));
278            Ok(meta)
279        }
280        .boxed()
281    }
282
283    fn write_bytes(&mut self, _buf: Bytes) -> FsFuture<'_, ()> {
284        async move { Err(FsError::Forbidden) }.boxed()
285    }
286
287    fn write_buf(&mut self, _buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()> {
288        async move { Err(FsError::Forbidden) }.boxed()
289    }
290
291    fn read_bytes(&mut self, count: usize) -> FsFuture<'_, Bytes> {
292        let fs = self.fs.clone();
293        let seek = self.seek;
294        let open = self.open.clone();
295        async move {
296            let data = spawn_blocking(move || {
297                fs.repo
298                    .read_file_at(&open, seek, count)
299                    .map_err(|_err| FsError::GeneralFailure)
300            })
301            .await
302            .map_err(|_| FsError::GeneralFailure)??;
303            self.seek += data.len();
304            Ok(data)
305        }
306        .boxed()
307    }
308
309    fn seek(&mut self, pos: SeekFrom) -> FsFuture<'_, u64> {
310        async move {
311            match pos {
312                SeekFrom::Start(start) => {
313                    self.seek = usize::try_from(start).expect("usize overflow should not happen");
314                }
315                SeekFrom::Current(delta) => {
316                    self.seek = usize::try_from(
317                        i64::try_from(self.seek).expect("i64 wrapped around") + delta,
318                    )
319                    .expect("usize overflow should not happen");
320                }
321                SeekFrom::End(end) => {
322                    self.seek = usize::try_from(
323                        i64::try_from(self.node.meta.size).expect("i64 wrapped around") + end,
324                    )
325                    .expect("usize overflow should not happen");
326                }
327            }
328
329            Ok(self.seek as u64)
330        }
331        .boxed()
332    }
333
334    fn flush(&mut self) -> FsFuture<'_, ()> {
335        async move { Ok(()) }.boxed()
336    }
337}
338
339/// A [`DavMetaData`] implementation for [`Node`]s.
340#[derive(Clone, Debug)]
341struct DavFsMetaData(Node);
342
343impl DavMetaData for DavFsMetaData {
344    fn len(&self) -> u64 {
345        self.0.meta.size
346    }
347    fn created(&self) -> FsResult<SystemTime> {
348        Ok(now())
349    }
350    fn modified(&self) -> FsResult<SystemTime> {
351        Ok(self.0.meta.mtime.map_or_else(now, SystemTime::from))
352    }
353    fn accessed(&self) -> FsResult<SystemTime> {
354        Ok(self.0.meta.atime.map_or_else(now, SystemTime::from))
355    }
356
357    fn status_changed(&self) -> FsResult<SystemTime> {
358        Ok(self.0.meta.ctime.map_or_else(now, SystemTime::from))
359    }
360
361    fn is_dir(&self) -> bool {
362        self.0.is_dir()
363    }
364    fn is_file(&self) -> bool {
365        self.0.is_file()
366    }
367    fn is_symlink(&self) -> bool {
368        self.0.is_symlink()
369    }
370    fn executable(&self) -> FsResult<bool> {
371        if self.0.is_file() {
372            let Some(mode) = self.0.meta.mode else {
373                return Ok(false);
374            };
375            return Ok((mode & 0o100) > 0);
376        }
377        Err(FsError::NotImplemented)
378    }
379}