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