webdav_handler/
actix.rs

1//! Adapters to use the standard `http` types with Actix.
2//!
3//! Using the adapters in this crate, it's easy to build a webdav
4//! handler for actix:
5//!
6//! ```no_run
7//! use webdav_handler::{DavHandler, actix::DavRequest, actix::DavResponse};
8//! use actix_web::web;
9//!
10//! pub async fn dav_handler(req: DavRequest, davhandler: web::Data<DavHandler>) -> DavResponse {
11//!     davhandler.handle(req.request).await.into()
12//! }
13//! ```
14//!
15use std::io;
16
17use std::pin::Pin;
18use std::task::{Context, Poll};
19
20use actix_web::error::PayloadError;
21use actix_web::{dev, Error, FromRequest, HttpRequest, HttpResponse};
22use bytes::Bytes;
23use futures::{future, Stream};
24use pin_project::pin_project;
25
26/// http::Request compatibility.
27///
28/// Wraps `http::Request<DavBody>` and implements `actix_web::FromRequest`.
29pub struct DavRequest {
30    pub request: http::Request<DavBody>,
31    prefix:      Option<String>,
32}
33
34impl DavRequest {
35    /// Returns the request path minus the tail.
36    pub fn prefix(&self) -> Option<&str> {
37        self.prefix.as_ref().map(|s| s.as_str())
38    }
39}
40
41impl FromRequest for DavRequest {
42    type Config = ();
43    type Error = Error;
44    type Future = future::Ready<Result<DavRequest, Error>>;
45
46    fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
47        let mut builder = http::Request::builder()
48            .method(req.method().to_owned())
49            .uri(req.uri().to_owned())
50            .version(req.version().to_owned());
51        for (name, value) in req.headers().iter() {
52            builder = builder.header(name, value);
53        }
54        let path = req.match_info().path();
55        let tail = req.match_info().unprocessed();
56        let prefix = match &path[..path.len() - tail.len()] {
57            "" | "/" => None,
58            x => Some(x.to_string()),
59        };
60
61        let body = DavBody { body: payload.take() };
62        let stdreq = DavRequest {
63            request: builder.body(body).unwrap(),
64            prefix,
65        };
66        future::ready(Ok(stdreq))
67    }
68}
69
70/// Body type for `DavRequest`.
71///
72/// It wraps actix's `PayLoad` and implements `http_body::Body`.
73#[pin_project]
74pub struct DavBody {
75    #[pin]
76    body: dev::Payload,
77}
78
79impl http_body::Body for DavBody {
80    type Data = Bytes;
81    type Error = io::Error;
82
83    fn poll_data(
84        self: Pin<&mut Self>,
85        cx: &mut Context<'_>,
86    ) -> Poll<Option<Result<Self::Data, Self::Error>>>
87    {
88        let this = self.project();
89        match this.body.poll_next(cx) {
90            Poll::Ready(Some(Ok(data))) => Poll::Ready(Some(Ok(data))),
91            Poll::Ready(Some(Err(err))) => {
92                Poll::Ready(Some(Err(match err {
93                    PayloadError::Incomplete(Some(err)) => err,
94                    PayloadError::Incomplete(None) => io::ErrorKind::BrokenPipe.into(),
95                    PayloadError::Io(err) => err,
96                    other => io::Error::new(io::ErrorKind::Other, format!("{:?}", other)),
97                })))
98            },
99            Poll::Ready(None) => Poll::Ready(None),
100            Poll::Pending => Poll::Pending,
101        }
102    }
103
104    fn poll_trailers(
105        self: Pin<&mut Self>,
106        _cx: &mut Context,
107    ) -> Poll<Result<Option<http::HeaderMap>, Self::Error>>
108    {
109        Poll::Ready(Ok(None))
110    }
111}
112
113/// `http::Response` compatibility.
114///
115/// Wraps `http::Response<dav_handler::body::Body>` and implements actix_web::Responder.
116pub struct DavResponse(pub http::Response<crate::body::Body>);
117
118impl From<http::Response<crate::body::Body>> for DavResponse {
119    fn from(resp: http::Response<crate::body::Body>) -> DavResponse {
120        DavResponse(resp)
121    }
122}
123
124impl actix_web::Responder for DavResponse {
125
126    fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
127        use crate::body::{Body, BodyType};
128
129        let (parts, body) = self.0.into_parts();
130        let mut builder = HttpResponse::build(parts.status);
131        for (name, value) in parts.headers.into_iter() {
132            builder.append_header((name.unwrap(), value));
133        }
134        // I noticed that actix-web returns an empty chunked body
135        // (\r\n0\r\n\r\n) and _no_ Transfer-Encoding header on
136        // a 204 statuscode. It's probably because of
137        // builder.streaming(). So only use builder.streaming()
138        // on actual streaming replies.
139        let resp = match body.inner {
140            BodyType::Bytes(None) => builder.body(""),
141            BodyType::Bytes(Some(b)) => builder.body(b),
142            BodyType::Empty => builder.body(""),
143            b @ BodyType::AsyncStream(..) => builder.streaming(Body { inner: b }),
144        };
145        resp
146    }
147}