Skip to main content

dav_server/
davhandler.rs

1//
2// This module contains the main entry point of the library,
3// DavHandler.
4//
5use std::error::Error as StdError;
6use std::io;
7use std::pin::pin;
8use std::sync::Arc;
9
10use bytes::{self, buf::Buf};
11use derive_where::derive_where;
12use futures_util::stream::Stream;
13use headers::HeaderMapExt;
14use http::{Request, Response, StatusCode};
15use http_body::Body as HttpBody;
16use http_body_util::BodyExt;
17
18use crate::body::{Body, StreamBody};
19use crate::davheaders;
20use crate::davpath::DavPath;
21use crate::util::{DavMethod, DavMethodSet, dav_method};
22
23use crate::DavResult;
24use crate::errors::DavError;
25use crate::fs::*;
26use crate::ls::*;
27use crate::voidfs::{VoidFs, is_voidfs};
28
29/// WebDAV request handler.
30///
31/// The [`new`](Self::new) and [`builder`](Self::builder) methods are used to instantiate a handler.
32///
33/// The [`handle`](Self::handle) and [`handle_with`](Self::handle_with) methods do the actual work.
34///
35/// Type parameter `C` represents credentials for file systems with access control.
36#[derive(Clone)]
37#[derive_where(Default)]
38pub struct DavHandler<C = ()> {
39    pub(crate) config: Arc<DavConfig<C>>,
40}
41
42/// Configuration of the handler.
43#[derive(Clone)]
44#[derive_where(Default)]
45pub struct DavConfig<C = ()> {
46    // Prefix to be stripped off when handling request.
47    pub(crate) prefix: Option<String>,
48    // Filesystem backend.
49    pub(crate) fs: Option<Box<dyn GuardedFileSystem<C>>>,
50    // Locksystem backend.
51    pub(crate) ls: Option<Box<dyn DavLockSystem>>,
52    // Set of allowed methods (None means "all methods")
53    pub(crate) allow: Option<DavMethodSet>,
54    // Principal is webdav speak for "user", used to give locks an owner (if a locksystem is
55    // active).
56    pub(crate) principal: Option<String>,
57    // Hide symbolic links? `None` maps to `true`.
58    pub(crate) hide_symlinks: Option<bool>,
59    // Does GET on a directory return indexes.
60    pub(crate) autoindex: Option<bool>,
61    // index.html
62    pub(crate) indexfile: Option<String>,
63    // read buffer size in bytes
64    pub(crate) read_buf_size: Option<usize>,
65    // Does GET on a file return 302 redirect.
66    pub(crate) redirect: Option<bool>,
67}
68
69impl<C> DavConfig<C> {
70    /// Create a new configuration builder.
71    pub fn new() -> Self {
72        Self::default()
73    }
74
75    /// Use the configuration that was built to generate a [`DavHandler`].
76    pub fn build_handler(self) -> DavHandler<C> {
77        DavHandler {
78            config: Arc::new(self),
79        }
80    }
81
82    /// Prefix to be stripped off before translating the rest of
83    /// the request path to a filesystem path.
84    pub fn strip_prefix(self, prefix: impl Into<String>) -> Self {
85        let mut this = self;
86        this.prefix = Some(prefix.into());
87        this
88    }
89
90    /// Set the filesystem to use.
91    pub fn filesystem(self, fs: Box<dyn GuardedFileSystem<C>>) -> Self {
92        let mut this = self;
93        this.fs = Some(fs);
94        this
95    }
96
97    /// Set the locksystem to use.
98    pub fn locksystem(self, ls: Box<dyn DavLockSystem>) -> Self {
99        let mut this = self;
100        this.ls = Some(ls);
101        this
102    }
103
104    /// Which methods to allow (default is all methods).
105    pub fn methods(self, allow: DavMethodSet) -> Self {
106        let mut this = self;
107        this.allow = Some(allow);
108        this
109    }
110
111    /// Set the name of the "webdav principal". This will be the owner of any created locks.
112    pub fn principal(self, principal: impl Into<String>) -> Self {
113        let mut this = self;
114        this.principal = Some(principal.into());
115        this
116    }
117
118    /// Hide symbolic links (default is true)
119    pub fn hide_symlinks(self, hide: bool) -> Self {
120        let mut this = self;
121        this.hide_symlinks = Some(hide);
122        this
123    }
124
125    /// Does a GET on a directory produce a directory index.
126    pub fn autoindex(self, autoindex: bool) -> Self {
127        let mut this = self;
128        this.autoindex = Some(autoindex);
129        this
130    }
131
132    /// Indexfile to show (index.html, usually).
133    pub fn indexfile(self, indexfile: impl Into<String>) -> Self {
134        let mut this = self;
135        this.indexfile = Some(indexfile.into());
136        this
137    }
138
139    /// Read buffer size in bytes
140    pub fn read_buf_size(self, size: usize) -> Self {
141        let mut this = self;
142        this.read_buf_size = Some(size);
143        this
144    }
145
146    pub fn redirect(self, redirect: bool) -> Self {
147        let mut this = self;
148        this.redirect = Some(redirect);
149        this
150    }
151
152    fn merge(&self, new: Self) -> Self {
153        Self {
154            prefix: new.prefix.or_else(|| self.prefix.clone()),
155            fs: new.fs.or_else(|| self.fs.clone()),
156            ls: new.ls.or_else(|| self.ls.clone()),
157            allow: new.allow.or(self.allow),
158            principal: new.principal.or_else(|| self.principal.clone()),
159            hide_symlinks: new.hide_symlinks.or(self.hide_symlinks),
160            autoindex: new.autoindex.or(self.autoindex),
161            indexfile: new.indexfile.or_else(|| self.indexfile.clone()),
162            read_buf_size: new.read_buf_size.or(self.read_buf_size),
163            redirect: new.redirect.or(self.redirect),
164        }
165    }
166}
167
168// The actual inner struct.
169//
170// At the start of the request, DavConfig is used to generate
171// a DavInner struct. DavInner::handle then handles the request.
172pub(crate) struct DavInner<C> {
173    pub prefix: String,
174    pub fs: Box<dyn GuardedFileSystem<C>>,
175    pub ls: Option<Box<dyn DavLockSystem>>,
176    pub allow: Option<DavMethodSet>,
177    pub principal: Option<String>,
178    pub hide_symlinks: Option<bool>,
179    pub autoindex: Option<bool>,
180    pub indexfile: Option<String>,
181    pub read_buf_size: Option<usize>,
182    pub redirect: Option<bool>,
183    pub credentials: C,
184}
185
186impl<C: Clone + Send + Sync + 'static> DavHandler<C> {
187    /// Create a new `DavHandler`.
188    ///
189    /// This returns a DavHandler with an empty configuration. That's only
190    /// useful if you use the `handle_with` method instead of `handle`.
191    /// Normally you should create a new `DavHandler` using `DavHandler::build`
192    /// and configure at least the filesystem, and probably the strip_prefix.
193    pub fn new() -> Self {
194        Self {
195            config: Default::default(),
196        }
197    }
198
199    /// Return a configuration builder.
200    pub fn builder() -> DavConfig<C> {
201        DavConfig::new()
202    }
203
204    /// Process a WebDAV request to a file system with access control.
205    pub async fn handle_guarded<ReqBody, ReqData, ReqError>(
206        &self,
207        req: Request<ReqBody>,
208        credentials: C,
209    ) -> Response<Body>
210    where
211        ReqData: Buf + Send + 'static,
212        ReqError: StdError + Send + Sync + 'static,
213        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
214    {
215        let inner = DavInner::new(self.config.as_ref().clone(), credentials);
216        inner.handle(req).await
217    }
218}
219
220impl DavHandler {
221    /// Process a WebDAV request to a file system without access control.
222    pub async fn handle<ReqBody, ReqData, ReqError>(&self, req: Request<ReqBody>) -> Response<Body>
223    where
224        ReqData: Buf + Send + 'static,
225        ReqError: StdError + Send + Sync + 'static,
226        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
227    {
228        let inner = DavInner::new(self.config.as_ref().clone(), ());
229        inner.handle(req).await
230    }
231
232    /// Handle a webdav request, overriding parts of the config.
233    ///
234    /// For example, the `principal` can be set for this request.
235    ///
236    /// Or, the default config has no locksystem, and you pass in
237    /// a fake locksystem (`FakeLs`) because this is a request from a
238    /// windows or macos client that needs to see locking support.
239    pub async fn handle_with<ReqBody, ReqData, ReqError>(
240        &self,
241        config: DavConfig,
242        req: Request<ReqBody>,
243    ) -> Response<Body>
244    where
245        ReqData: Buf + Send + 'static,
246        ReqError: StdError + Send + Sync + 'static,
247        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
248    {
249        let inner = DavInner::new(self.config.merge(config), ());
250        inner.handle(req).await
251    }
252
253    /// Handles a request with a `Stream` body instead of a `HttpBody`.
254    /// Used with webserver frameworks that have not
255    /// opted to use the `http_body` crate just yet.
256    #[doc(hidden)]
257    pub async fn handle_stream<ReqBody, ReqData, ReqError>(
258        &self,
259        req: Request<ReqBody>,
260    ) -> Response<Body>
261    where
262        ReqData: Buf + Send + 'static,
263        ReqError: StdError + Send + Sync + 'static,
264        ReqBody: Stream<Item = Result<ReqData, ReqError>>,
265    {
266        let req = {
267            let (parts, body) = req.into_parts();
268            Request::from_parts(parts, StreamBody::new(body))
269        };
270        let inner = DavInner::new(self.config.as_ref().clone(), ());
271        inner.handle(req).await
272    }
273
274    /// Handles a request with a `Stream` body instead of a `HttpBody`.
275    #[doc(hidden)]
276    pub async fn handle_stream_with<ReqBody, ReqData, ReqError>(
277        &self,
278        config: DavConfig,
279        req: Request<ReqBody>,
280    ) -> Response<Body>
281    where
282        ReqData: Buf + Send + 'static,
283        ReqError: StdError + Send + Sync + 'static,
284        ReqBody: Stream<Item = Result<ReqData, ReqError>>,
285    {
286        let req = {
287            let (parts, body) = req.into_parts();
288            Request::from_parts(parts, StreamBody::new(body))
289        };
290        let inner = DavInner::new(self.config.merge(config), ());
291        inner.handle(req).await
292    }
293}
294
295impl<C> DavInner<C>
296where
297    C: Clone + Send + Sync + 'static,
298{
299    pub fn new(cfg: DavConfig<C>, credentials: C) -> Self {
300        let DavConfig {
301            prefix,
302            fs,
303            ls,
304            allow,
305            principal,
306            hide_symlinks,
307            autoindex,
308            indexfile,
309            read_buf_size,
310            redirect,
311        } = cfg;
312        Self {
313            prefix: prefix.unwrap_or_default(),
314            fs: fs.unwrap_or_else(|| VoidFs::<C>::new()),
315            ls,
316            allow,
317            principal,
318            hide_symlinks,
319            autoindex,
320            indexfile,
321            read_buf_size,
322            redirect,
323            credentials,
324        }
325    }
326
327    // helper.
328    pub(crate) async fn has_parent<'a>(&'a self, path: &'a DavPath) -> bool {
329        let p = path.parent();
330        self.fs
331            .metadata(&p, &self.credentials)
332            .await
333            .map(|m| m.is_dir())
334            .unwrap_or(false)
335    }
336
337    // helper.
338    pub(crate) fn path(&self, req: &Request<()>) -> DavPath {
339        // This never fails (has been checked before)
340        DavPath::from_uri_and_prefix(req.uri(), &self.prefix).unwrap()
341    }
342
343    // See if this is a directory and if so, if we have
344    // to fixup the path by adding a slash at the end.
345    pub(crate) fn fixpath(
346        &self,
347        res: &mut Response<Body>,
348        path: &mut DavPath,
349        meta: Box<dyn DavMetaData>,
350    ) -> Box<dyn DavMetaData> {
351        if meta.is_dir() && !path.is_collection() {
352            path.add_slash();
353            let newloc = path.with_prefix().as_url_string();
354            res.headers_mut()
355                .typed_insert(davheaders::ContentLocation(newloc));
356        }
357        meta
358    }
359
360    // drain request body and return length.
361    pub(crate) async fn read_request<ReqBody, ReqData, ReqError>(
362        &self,
363        body: ReqBody,
364        max_size: usize,
365    ) -> DavResult<Vec<u8>>
366    where
367        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
368        ReqData: Buf + Send + 'static,
369        ReqError: StdError + Send + Sync + 'static,
370    {
371        let mut data = Vec::new();
372        let mut body = pin!(body);
373
374        while let Some(res) = body.frame().await {
375            let mut data_frame = res.map_err(|_| {
376                DavError::IoError(io::Error::new(
377                    io::ErrorKind::UnexpectedEof,
378                    "UnexpectedEof",
379                ))
380            })?;
381
382            let Some(buf) = data_frame.data_mut() else {
383                continue;
384            };
385
386            while buf.has_remaining() {
387                if data.len() + buf.remaining() > max_size {
388                    return Err(StatusCode::PAYLOAD_TOO_LARGE.into());
389                }
390                let b = buf.chunk();
391                let l = b.len();
392                data.extend_from_slice(b);
393                buf.advance(l);
394            }
395        }
396        Ok(data)
397    }
398
399    // internal dispatcher.
400    async fn handle<ReqBody, ReqData, ReqError>(self, req: Request<ReqBody>) -> Response<Body>
401    where
402        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
403        ReqData: Buf + Send + 'static,
404        ReqError: StdError + Send + Sync + 'static,
405    {
406        let is_ms = req
407            .headers()
408            .get("user-agent")
409            .and_then(|s| s.to_str().ok())
410            .map(|s| s.contains("Microsoft"))
411            .unwrap_or(false);
412
413        // Turn any DavError results into a HTTP error response.
414        match self.handle2(req).await {
415            Ok(resp) => {
416                debug!("== END REQUEST result OK");
417                resp
418            }
419            Err(err) => {
420                debug!("== END REQUEST result {err:?}");
421                let mut resp = Response::builder();
422                if is_ms && err.statuscode() == StatusCode::NOT_FOUND {
423                    // This is an attempt to convince Windows to not
424                    // cache a 404 NOT_FOUND for 30-60 seconds.
425                    //
426                    // That is a problem since windows caches the NOT_FOUND in a
427                    // case-insensitive way. So if "www" does not exist, but "WWW" does,
428                    // and you do a "dir www" and then a "dir WWW" the second one
429                    // will fail.
430                    //
431                    // Ofcourse the below is not sufficient. Fixes welcome.
432                    resp = resp
433                        .header("Cache-Control", "no-store, no-cache, must-revalidate")
434                        .header("Progma", "no-cache")
435                        .header("Expires", "0")
436                        .header("Vary", "*");
437                }
438                resp = resp.header("Content-Length", "0").status(err.statuscode());
439                if err.must_close() {
440                    resp = resp.header("connection", "close");
441                }
442                resp.body(Body::empty()).unwrap()
443            }
444        }
445    }
446
447    // internal dispatcher part 2.
448    async fn handle2<ReqBody, ReqData, ReqError>(
449        mut self,
450        req: Request<ReqBody>,
451    ) -> DavResult<Response<Body>>
452    where
453        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
454        ReqData: Buf + Send + 'static,
455        ReqError: StdError + Send + Sync + 'static,
456    {
457        let (req, body) = {
458            let (parts, body) = req.into_parts();
459            (Request::from_parts(parts, ()), body)
460        };
461
462        // debug when running the webdav litmus tests.
463        if log_enabled!(log::Level::Debug)
464            && let Some(t) = req.headers().typed_get::<davheaders::XLitmus>()
465        {
466            debug!("X-Litmus: {t:?}");
467        }
468
469        // translate HTTP method to Webdav method.
470        let method = match dav_method(req.method()) {
471            Ok(m) => m,
472            Err(e) => {
473                debug!("refusing method {} request {}", req.method(), req.uri());
474                return Err(e);
475            }
476        };
477
478        // See if method makes sense if we don't have a filesystem.
479        if is_voidfs::<C>(&self.fs) {
480            match method {
481                DavMethod::Options => {
482                    if self
483                        .allow
484                        .as_ref()
485                        .map(|a| a.contains(DavMethod::Options))
486                        .unwrap_or(true)
487                    {
488                        let mut a = DavMethodSet::none();
489                        a.add(DavMethod::Options);
490                        self.allow = Some(a);
491                    }
492                }
493                _ => {
494                    debug!("no filesystem: method not allowed on request {}", req.uri());
495                    return Err(DavError::StatusClose(StatusCode::METHOD_NOT_ALLOWED));
496                }
497            }
498        }
499
500        // see if method is allowed.
501        if let Some(ref a) = self.allow
502            && !a.contains(method)
503        {
504            debug!(
505                "method {} not allowed on request {}",
506                req.method(),
507                req.uri()
508            );
509            return Err(DavError::StatusClose(StatusCode::METHOD_NOT_ALLOWED));
510        }
511
512        // make sure the request path is valid.
513        let path = DavPath::from_uri_and_prefix(req.uri(), &self.prefix)?;
514
515        // PUT is the only handler that reads the body itself. All the
516        // other handlers either expected no body, or a pre-read Vec<u8>.
517        let (body_strm, body_data) = match method {
518            DavMethod::Put | DavMethod::Patch => (Some(body), Vec::new()),
519            _ => (None, self.read_request(body, 65536).await?),
520        };
521
522        // Not all methods accept a body.
523        match method {
524            DavMethod::Put
525            | DavMethod::Patch
526            | DavMethod::PropFind
527            | DavMethod::PropPatch
528            | DavMethod::Lock
529            | DavMethod::Report
530            | DavMethod::MkCalendar
531            | DavMethod::MkAddressbook => {}
532            _ => {
533                if !body_data.is_empty() {
534                    return Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into());
535                }
536            }
537        }
538
539        debug!("== START REQUEST {method:?} {path}");
540
541        match method {
542            DavMethod::Options => self.handle_options(&req).await,
543            DavMethod::PropFind => self.handle_propfind(&req, &body_data).await,
544            DavMethod::PropPatch => self.handle_proppatch(&req, &body_data).await,
545            DavMethod::MkCol => self.handle_mkcol(&req).await,
546            DavMethod::Delete => self.handle_delete(&req).await,
547            DavMethod::Lock => self.handle_lock(&req, &body_data).await,
548            DavMethod::Unlock => self.handle_unlock(&req).await,
549            DavMethod::Head | DavMethod::Get => self.handle_get(&req).await,
550            DavMethod::Copy | DavMethod::Move => self.handle_copymove(&req, method).await,
551            DavMethod::Put | DavMethod::Patch => self.handle_put(&req, body_strm.unwrap()).await,
552            #[cfg(any(feature = "caldav", feature = "carddav"))]
553            DavMethod::Report => self.handle_report(&req, &body_data).await,
554            #[cfg(feature = "caldav")]
555            DavMethod::MkCalendar => self.handle_mkcalendar(&req, &body_data).await,
556            #[cfg(feature = "carddav")]
557            DavMethod::MkAddressbook => self.handle_mkaddressbook(&req, &body_data).await,
558            #[cfg(not(any(feature = "caldav", feature = "carddav")))]
559            DavMethod::Report => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
560            #[cfg(not(feature = "caldav"))]
561            DavMethod::MkCalendar => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
562            #[cfg(not(feature = "carddav"))]
563            DavMethod::MkAddressbook => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
564        }
565    }
566}