use std::error::Error as StdError;
use std::io;
use std::pin::pin;
use std::sync::Arc;
use bytes::{self, buf::Buf};
use derive_where::derive_where;
use futures_util::stream::Stream;
use headers::HeaderMapExt;
use http::{Request, Response, StatusCode};
use http_body::Body as HttpBody;
use http_body_util::BodyExt;
use crate::body::{Body, StreamBody};
use crate::davheaders;
use crate::davpath::DavPath;
use crate::util::{DavMethod, DavMethodSet, dav_method};
use crate::DavResult;
use crate::errors::DavError;
use crate::fs::*;
use crate::ls::*;
use crate::voidfs::{VoidFs, is_voidfs};
#[derive(Clone)]
#[derive_where(Default)]
pub struct DavHandler<C = ()> {
pub(crate) config: Arc<DavConfig<C>>,
}
#[derive(Clone)]
#[derive_where(Default)]
pub struct DavConfig<C = ()> {
pub(crate) prefix: Option<String>,
pub(crate) fs: Option<Box<dyn GuardedFileSystem<C>>>,
pub(crate) ls: Option<Box<dyn DavLockSystem>>,
pub(crate) allow: Option<DavMethodSet>,
pub(crate) principal: Option<String>,
pub(crate) hide_symlinks: Option<bool>,
pub(crate) autoindex: Option<bool>,
pub(crate) indexfile: Option<String>,
pub(crate) read_buf_size: Option<usize>,
pub(crate) redirect: Option<bool>,
}
impl<C> DavConfig<C> {
pub fn new() -> Self {
Self::default()
}
pub fn build_handler(self) -> DavHandler<C> {
DavHandler {
config: Arc::new(self),
}
}
pub fn strip_prefix(self, prefix: impl Into<String>) -> Self {
let mut this = self;
this.prefix = Some(prefix.into());
this
}
pub fn filesystem(self, fs: Box<dyn GuardedFileSystem<C>>) -> Self {
let mut this = self;
this.fs = Some(fs);
this
}
pub fn locksystem(self, ls: Box<dyn DavLockSystem>) -> Self {
let mut this = self;
this.ls = Some(ls);
this
}
pub fn methods(self, allow: DavMethodSet) -> Self {
let mut this = self;
this.allow = Some(allow);
this
}
pub fn principal(self, principal: impl Into<String>) -> Self {
let mut this = self;
this.principal = Some(principal.into());
this
}
pub fn hide_symlinks(self, hide: bool) -> Self {
let mut this = self;
this.hide_symlinks = Some(hide);
this
}
pub fn autoindex(self, autoindex: bool) -> Self {
let mut this = self;
this.autoindex = Some(autoindex);
this
}
pub fn indexfile(self, indexfile: impl Into<String>) -> Self {
let mut this = self;
this.indexfile = Some(indexfile.into());
this
}
pub fn read_buf_size(self, size: usize) -> Self {
let mut this = self;
this.read_buf_size = Some(size);
this
}
pub fn redirect(self, redirect: bool) -> Self {
let mut this = self;
this.redirect = Some(redirect);
this
}
fn merge(&self, new: Self) -> Self {
Self {
prefix: new.prefix.or_else(|| self.prefix.clone()),
fs: new.fs.or_else(|| self.fs.clone()),
ls: new.ls.or_else(|| self.ls.clone()),
allow: new.allow.or(self.allow),
principal: new.principal.or_else(|| self.principal.clone()),
hide_symlinks: new.hide_symlinks.or(self.hide_symlinks),
autoindex: new.autoindex.or(self.autoindex),
indexfile: new.indexfile.or_else(|| self.indexfile.clone()),
read_buf_size: new.read_buf_size.or(self.read_buf_size),
redirect: new.redirect.or(self.redirect),
}
}
}
pub(crate) struct DavInner<C> {
pub prefix: String,
pub fs: Box<dyn GuardedFileSystem<C>>,
pub ls: Option<Box<dyn DavLockSystem>>,
pub allow: Option<DavMethodSet>,
pub principal: Option<String>,
pub hide_symlinks: Option<bool>,
pub autoindex: Option<bool>,
pub indexfile: Option<String>,
pub read_buf_size: Option<usize>,
pub redirect: Option<bool>,
pub credentials: C,
}
impl<C: Clone + Send + Sync + 'static> DavHandler<C> {
pub fn new() -> Self {
Self {
config: Default::default(),
}
}
pub fn builder() -> DavConfig<C> {
DavConfig::new()
}
pub async fn handle_guarded<ReqBody, ReqData, ReqError>(
&self,
req: Request<ReqBody>,
principal: String,
credentials: C,
) -> Response<Body>
where
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
{
let mut inner = DavInner::new(self.config.as_ref().clone(), credentials);
inner.principal = Some(principal);
inner.handle(req).await
}
}
impl DavHandler {
pub async fn handle<ReqBody, ReqData, ReqError>(&self, req: Request<ReqBody>) -> Response<Body>
where
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
{
let inner = DavInner::new(self.config.as_ref().clone(), ());
inner.handle(req).await
}
pub async fn handle_with<ReqBody, ReqData, ReqError>(
&self,
config: DavConfig,
req: Request<ReqBody>,
) -> Response<Body>
where
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
{
let inner = DavInner::new(self.config.merge(config), ());
inner.handle(req).await
}
#[doc(hidden)]
pub async fn handle_stream<ReqBody, ReqData, ReqError>(
&self,
req: Request<ReqBody>,
) -> Response<Body>
where
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
ReqBody: Stream<Item = Result<ReqData, ReqError>>,
{
let req = {
let (parts, body) = req.into_parts();
Request::from_parts(parts, StreamBody::new(body))
};
let inner = DavInner::new(self.config.as_ref().clone(), ());
inner.handle(req).await
}
#[doc(hidden)]
pub async fn handle_stream_with<ReqBody, ReqData, ReqError>(
&self,
config: DavConfig,
req: Request<ReqBody>,
) -> Response<Body>
where
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
ReqBody: Stream<Item = Result<ReqData, ReqError>>,
{
let req = {
let (parts, body) = req.into_parts();
Request::from_parts(parts, StreamBody::new(body))
};
let inner = DavInner::new(self.config.merge(config), ());
inner.handle(req).await
}
}
impl<C> DavInner<C>
where
C: Clone + Send + Sync + 'static,
{
pub fn new(cfg: DavConfig<C>, credentials: C) -> Self {
let DavConfig {
prefix,
fs,
ls,
allow,
principal,
hide_symlinks,
autoindex,
indexfile,
read_buf_size,
redirect,
} = cfg;
Self {
prefix: prefix.unwrap_or_default(),
fs: fs.unwrap_or_else(|| VoidFs::<C>::new()),
ls,
allow,
principal,
hide_symlinks,
autoindex,
indexfile,
read_buf_size,
redirect,
credentials,
}
}
pub(crate) async fn has_parent<'a>(&'a self, path: &'a DavPath) -> bool {
let p = path.parent();
self.fs
.metadata(&p, &self.credentials)
.await
.map(|m| m.is_dir())
.unwrap_or(false)
}
pub(crate) fn path(&self, req: &Request<()>) -> DavPath {
DavPath::from_uri_and_prefix(req.uri(), &self.prefix).unwrap()
}
pub(crate) fn fixpath(
&self,
res: &mut Response<Body>,
path: &mut DavPath,
meta: Box<dyn DavMetaData>,
) -> Box<dyn DavMetaData> {
if meta.is_dir() && !path.is_collection() {
path.add_slash();
let newloc = path.with_prefix().as_url_string();
res.headers_mut()
.typed_insert(davheaders::ContentLocation(newloc));
}
meta
}
pub(crate) async fn read_request<ReqBody, ReqData, ReqError>(
&self,
body: ReqBody,
max_size: usize,
) -> DavResult<Vec<u8>>
where
ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
{
let mut data = Vec::new();
let mut body = pin!(body);
while let Some(res) = body.frame().await {
let mut data_frame = res.map_err(|_| {
DavError::IoError(io::Error::new(
io::ErrorKind::UnexpectedEof,
"UnexpectedEof",
))
})?;
let Some(buf) = data_frame.data_mut() else {
continue;
};
while buf.has_remaining() {
if data.len() + buf.remaining() > max_size {
return Err(StatusCode::PAYLOAD_TOO_LARGE.into());
}
let b = buf.chunk();
let l = b.len();
data.extend_from_slice(b);
buf.advance(l);
}
}
Ok(data)
}
async fn handle<ReqBody, ReqData, ReqError>(self, req: Request<ReqBody>) -> Response<Body>
where
ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
{
let is_ms = req
.headers()
.get("user-agent")
.and_then(|s| s.to_str().ok())
.map(|s| s.contains("Microsoft"))
.unwrap_or(false);
match self.handle2(req).await {
Ok(resp) => {
debug!("== END REQUEST result OK");
resp
}
Err(err) => {
debug!("== END REQUEST result {err:?}");
let mut resp = Response::builder();
if is_ms && err.statuscode() == StatusCode::NOT_FOUND {
resp = resp
.header("Cache-Control", "no-store, no-cache, must-revalidate")
.header("Progma", "no-cache")
.header("Expires", "0")
.header("Vary", "*");
}
resp = resp.header("Content-Length", "0").status(err.statuscode());
if err.must_close() {
resp = resp.header("connection", "close");
}
resp.body(Body::empty()).unwrap()
}
}
}
async fn handle2<ReqBody, ReqData, ReqError>(
mut self,
req: Request<ReqBody>,
) -> DavResult<Response<Body>>
where
ReqBody: HttpBody<Data = ReqData, Error = ReqError>,
ReqData: Buf + Send + 'static,
ReqError: StdError + Send + Sync + 'static,
{
let (req, body) = {
let (parts, body) = req.into_parts();
(Request::from_parts(parts, ()), body)
};
if log_enabled!(log::Level::Debug)
&& let Some(t) = req.headers().typed_get::<davheaders::XLitmus>()
{
debug!("X-Litmus: {t:?}");
}
let method = match dav_method(req.method()) {
Ok(m) => m,
Err(e) => {
debug!("refusing method {} request {}", req.method(), req.uri());
return Err(e);
}
};
if is_voidfs::<C>(&self.fs) {
match method {
DavMethod::Options => {
if self
.allow
.as_ref()
.map(|a| a.contains(DavMethod::Options))
.unwrap_or(true)
{
let mut a = DavMethodSet::none();
a.add(DavMethod::Options);
self.allow = Some(a);
}
}
_ => {
debug!("no filesystem: method not allowed on request {}", req.uri());
return Err(DavError::StatusClose(StatusCode::METHOD_NOT_ALLOWED));
}
}
}
if let Some(ref a) = self.allow
&& !a.contains(method)
{
debug!(
"method {} not allowed on request {}",
req.method(),
req.uri()
);
return Err(DavError::StatusClose(StatusCode::METHOD_NOT_ALLOWED));
}
let path = DavPath::from_uri_and_prefix(req.uri(), &self.prefix)?;
let (body_strm, body_data) = match method {
DavMethod::Put | DavMethod::Patch => (Some(body), Vec::new()),
_ => (None, self.read_request(body, 65536).await?),
};
match method {
DavMethod::Put
| DavMethod::Patch
| DavMethod::PropFind
| DavMethod::PropPatch
| DavMethod::Lock
| DavMethod::Report
| DavMethod::MkCalendar
| DavMethod::MkAddressbook => {}
_ => {
if !body_data.is_empty() {
return Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into());
}
}
}
debug!("== START REQUEST {method:?} {path}");
match method {
DavMethod::Options => self.handle_options(&req).await,
DavMethod::PropFind => self.handle_propfind(&req, &body_data).await,
DavMethod::PropPatch => self.handle_proppatch(&req, &body_data).await,
DavMethod::MkCol => self.handle_mkcol(&req).await,
DavMethod::Delete => self.handle_delete(&req).await,
DavMethod::Lock => self.handle_lock(&req, &body_data).await,
DavMethod::Unlock => self.handle_unlock(&req).await,
DavMethod::Head | DavMethod::Get => self.handle_get(&req).await,
DavMethod::Copy | DavMethod::Move => self.handle_copymove(&req, method).await,
DavMethod::Put | DavMethod::Patch => self.handle_put(&req, body_strm.unwrap()).await,
#[cfg(any(feature = "caldav", feature = "carddav"))]
DavMethod::Report => self.handle_report(&req, &body_data).await,
#[cfg(feature = "caldav")]
DavMethod::MkCalendar => self.handle_mkcalendar(&req, &body_data).await,
#[cfg(feature = "carddav")]
DavMethod::MkAddressbook => self.handle_mkaddressbook(&req, &body_data).await,
#[cfg(not(any(feature = "caldav", feature = "carddav")))]
DavMethod::Report => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
#[cfg(not(feature = "caldav"))]
DavMethod::MkCalendar => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
#[cfg(not(feature = "carddav"))]
DavMethod::MkAddressbook => Err(DavError::StatusClose(StatusCode::NOT_IMPLEMENTED)),
}
}
}