use std::error::Error as StdError;
use std::io;
use std::sync::Arc;
use bytes::{self, buf::Buf};
use futures::stream::Stream;
use headers::HeaderMapExt;
use http::{Request, Response, StatusCode};
use http_body::Body as HttpBody;
use crate::body::{Body, StreamBody};
use crate::davheaders;
use crate::davpath::DavPath;
use crate::util::{dav_method, DavMethod, DavMethodSet};
use crate::errors::DavError;
use crate::fs::*;
use crate::ls::*;
use crate::voidfs::{is_voidfs, VoidFs};
use crate::DavResult;
#[derive(Clone)]
pub struct DavHandler {
pub(crate) config: Arc<DavConfig>,
}
#[derive(Default)]
pub struct DavConfig {
pub(crate) prefix: Option<String>,
pub(crate) fs: Option<Box<dyn DavFileSystem>>,
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>,
}
impl DavConfig {
pub fn new() -> DavConfig {
DavConfig::default()
}
pub fn build_handler(self) -> DavHandler {
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 DavFileSystem>) -> 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
}
fn merge(&self, new: DavConfig) -> DavConfig {
DavConfig {
prefix: new.prefix.or(self.prefix.clone()),
fs: new.fs.or(self.fs.clone()),
ls: new.ls.or(self.ls.clone()),
allow: new.allow.or(self.allow.clone()),
principal: new.principal.or(self.principal.clone()),
hide_symlinks: new.hide_symlinks.or(self.hide_symlinks.clone()),
autoindex: new.autoindex.or(self.autoindex.clone()),
indexfile: new.indexfile.or(self.indexfile.clone()),
}
}
}
pub(crate) struct DavInner {
pub prefix: String,
pub fs: Box<dyn DavFileSystem>,
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>,
}
impl From<DavConfig> for DavInner {
fn from(cfg: DavConfig) -> Self {
DavInner {
prefix: cfg.prefix.unwrap_or("".to_string()),
fs: cfg.fs.unwrap_or(VoidFs::new()),
ls: cfg.ls,
allow: cfg.allow,
principal: cfg.principal,
hide_symlinks: cfg.hide_symlinks,
autoindex: cfg.autoindex,
indexfile: cfg.indexfile,
}
}
}
impl From<&DavConfig> for DavInner {
fn from(cfg: &DavConfig) -> Self {
DavInner {
prefix: cfg
.prefix
.as_ref()
.map(|p| p.to_owned())
.unwrap_or("".to_string()),
fs: cfg.fs.clone().unwrap(),
ls: cfg.ls.clone(),
allow: cfg.allow,
principal: cfg.principal.clone(),
hide_symlinks: cfg.hide_symlinks.clone(),
autoindex: cfg.autoindex.clone(),
indexfile: cfg.indexfile.clone(),
}
}
}
impl Clone for DavInner {
fn clone(&self) -> Self {
DavInner {
prefix: self.prefix.clone(),
fs: self.fs.clone(),
ls: self.ls.clone(),
allow: self.allow.clone(),
principal: self.principal.clone(),
hide_symlinks: self.hide_symlinks.clone(),
autoindex: self.autoindex.clone(),
indexfile: self.indexfile.clone(),
}
}
}
impl DavHandler {
pub fn new() -> DavHandler {
DavHandler {
config: Arc::new(DavConfig::default()),
}
}
pub fn builder() -> DavConfig {
DavConfig::new()
}
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::from(&*self.config);
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::from(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::from(&*self.config);
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::from(self.config.merge(config));
inner.handle(req).await
}
}
impl DavInner {
pub(crate) async fn has_parent<'a>(&'a self, path: &'a DavPath) -> bool {
let p = path.parent();
self.fs.metadata(&p).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<'a, ReqBody, ReqData, ReqError>(
&'a 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();
pin_utils::pin_mut!(body);
while let Some(res) = body.data().await {
let mut buf = res.map_err(|_| {
DavError::IoError(io::Error::new(io::ErrorKind::UnexpectedEof, "UnexpectedEof"))
})?;
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) {
if 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(&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 {
if !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 => {},
_ => {
if body_data.len() > 0 {
return Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into());
}
},
}
debug!("== START REQUEST {:?} {}", method, path);
let res = 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,
};
res
}
}