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        principal: String,
209        credentials: C,
210    ) -> Response<Body>
211    where
212        ReqData: Buf + Send + 'static,
213        ReqError: StdError + Send + Sync + 'static,
214        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
215    {
216        let mut inner = DavInner::new(self.config.as_ref().clone(), credentials);
217        inner.principal = Some(principal);
218        inner.handle(req).await
219    }
220}
221
222impl DavHandler {
223    /// Process a WebDAV request to a file system without access control.
224    pub async fn handle<ReqBody, ReqData, ReqError>(&self, req: Request<ReqBody>) -> Response<Body>
225    where
226        ReqData: Buf + Send + 'static,
227        ReqError: StdError + Send + Sync + 'static,
228        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
229    {
230        let inner = DavInner::new(self.config.as_ref().clone(), ());
231        inner.handle(req).await
232    }
233
234    /// Handle a webdav request, overriding parts of the config.
235    ///
236    /// For example, the `principal` can be set for this request.
237    ///
238    /// Or, the default config has no locksystem, and you pass in
239    /// a fake locksystem (`FakeLs`) because this is a request from a
240    /// windows or macos client that needs to see locking support.
241    pub async fn handle_with<ReqBody, ReqData, ReqError>(
242        &self,
243        config: DavConfig,
244        req: Request<ReqBody>,
245    ) -> Response<Body>
246    where
247        ReqData: Buf + Send + 'static,
248        ReqError: StdError + Send + Sync + 'static,
249        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
250    {
251        let inner = DavInner::new(self.config.merge(config), ());
252        inner.handle(req).await
253    }
254
255    /// Handles a request with a `Stream` body instead of a `HttpBody`.
256    /// Used with webserver frameworks that have not
257    /// opted to use the `http_body` crate just yet.
258    #[doc(hidden)]
259    pub async fn handle_stream<ReqBody, ReqData, ReqError>(
260        &self,
261        req: Request<ReqBody>,
262    ) -> Response<Body>
263    where
264        ReqData: Buf + Send + 'static,
265        ReqError: StdError + Send + Sync + 'static,
266        ReqBody: Stream<Item = Result<ReqData, ReqError>>,
267    {
268        let req = {
269            let (parts, body) = req.into_parts();
270            Request::from_parts(parts, StreamBody::new(body))
271        };
272        let inner = DavInner::new(self.config.as_ref().clone(), ());
273        inner.handle(req).await
274    }
275
276    /// Handles a request with a `Stream` body instead of a `HttpBody`.
277    #[doc(hidden)]
278    pub async fn handle_stream_with<ReqBody, ReqData, ReqError>(
279        &self,
280        config: DavConfig,
281        req: Request<ReqBody>,
282    ) -> Response<Body>
283    where
284        ReqData: Buf + Send + 'static,
285        ReqError: StdError + Send + Sync + 'static,
286        ReqBody: Stream<Item = Result<ReqData, ReqError>>,
287    {
288        let req = {
289            let (parts, body) = req.into_parts();
290            Request::from_parts(parts, StreamBody::new(body))
291        };
292        let inner = DavInner::new(self.config.merge(config), ());
293        inner.handle(req).await
294    }
295}
296
297impl<C> DavInner<C>
298where
299    C: Clone + Send + Sync + 'static,
300{
301    pub fn new(cfg: DavConfig<C>, credentials: C) -> Self {
302        let DavConfig {
303            prefix,
304            fs,
305            ls,
306            allow,
307            principal,
308            hide_symlinks,
309            autoindex,
310            indexfile,
311            read_buf_size,
312            redirect,
313        } = cfg;
314        Self {
315            prefix: prefix.unwrap_or_default(),
316            fs: fs.unwrap_or_else(|| VoidFs::<C>::new()),
317            ls,
318            allow,
319            principal,
320            hide_symlinks,
321            autoindex,
322            indexfile,
323            read_buf_size,
324            redirect,
325            credentials,
326        }
327    }
328
329    // helper.
330    pub(crate) async fn has_parent<'a>(&'a self, path: &'a DavPath) -> bool {
331        let p = path.parent();
332        self.fs
333            .metadata(&p, &self.credentials)
334            .await
335            .map(|m| m.is_dir())
336            .unwrap_or(false)
337    }
338
339    // helper.
340    pub(crate) fn path(&self, req: &Request<()>) -> DavPath {
341        // This never fails (has been checked before)
342        DavPath::from_uri_and_prefix(req.uri(), &self.prefix).unwrap()
343    }
344
345    // See if this is a directory and if so, if we have
346    // to fixup the path by adding a slash at the end.
347    pub(crate) fn fixpath(
348        &self,
349        res: &mut Response<Body>,
350        path: &mut DavPath,
351        meta: Box<dyn DavMetaData>,
352    ) -> Box<dyn DavMetaData> {
353        if meta.is_dir() && !path.is_collection() {
354            path.add_slash();
355            let newloc = path.with_prefix().as_url_string();
356            res.headers_mut()
357                .typed_insert(davheaders::ContentLocation(newloc));
358        }
359        meta
360    }
361
362    // drain request body and return length.
363    pub(crate) async fn read_request<ReqBody, ReqData, ReqError>(
364        &self,
365        body: ReqBody,
366        max_size: usize,
367    ) -> DavResult<Vec<u8>>
368    where
369        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
370        ReqData: Buf + Send + 'static,
371        ReqError: StdError + Send + Sync + 'static,
372    {
373        let mut data = Vec::new();
374        let mut body = pin!(body);
375
376        while let Some(res) = body.frame().await {
377            let mut data_frame = res.map_err(|_| {
378                DavError::IoError(io::Error::new(
379                    io::ErrorKind::UnexpectedEof,
380                    "UnexpectedEof",
381                ))
382            })?;
383
384            let Some(buf) = data_frame.data_mut() else {
385                continue;
386            };
387
388            while buf.has_remaining() {
389                if data.len() + buf.remaining() > max_size {
390                    return Err(StatusCode::PAYLOAD_TOO_LARGE.into());
391                }
392                let b = buf.chunk();
393                let l = b.len();
394                data.extend_from_slice(b);
395                buf.advance(l);
396            }
397        }
398        Ok(data)
399    }
400
401    // internal dispatcher.
402    async fn handle<ReqBody, ReqData, ReqError>(self, req: Request<ReqBody>) -> Response<Body>
403    where
404        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
405        ReqData: Buf + Send + 'static,
406        ReqError: StdError + Send + Sync + 'static,
407    {
408        let is_ms = req
409            .headers()
410            .get("user-agent")
411            .and_then(|s| s.to_str().ok())
412            .map(|s| s.contains("Microsoft"))
413            .unwrap_or(false);
414
415        // Turn any DavError results into a HTTP error response.
416        match self.handle2(req).await {
417            Ok(resp) => {
418                debug!("== END REQUEST result OK");
419                resp
420            }
421            Err(err) => {
422                debug!("== END REQUEST result {err:?}");
423                let mut resp = Response::builder();
424                if is_ms && err.statuscode() == StatusCode::NOT_FOUND {
425                    // This is an attempt to convince Windows to not
426                    // cache a 404 NOT_FOUND for 30-60 seconds.
427                    //
428                    // That is a problem since windows caches the NOT_FOUND in a
429                    // case-insensitive way. So if "www" does not exist, but "WWW" does,
430                    // and you do a "dir www" and then a "dir WWW" the second one
431                    // will fail.
432                    //
433                    // Ofcourse the below is not sufficient. Fixes welcome.
434                    resp = resp
435                        .header("Cache-Control", "no-store, no-cache, must-revalidate")
436                        .header("Progma", "no-cache")
437                        .header("Expires", "0")
438                        .header("Vary", "*");
439                }
440                resp = resp.header("Content-Length", "0").status(err.statuscode());
441                if err.must_close() {
442                    resp = resp.header("connection", "close");
443                }
444                resp.body(Body::empty()).unwrap()
445            }
446        }
447    }
448
449    // internal dispatcher part 2.
450    async fn handle2<ReqBody, ReqData, ReqError>(
451        mut self,
452        req: Request<ReqBody>,
453    ) -> DavResult<Response<Body>>
454    where
455        ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
456        ReqData: Buf + Send + 'static,
457        ReqError: StdError + Send + Sync + 'static,
458    {
459        let (req, body) = {
460            let (parts, body) = req.into_parts();
461            (Request::from_parts(parts, ()), body)
462        };
463
464        // debug when running the webdav litmus tests.
465        if log_enabled!(log::Level::Debug)
466            && let Some(t) = req.headers().typed_get::<davheaders::XLitmus>()
467        {
468            debug!("X-Litmus: {t:?}");
469        }
470
471        // translate HTTP method to Webdav method.
472        let method = match dav_method(req.method()) {
473            Ok(m) => m,
474            Err(e) => {
475                debug!("refusing method {} request {}", req.method(), req.uri());
476                return Err(e);
477            }
478        };
479
480        // See if method makes sense if we don't have a filesystem.
481        if is_voidfs::<C>(&self.fs) {
482            match method {
483                DavMethod::Options => {
484                    if self
485                        .allow
486                        .as_ref()
487                        .map(|a| a.contains(DavMethod::Options))
488                        .unwrap_or(true)
489                    {
490                        let mut a = DavMethodSet::none();
491                        a.add(DavMethod::Options);
492                        self.allow = Some(a);
493                    }
494                }
495                _ => {
496                    debug!("no filesystem: method not allowed on request {}", req.uri());
497                    return Err(DavError::StatusClose(StatusCode::METHOD_NOT_ALLOWED));
498                }
499            }
500        }
501
502        // see if method is allowed.
503        if let Some(ref a) = self.allow
504            && !a.contains(method)
505        {
506            debug!(
507                "method {} not allowed on request {}",
508                req.method(),
509                req.uri()
510            );
511            return Err(DavError::StatusClose(StatusCode::METHOD_NOT_ALLOWED));
512        }
513
514        // make sure the request path is valid.
515        let path = DavPath::from_uri_and_prefix(req.uri(), &self.prefix)?;
516
517        // PUT is the only handler that reads the body itself. All the
518        // other handlers either expected no body, or a pre-read Vec<u8>.
519        let (body_strm, body_data) = match method {
520            DavMethod::Put | DavMethod::Patch => (Some(body), Vec::new()),
521            _ => (None, self.read_request(body, 65536).await?),
522        };
523
524        // Not all methods accept a body.
525        match method {
526            DavMethod::Put
527            | DavMethod::Patch
528            | DavMethod::PropFind
529            | DavMethod::PropPatch
530            | DavMethod::Lock
531            | DavMethod::Report
532            | DavMethod::MkCalendar
533            | DavMethod::MkAddressbook => {}
534            _ => {
535                if !body_data.is_empty() {
536                    return Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into());
537                }
538            }
539        }
540
541        debug!("== START REQUEST {method:?} {path}");
542
543        match method {
544            DavMethod::Options => self.handle_options(&req).await,
545            DavMethod::PropFind => self.handle_propfind(&req, &body_data).await,
546            DavMethod::PropPatch => self.handle_proppatch(&req, &body_data).await,
547            DavMethod::MkCol => self.handle_mkcol(&req).await,
548            DavMethod::Delete => self.handle_delete(&req).await,
549            DavMethod::Lock => self.handle_lock(&req, &body_data).await,
550            DavMethod::Unlock => self.handle_unlock(&req).await,
551            DavMethod::Head | DavMethod::Get => self.handle_get(&req).await,
552            DavMethod::Copy | DavMethod::Move => self.handle_copymove(&req, method).await,
553            DavMethod::Put | DavMethod::Patch => self.handle_put(&req, body_strm.unwrap()).await,
554            #[cfg(any(feature = "caldav", feature = "carddav"))]
555            DavMethod::Report => self.handle_report(&req, &body_data).await,
556            #[cfg(feature = "caldav")]
557            DavMethod::MkCalendar => self.handle_mkcalendar(&req, &body_data).await,
558            #[cfg(feature = "carddav")]
559            DavMethod::MkAddressbook => self.handle_mkaddressbook(&req, &body_data).await,
560            #[cfg(not(any(feature = "caldav", feature = "carddav")))]
561            DavMethod::Report => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
562            #[cfg(not(feature = "caldav"))]
563            DavMethod::MkCalendar => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
564            #[cfg(not(feature = "carddav"))]
565            DavMethod::MkAddressbook => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
566        }
567    }
568}