use headers::HeaderMapExt;
use http::StatusCode as SC;
use http::{Request, Response};
use std::cmp;
use std::io::Cursor;
use std::time::Duration;
use xmltree::{self, Element};
use crate::body::Body;
use crate::conditional::{dav_if_match, if_match};
use crate::davheaders::{self, DavTimeout};
use crate::davpath::DavPath;
use crate::errors::*;
use crate::fs::{FsError, OpenOptions};
use crate::ls::*;
use crate::util::MemBuffer;
use crate::xmltree_ext::{self, ElementExt};
use crate::{DavInner, DavResult};
impl<C: Clone + Send + Sync + 'static> DavInner<C> {
pub(crate) async fn handle_lock(
&self,
req: &Request<()>,
xmldata: &[u8],
) -> DavResult<Response<Body>> {
let locksystem = match self.ls {
Some(ref ls) => ls,
None => return Err(SC::METHOD_NOT_ALLOWED.into()),
};
let mut res = Response::new(Body::empty());
let mut path = self.path(req);
let meta = match self.fs.metadata(&path, &self.credentials).await {
Ok(meta) => Some(self.fixpath(&mut res, &mut path, meta)),
Err(_) => None,
};
if xmldata.is_empty() {
let (_, tokens) =
dav_if_match(req, self.fs.as_ref(), &self.ls, &path, &self.credentials).await;
if tokens.len() != 1 {
return Err(SC::BAD_REQUEST.into());
}
if locksystem
.check(&path, self.principal.as_deref(), false, false, &tokens)
.await
.is_ok()
{
let timeout = get_timeout(req, true, false);
let lock = match locksystem.refresh(&path, &tokens[0], timeout).await {
Ok(lock) => lock,
Err(_) => return Err(SC::PRECONDITION_FAILED.into()),
};
let prop = build_lock_prop(&lock, true);
let mut emitter = xmltree_ext::emitter(MemBuffer::new())?;
prop.write_ev(&mut emitter)?;
let buffer = emitter.into_inner().take();
let ct = "application/xml; charset=utf-8".to_owned();
res.headers_mut().typed_insert(davheaders::ContentType(ct));
*res.body_mut() = Body::from(buffer);
return Ok(res);
} else {
return Err(SC::PRECONDITION_FAILED.into());
}
}
let deep = match req.headers().typed_get::<davheaders::Depth>() {
Some(davheaders::Depth::Infinity) | None => true,
Some(davheaders::Depth::Zero) => false,
_ => return Err(SC::BAD_REQUEST.into()),
};
if let Some(s) = if_match(
req,
meta.as_ref().map(|v| v.as_ref()),
self.fs.as_ref(),
&self.ls,
&path,
&self.credentials,
)
.await
{
return Err(s.into());
}
let mut oo = OpenOptions::write();
oo.create = true;
if req
.headers()
.typed_get::<davheaders::IfMatch>()
.is_some_and(|h| h.0 == davheaders::ETagList::Star)
{
oo.create = false;
}
if req
.headers()
.typed_get::<davheaders::IfNoneMatch>()
.is_some_and(|h| h.0 == davheaders::ETagList::Star)
{
oo.create_new = true;
}
let tree = xmltree::Element::parse2(Cursor::new(xmldata))?;
if tree.name != "lockinfo" {
return Err(DavError::XmlParseError);
}
let mut shared: Option<bool> = None;
let mut owner: Option<Element> = None;
let mut locktype = false;
for elem in tree.child_elems_iter() {
match elem.name.as_str() {
"lockscope" => {
let name = elem.child_elems_iter().map(|e| e.name.as_ref()).next();
match name {
Some("exclusive") => shared = Some(false),
Some("shared") => shared = Some(true),
_ => return Err(DavError::XmlParseError),
}
}
"locktype" => {
let name = elem.child_elems_iter().map(|e| e.name.as_ref()).next();
match name {
Some("write") => locktype = true,
_ => return Err(DavError::XmlParseError),
}
}
"owner" => {
let mut o = elem.clone();
o.prefix = Some("D".to_owned());
owner = Some(o);
}
_ => return Err(DavError::XmlParseError),
}
}
if shared.is_none() || !locktype {
return Err(DavError::XmlParseError);
};
let shared = shared.unwrap();
let timeout = get_timeout(req, false, shared);
let principal = self.principal.as_deref();
let lock = match locksystem
.lock(&path, principal, owner.as_ref(), timeout, shared, deep)
.await
{
Ok(lock) => lock,
Err(_) => return Err(SC::LOCKED.into()),
};
let create = oo.create;
let create_new = oo.create_new;
if meta.is_none() {
match self.fs.open(&path, oo, &self.credentials).await {
Ok(_) => {}
Err(FsError::NotFound) | Err(FsError::Exists) => {
let s = if !create || create_new {
SC::PRECONDITION_FAILED
} else {
SC::CONFLICT
};
let _ = locksystem.unlock(&path, &lock.token).await;
return Err(s.into());
}
Err(e) => {
let _ = locksystem.unlock(&path, &lock.token).await;
return Err(e.into());
}
};
}
let lt = format!("<{}>", lock.token);
let ct = "application/xml; charset=utf-8".to_owned();
res.headers_mut().typed_insert(davheaders::LockToken(lt));
res.headers_mut().typed_insert(davheaders::ContentType(ct));
if meta.is_none() {
*res.status_mut() = SC::CREATED;
} else {
*res.status_mut() = SC::OK;
}
let mut emitter = xmltree_ext::emitter(MemBuffer::new())?;
let prop = build_lock_prop(&lock, true);
prop.write_ev(&mut emitter)?;
let buffer = emitter.into_inner().take();
*res.body_mut() = Body::from(buffer);
Ok(res)
}
pub(crate) async fn handle_unlock(&self, req: &Request<()>) -> DavResult<Response<Body>> {
let locksystem = match self.ls {
Some(ref ls) => ls,
None => return Err(SC::METHOD_NOT_ALLOWED.into()),
};
let t = req
.headers()
.typed_get::<davheaders::LockToken>()
.ok_or(DavError::Status(SC::BAD_REQUEST))?;
let token = t.0.trim_matches(|c| c == '<' || c == '>');
let mut res = Response::new(Body::empty());
let mut path = self.path(req);
if let Ok(meta) = self.fs.metadata(&path, &self.credentials).await {
self.fixpath(&mut res, &mut path, meta);
}
match locksystem.unlock(&path, token).await {
Ok(_) => {
*res.status_mut() = SC::NO_CONTENT;
Ok(res)
}
Err(_) => Err(SC::CONFLICT.into()),
}
}
}
#[allow(clippy::borrowed_box)]
pub(crate) async fn list_lockdiscovery(
ls: Option<&Box<dyn DavLockSystem>>,
path: &DavPath,
) -> Element {
let mut elem = Element::new2("D:lockdiscovery");
let locksystem = match ls {
Some(ls) => ls,
None => return elem,
};
let locks = locksystem.discover(path).await;
for lock in &locks {
elem.push_element(build_lock_prop(lock, false));
}
elem
}
#[allow(clippy::borrowed_box)]
pub(crate) fn list_supportedlock(ls: Option<&Box<dyn DavLockSystem>>) -> Element {
let mut elem = Element::new2("D:supportedlock");
if ls.is_none() {
return elem;
}
let mut entry = Element::new2("D:lockentry");
let mut scope = Element::new2("D:lockscope");
let mut ltype = Element::new2("D:locktype");
scope.push_element(Element::new2("D:exclusive"));
ltype.push_element(Element::new2("D:write"));
entry.push_element(scope);
entry.push_element(ltype);
elem.push_element(entry);
let mut entry = Element::new2("D:lockentry");
let mut scope = Element::new2("D:lockscope");
let mut ltype = Element::new2("D:locktype");
scope.push_element(Element::new2("D:shared"));
ltype.push_element(Element::new2("D:write"));
entry.push_element(scope);
entry.push_element(ltype);
elem.push_element(entry);
elem
}
fn get_timeout(req: &Request<()>, refresh: bool, shared: bool) -> Option<Duration> {
let max_timeout = if shared {
Duration::new(86400, 0)
} else {
Duration::new(600, 0)
};
match req.headers().typed_get::<davheaders::Timeout>() {
Some(davheaders::Timeout(ref vec)) if !vec.is_empty() => match vec[0] {
DavTimeout::Infinite => {
if refresh {
None
} else {
Some(max_timeout)
}
}
DavTimeout::Seconds(n) => Some(cmp::min(max_timeout, Duration::new(n as u64, 0))),
},
_ => None,
}
}
fn build_lock_prop(lock: &DavLock, full: bool) -> Element {
let mut actlock = Element::new2("D:activelock");
let mut elem = Element::new2("D:lockscope");
elem.push_element(match lock.shared {
false => Element::new2("D:exclusive"),
true => Element::new2("D:shared"),
});
actlock.push_element(elem);
let mut elem = Element::new2("D:locktype");
elem.push_element(Element::new2("D:write"));
actlock.push_element(elem);
actlock.push_element(
Element::new2("D:depth").text(
match lock.deep {
false => "0",
true => "Infinity",
}
.to_string(),
),
);
actlock.push_element(Element::new2("D:timeout").text(match lock.timeout {
None => "Infinite".to_string(),
Some(d) => format!("Second-{}", d.as_secs()),
}));
let mut locktokenelem = Element::new2("D:locktoken");
locktokenelem.push_element(Element::new2("D:href").text(lock.token.clone()));
actlock.push_element(locktokenelem);
let mut lockroot = Element::new2("D:lockroot");
lockroot.push_element(Element::new2("D:href").text(lock.path.with_prefix().as_url_string()));
actlock.push_element(lockroot);
if let Some(ref o) = lock.owner {
actlock.push_element(*o.clone());
}
if !full {
return actlock;
}
let mut ldis = Element::new2("D:lockdiscovery");
ldis.push_element(actlock);
let mut prop = Element::new2("D:prop").ns("D", "DAV:");
prop.push_element(ldis);
prop
}