dav_server/
warp.rs

1//! Adapter for the `warp` HTTP server framework.
2//!
3//! The filters in this module will always succeed and never
4//! return an error. For example, if a file is not found, the
5//! filter will return a 404 reply, and not an internal
6//! rejection.
7//!
8use std::convert::Infallible;
9#[cfg(any(docsrs, feature = "localfs"))]
10use std::path::Path;
11
12use warp::{
13    Filter, Reply,
14    filters::BoxedFilter,
15    http::{HeaderMap, Method},
16};
17
18use crate::{DavHandler, body::Body};
19#[cfg(any(docsrs, feature = "localfs"))]
20use crate::{fakels::FakeLs, localfs::LocalFs};
21
22/// Reply-filter that runs a DavHandler.
23///
24/// Just pass in a pre-configured DavHandler. If a prefix was not
25/// configured, it will be the request path up to this point.
26pub fn dav_handler(handler: DavHandler) -> BoxedFilter<(impl Reply,)> {
27    use http::uri::Uri;
28    use warp::path::{FullPath, Tail};
29
30    warp::method()
31        .and(warp::path::full())
32        .and(warp::path::tail())
33        .and(warp::header::headers_cloned())
34        .and(warp::body::stream())
35        .and_then(
36            move |method: Method,
37                  path_full: FullPath,
38                  path_tail: Tail,
39                  headers: HeaderMap,
40                  body| {
41                let handler = handler.clone();
42
43                async move {
44                    // rebuild an http::Request struct.
45                    let path_str = path_full.as_str();
46                    let uri = path_str.parse::<Uri>().unwrap();
47                    let mut builder = http::Request::builder().method(method.as_ref()).uri(uri);
48                    for (k, v) in headers.iter() {
49                        builder = builder.header(k.as_str(), v.as_ref());
50                    }
51                    let request = builder.body(body).unwrap();
52
53                    let response = if handler.config.prefix.is_some() {
54                        // Run a handler with the configured path prefix.
55                        handler.handle_stream(request).await
56                    } else {
57                        // Run a handler with the current path prefix.
58                        let path_len = path_str.len();
59                        let tail_len = path_tail.as_str().len();
60                        let prefix = path_str[..path_len - tail_len].to_string();
61                        let config = DavHandler::builder().strip_prefix(prefix);
62                        handler.handle_stream_with(config, request).await
63                    };
64
65                    // Need to remap the http_body::Body to a hyper::Body.
66                    let response = warp_response(response).unwrap();
67                    Ok::<_, Infallible>(response)
68                }
69            },
70        )
71        .boxed()
72}
73
74/// Creates a Filter that serves files and directories at the
75/// base path joined with the remainder of the request path,
76/// like `warp::filters::fs::dir`.
77///
78/// The behaviour for serving a directory depends on the flags:
79///
80/// - `index_html`: if an `index.html` file is found, serve it.
81/// - `auto_index_over_get`: Create a directory index page when accessing over HTTP `GET` (but NOT
82///   affecting WebDAV `PROPFIND` method currently). In the current implementation, this only
83///   affects HTTP `GET` method (commonly used for listing the directories when accessing through a
84///   `http://` or `https://` URL for a directory in a browser), but NOT WebDAV listing of a
85///   directory (HTTP `PROPFIND`). BEWARE: The name and behaviour of this parameter variable may
86///   change, and later it may control WebDAV `PROPFIND`, too (but not as of now).
87///   
88///   In release mode, if `auto_index_over_get` is `true`, then this executes as described above
89///   (currently affecting only HTTP `GET`), but beware of this current behaviour.
90///   
91///   In debug mode, if `auto_index_over_get` is `false`, this _panics_. That is so that it alerts
92///   the developers to this current limitation, so they don't accidentally expect
93///   `auto_index_over_get` to control WebDAV.
94/// - no flags set: 404.
95#[cfg(any(docsrs, feature = "localfs"))]
96pub fn dav_dir(
97    base: impl AsRef<Path>,
98    index_html: bool,
99    auto_index_over_get: bool,
100) -> BoxedFilter<(impl Reply,)> {
101    debug_assert!(
102        auto_index_over_get,
103        "See documentation of dav_server::warp::dav_dir(...)."
104    );
105    let mut builder = DavHandler::builder()
106        .filesystem(LocalFs::new(base, false, false, false))
107        .locksystem(FakeLs::new())
108        .autoindex(auto_index_over_get);
109    if index_html {
110        builder = builder.indexfile("index.html".to_string())
111    }
112    let handler = builder.build_handler();
113    dav_handler(handler)
114}
115
116/// Creates a Filter that serves a single file, ignoring the request path,
117/// like `warp::filters::fs::file`.
118#[cfg(any(docsrs, feature = "localfs"))]
119pub fn dav_file(file: impl AsRef<Path>) -> BoxedFilter<(impl Reply,)> {
120    let handler = DavHandler::builder()
121        .filesystem(LocalFs::new_file(file, false))
122        .locksystem(FakeLs::new())
123        .build_handler();
124    dav_handler(handler)
125}
126
127/// Adapts the response to the `warp` versions of `hyper` and `http` while `warp` remains on old versions.
128/// https://github.com/seanmonstar/warp/issues/1088
129fn warp_response(
130    response: http::Response<Body>,
131) -> Result<warp::http::Response<warp::hyper::Body>, warp::http::Error> {
132    let (parts, body) = response.into_parts();
133    // Leave response extensions empty.
134    let mut response = warp::http::Response::builder()
135        .version(warp_http_version(parts.version))
136        .status(parts.status.as_u16());
137    // Ignore headers without the name.
138    let headers = parts.headers.into_iter().filter_map(|(k, v)| Some((k?, v)));
139    for (k, v) in headers {
140        response = response.header(k.as_str(), v.as_ref());
141    }
142    response.body(warp::hyper::Body::wrap_stream(body))
143}
144
145/// Adapts HTTP version to the `warp` version of `http` crate while `warp` remains on old version.
146/// https://github.com/seanmonstar/warp/issues/1088
147fn warp_http_version(v: http::Version) -> warp::http::Version {
148    match v {
149        http::Version::HTTP_3 => warp::http::Version::HTTP_3,
150        http::Version::HTTP_2 => warp::http::Version::HTTP_2,
151        http::Version::HTTP_11 => warp::http::Version::HTTP_11,
152        http::Version::HTTP_10 => warp::http::Version::HTTP_10,
153        http::Version::HTTP_09 => warp::http::Version::HTTP_09,
154        v => unreachable!("unexpected HTTP version {:?}", v),
155    }
156}