Bound reads to their limit, treat 416 as end of file, and refuse to remove a non-empty directory
This commit is contained in:
+60
-29
@@ -9,7 +9,7 @@ use sdk::{
|
||||
GuestReader, GuestWriter, ListPage, Locator, Reader, Setting, WriteCondition,
|
||||
Writer,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
/// The PROPFIND body. Asking for named properties rather than allprop keeps
|
||||
/// replies small on servers that store a great many of them.
|
||||
@@ -131,6 +131,20 @@ fn content_type() -> host::Header {
|
||||
}
|
||||
}
|
||||
|
||||
/// Asks the server about one path, and its children when `depth` is 1.
|
||||
fn describe(
|
||||
path: &str,
|
||||
depth_value: &str,
|
||||
) -> Result<Vec<dav::Found>, ErrorCode> {
|
||||
let body = exchange(
|
||||
host::Method::Propfind,
|
||||
path,
|
||||
vec![depth(depth_value), content_type()],
|
||||
PROPS.as_bytes(),
|
||||
)?;
|
||||
dav::multistatus(&body)
|
||||
}
|
||||
|
||||
fn info_of(found: &dav::Found) -> FileInfo {
|
||||
FileInfo {
|
||||
kind: if found.collection {
|
||||
@@ -147,11 +161,17 @@ fn info_of(found: &dav::Found) -> FileInfo {
|
||||
struct DavReader {
|
||||
call: host::Exchange,
|
||||
revision: Option<String>,
|
||||
body: RefCell<dav::Body>,
|
||||
}
|
||||
|
||||
impl GuestReader for DavReader {
|
||||
fn pull(&self, limit: u32) -> Result<Vec<u8>, ErrorCode> {
|
||||
self.call.pull(limit.min(MAX_CHUNK)).map_err(transport)
|
||||
self
|
||||
.body
|
||||
.borrow_mut()
|
||||
.pull(limit.min(MAX_CHUNK) as usize, |wanted| {
|
||||
self.call.pull(wanted).map_err(transport)
|
||||
})
|
||||
}
|
||||
fn revision(&self) -> Option<String> {
|
||||
self.revision.clone()
|
||||
@@ -162,6 +182,7 @@ impl GuestReader for DavReader {
|
||||
struct DavWriter {
|
||||
call: host::Exchange,
|
||||
done: RefCell<bool>,
|
||||
written: Cell<u64>,
|
||||
}
|
||||
|
||||
impl GuestWriter for DavWriter {
|
||||
@@ -169,7 +190,9 @@ impl GuestWriter for DavWriter {
|
||||
if *self.done.borrow() {
|
||||
return Err(ErrorCode::InvalidInput);
|
||||
}
|
||||
self.call.push(&data).map_err(transport)
|
||||
self.call.push(&data).map_err(transport)?;
|
||||
self.written.set(self.written.get() + data.len() as u64);
|
||||
Ok(())
|
||||
}
|
||||
/// One call is enough: the request body is already complete, so the response
|
||||
/// head settles the write. The etag it returns, when the server sends one,
|
||||
@@ -188,7 +211,7 @@ impl GuestWriter for DavWriter {
|
||||
.map(|header| header.value.clone());
|
||||
Ok(Some(FileInfo {
|
||||
kind: EntryKind::File,
|
||||
size: 0,
|
||||
size: self.written.get(),
|
||||
revision,
|
||||
}))
|
||||
}
|
||||
@@ -240,14 +263,7 @@ impl Guest for Webdav {
|
||||
}
|
||||
|
||||
fn stat(target: Locator) -> Result<FileInfo, ErrorCode> {
|
||||
let path = route(&target);
|
||||
let body = exchange(
|
||||
host::Method::Propfind,
|
||||
&path,
|
||||
vec![depth("0"), content_type()],
|
||||
PROPS.as_bytes(),
|
||||
)?;
|
||||
let found = dav::multistatus(&body)?;
|
||||
let found = describe(&route(&target), "0")?;
|
||||
let entry = found.first().ok_or(ErrorCode::NotFound)?;
|
||||
Ok(info_of(entry))
|
||||
}
|
||||
@@ -264,17 +280,9 @@ impl Guest for Webdav {
|
||||
Some(value) => value.parse().map_err(|_| ErrorCode::InvalidInput)?,
|
||||
None => 0,
|
||||
};
|
||||
let body = exchange(
|
||||
host::Method::Propfind,
|
||||
&path,
|
||||
vec![depth("1"), content_type()],
|
||||
PROPS.as_bytes(),
|
||||
)?;
|
||||
let found = dav::multistatus(&body)?;
|
||||
// The first response is the collection itself, which is not its own child.
|
||||
let children: Vec<_> = found
|
||||
let found = describe(&path, "1")?;
|
||||
let children: Vec<_> = dav::children(&found)?
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|entry| Entry {
|
||||
name: dav::name_of(&entry.href),
|
||||
info: info_of(entry),
|
||||
@@ -288,8 +296,15 @@ impl Guest for Webdav {
|
||||
})
|
||||
}
|
||||
|
||||
/// A GET on a collection answers with whatever page the server likes to
|
||||
/// show, so the path is checked to be a file before any byte is read.
|
||||
fn open_read(target: Locator, offset: u64) -> Result<Reader, ErrorCode> {
|
||||
let path = route(&target);
|
||||
let found = describe(&path, "0")?;
|
||||
let entry = found.first().ok_or(ErrorCode::NotFound)?;
|
||||
if entry.collection {
|
||||
return Err(ErrorCode::InvalidInput);
|
||||
}
|
||||
let call = begin(
|
||||
host::Method::Get,
|
||||
&path,
|
||||
@@ -299,13 +314,25 @@ impl Guest for Webdav {
|
||||
}],
|
||||
)?;
|
||||
let head = call.respond().map_err(transport)?;
|
||||
dav::outcome(head.status)?;
|
||||
let revision = head
|
||||
.headers
|
||||
.iter()
|
||||
.find(|header| header.name.eq_ignore_ascii_case("etag"))
|
||||
.map(|header| header.value.clone());
|
||||
Ok(Reader::new(DavReader { call, revision }))
|
||||
let body = if dav::range_outcome(head.status)? {
|
||||
dav::Body::default()
|
||||
} else {
|
||||
dav::Body::empty()
|
||||
};
|
||||
// The listing's etag is the one stat and list report, and the strong form
|
||||
// a later conditional write must send; the GET's may be a weak variant.
|
||||
let revision = entry.etag.clone().or_else(|| {
|
||||
head
|
||||
.headers
|
||||
.iter()
|
||||
.find(|header| header.name.eq_ignore_ascii_case("etag"))
|
||||
.map(|header| header.value.clone())
|
||||
});
|
||||
Ok(Reader::new(DavReader {
|
||||
call,
|
||||
revision,
|
||||
body: RefCell::new(body),
|
||||
}))
|
||||
}
|
||||
|
||||
fn open_write(
|
||||
@@ -329,6 +356,7 @@ impl Guest for Webdav {
|
||||
Ok(Writer::new(DavWriter {
|
||||
call,
|
||||
done: RefCell::new(false),
|
||||
written: Cell::new(0),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -368,8 +396,11 @@ impl Guest for Webdav {
|
||||
Self::stat(Locator::Path(destination))
|
||||
}
|
||||
|
||||
/// DELETE takes a whole collection at once, so a directory is looked at
|
||||
/// first and refused unless it is empty.
|
||||
fn remove(target: Locator) -> Result<(), ErrorCode> {
|
||||
let path = route(&target);
|
||||
dav::removable(&describe(&path, "1")?)?;
|
||||
exchange(host::Method::Delete, &path, Vec::new(), &[])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user