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