dav_server/
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 dav_server::{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::{
16    convert::TryInto,
17    io,
18    pin::Pin,
19    task::{Context, Poll},
20};
21
22use actix_web::body::BoxBody;
23use actix_web::error::PayloadError;
24use actix_web::{dev, Error, FromRequest, HttpRequest, HttpResponse};
25use bytes::Bytes;
26use futures_util::{future, Stream};
27use pin_project::pin_project;
28
29/// http::Request compatibility.
30///
31/// Wraps `http::Request<DavBody>` and implements `actix_web::FromRequest`.
32pub struct DavRequest {
33    pub request: http::Request<DavBody>,
34    prefix: Option<String>,
35}
36
37impl DavRequest {
38    /// Returns the request path minus the tail.
39    pub fn prefix(&self) -> Option<&str> {
40        self.prefix.as_ref().map(|s| s.as_str())
41    }
42}
43
44impl FromRequest for DavRequest {
45    type Error = Error;
46    type Future = future::Ready<Result<DavRequest, Error>>;
47
48    fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
49        let mut builder = http::Request::builder()
50            .method(req.method().as_ref())
51            .uri(req.uri().to_string())
52            .version(from_actix_http_version(req.version()));
53        for (name, value) in req.headers().iter() {
54            builder = builder.header(name.as_str(), value.as_ref());
55        }
56        let path = req.match_info().unprocessed();
57        let tail = req.match_info().unprocessed();
58        let prefix = match &path[..path.len() - tail.len()] {
59            "" | "/" => None,
60            x => Some(x.to_string()),
61        };
62
63        let body = DavBody {
64            body: payload.take(),
65        };
66        let stdreq = DavRequest {
67            request: builder.body(body).unwrap(),
68            prefix,
69        };
70        future::ready(Ok(stdreq))
71    }
72}
73
74/// Body type for `DavRequest`.
75///
76/// It wraps actix's `PayLoad` and implements `http_body::Body`.
77#[pin_project]
78pub struct DavBody {
79    #[pin]
80    body: dev::Payload,
81}
82
83impl http_body::Body for DavBody {
84    type Data = Bytes;
85    type Error = io::Error;
86
87    fn poll_frame(
88        self: Pin<&mut Self>,
89        cx: &mut Context<'_>,
90    ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
91        self.project()
92            .body
93            .poll_next(cx)
94            .map_ok(http_body::Frame::data)
95            .map_err(|err| match err {
96                PayloadError::Incomplete(Some(err)) => err,
97                PayloadError::Incomplete(None) => io::ErrorKind::BrokenPipe.into(),
98                PayloadError::Io(err) => err,
99                err => io::Error::new(io::ErrorKind::Other, format!("{err:?}")),
100            })
101    }
102}
103
104/// `http::Response` compatibility.
105///
106/// Wraps `http::Response<dav_handler::body::Body>` and implements actix_web::Responder.
107pub struct DavResponse(pub http::Response<crate::body::Body>);
108
109impl From<http::Response<crate::body::Body>> for DavResponse {
110    fn from(resp: http::Response<crate::body::Body>) -> DavResponse {
111        DavResponse(resp)
112    }
113}
114
115impl actix_web::Responder for DavResponse {
116    type Body = BoxBody;
117
118    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<BoxBody> {
119        use crate::body::{Body, BodyType};
120
121        let (parts, body) = self.0.into_parts();
122        let mut builder = HttpResponse::build(parts.status.as_u16().try_into().unwrap());
123        for (name, value) in parts.headers.into_iter() {
124            builder.append_header((name.unwrap().as_str(), value.as_ref()));
125        }
126        // I noticed that actix-web returns an empty chunked body
127        // (\r\n0\r\n\r\n) and _no_ Transfer-Encoding header on
128        // a 204 statuscode. It's probably because of
129        // builder.streaming(). So only use builder.streaming()
130        // on actual streaming replies.
131        let resp = match body.inner {
132            BodyType::Bytes(None) => builder.body(""),
133            BodyType::Bytes(Some(b)) => builder.body(b),
134            BodyType::Empty => builder.body(""),
135            b @ BodyType::AsyncStream(..) => builder.streaming(Body { inner: b }),
136        };
137        resp
138    }
139}
140
141/// Converts HTTP version from `actix_web` version of `http` crate while `actix_web` remains on old version.
142/// https://github.com/actix/actix-web/issues/3384
143fn from_actix_http_version(v: actix_web::http::Version) -> http::Version {
144    match v {
145        actix_web::http::Version::HTTP_3 => http::Version::HTTP_3,
146        actix_web::http::Version::HTTP_2 => http::Version::HTTP_2,
147        actix_web::http::Version::HTTP_11 => http::Version::HTTP_11,
148        actix_web::http::Version::HTTP_10 => http::Version::HTTP_10,
149        actix_web::http::Version::HTTP_09 => http::Version::HTTP_09,
150        v => unreachable!("unexpected HTTP version {:?}", v),
151    }
152}