use std::{
convert::TryInto,
future, io,
pin::Pin,
task::{Context, Poll},
};
use actix_web::body::BoxBody;
use actix_web::error::PayloadError;
use actix_web::{Error, FromRequest, HttpRequest, HttpResponse, dev};
use bytes::Bytes;
use futures_util::Stream;
use pin_project_lite::pin_project;
pub struct DavRequest {
pub request: http::Request<DavBody>,
prefix: Option<String>,
}
impl DavRequest {
pub fn prefix(&self) -> Option<&str> {
self.prefix.as_deref()
}
}
impl FromRequest for DavRequest {
type Error = Error;
type Future = future::Ready<Result<DavRequest, Error>>;
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let mut builder = http::Request::builder()
.method(req.method().as_ref())
.uri(req.uri().to_string())
.version(from_actix_http_version(req.version()));
for (name, value) in req.headers().iter() {
builder = builder.header(name.as_str(), value.as_ref());
}
let path = req.path();
let tail = req.match_info().unprocessed();
let prefix = match &path[..path.len() - tail.len()] {
"" | "/" => None,
x => Some(x.to_string()),
};
let body = DavBody {
body: payload.take(),
};
let stdreq = DavRequest {
request: builder.body(body).unwrap(),
prefix,
};
future::ready(Ok(stdreq))
}
}
pin_project! {
pub struct DavBody {
#[pin]
body: dev::Payload,
}
}
impl http_body::Body for DavBody {
type Data = Bytes;
type Error = io::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
self.project()
.body
.poll_next(cx)
.map_ok(http_body::Frame::data)
.map_err(|err| match err {
PayloadError::Incomplete(Some(err)) => err,
PayloadError::Incomplete(None) => io::ErrorKind::BrokenPipe.into(),
PayloadError::Io(err) => err,
err => io::Error::other(format!("{err:?}")),
})
}
}
pub struct DavResponse(pub http::Response<crate::body::Body>);
impl From<http::Response<crate::body::Body>> for DavResponse {
fn from(resp: http::Response<crate::body::Body>) -> DavResponse {
DavResponse(resp)
}
}
impl actix_web::Responder for DavResponse {
type Body = BoxBody;
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<BoxBody> {
use crate::body::{Body, BodyType};
let (parts, body) = self.0.into_parts();
let mut builder = HttpResponse::build(parts.status.as_u16().try_into().unwrap());
for (name, value) in parts.headers.into_iter() {
builder.append_header((name.unwrap().as_str(), value.as_ref()));
}
match body.inner {
BodyType::Bytes(None) => builder.body(""),
BodyType::Bytes(Some(b)) => builder.body(b),
BodyType::Empty => builder.body(""),
b @ BodyType::AsyncStream(..) => builder.streaming(Body { inner: b }),
}
}
}
fn from_actix_http_version(v: actix_web::http::Version) -> http::Version {
match v {
actix_web::http::Version::HTTP_3 => http::Version::HTTP_3,
actix_web::http::Version::HTTP_2 => http::Version::HTTP_2,
actix_web::http::Version::HTTP_11 => http::Version::HTTP_11,
actix_web::http::Version::HTTP_10 => http::Version::HTTP_10,
actix_web::http::Version::HTTP_09 => http::Version::HTTP_09,
v => unreachable!("unexpected HTTP version {:?}", v),
}
}