Files
markbase/markbase-core/src/webdav.rs
Warren dbca6e6d35
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled
Fix clippy warnings: unused imports, minor style fixes
2026-06-20 21:08:50 +08:00

310 lines
8.9 KiB
Rust

use crate::vfs::open_flags::OpenFlags;
use crate::vfs::{VfsBackend, VfsStat};
use crate::ssh_server::upload_hook::UploadHook;
use bytes::{Buf, Bytes};
use dav_server::davpath::DavPath;
use dav_server::fs::{
DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsStream, OpenOptions,
};
use dav_server::fakels::FakeLs;
use futures_util::stream;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::SystemTime;
pub struct VfsDavFs {
vfs: Box<dyn VfsBackend>,
root: PathBuf,
upload_hook: Option<Arc<UploadHook>>,
user_uuid: String,
}
impl Clone for VfsDavFs {
fn clone(&self) -> Self {
Self {
vfs: self.vfs.clone_boxed(),
root: self.root.clone(),
upload_hook: self.upload_hook.clone(),
user_uuid: self.user_uuid.clone(),
}
}
}
impl VfsDavFs {
pub fn new(
vfs: Box<dyn VfsBackend>,
root: PathBuf,
upload_hook: Option<Arc<UploadHook>>,
user_uuid: String,
) -> Box<Self> {
Box::new(Self {
vfs,
root,
upload_hook,
user_uuid,
})
}
fn resolve_path(&self, path: &DavPath) -> PathBuf {
let relative = path.as_pathbuf();
self.root.join(relative)
}
}
#[derive(Debug, Clone)]
pub struct VfsDavMetaData {
len: u64,
is_dir: bool,
modified: SystemTime,
}
impl VfsDavMetaData {
pub fn new(len: u64, is_dir: bool) -> Self {
Self {
len,
is_dir,
modified: SystemTime::now(),
}
}
pub fn from_stat(stat: &VfsStat) -> Self {
Self {
len: stat.size,
is_dir: stat.is_dir,
modified: stat.mtime,
}
}
}
impl DavMetaData for VfsDavMetaData {
fn len(&self) -> u64 {
self.len
}
fn modified(&self) -> Result<SystemTime, FsError> {
Ok(self.modified)
}
fn is_dir(&self) -> bool {
self.is_dir
}
}
#[derive(Debug, Clone)]
pub struct VfsDavDirEntry {
name: String,
meta: VfsDavMetaData,
}
impl VfsDavDirEntry {
pub fn new(name: String, meta: VfsDavMetaData) -> Self {
Self { name, meta }
}
}
impl DavDirEntry for VfsDavDirEntry {
fn name(&self) -> Vec<u8> {
self.name.as_bytes().to_vec()
}
fn metadata(&self) -> FsFuture<'_, Box<dyn DavMetaData>> {
Box::pin(std::future::ready(Ok(Box::new(self.meta.clone()) as Box<dyn DavMetaData>)))
}
}
pub struct VfsDavFile {
data: Vec<u8>,
position: u64,
path: Option<PathBuf>,
upload_hook: Option<Arc<UploadHook>>,
user_uuid: String,
is_write: bool,
}
impl std::fmt::Debug for VfsDavFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VfsDavFile")
.field("is_write", &self.is_write)
.field("path", &self.path)
.finish()
}
}
impl VfsDavFile {
pub fn new_read(data: Vec<u8>) -> Self {
Self {
data,
position: 0,
path: None,
upload_hook: None,
user_uuid: String::new(),
is_write: false,
}
}
pub fn new_write(
path: PathBuf,
upload_hook: Option<Arc<UploadHook>>,
user_uuid: String,
) -> Self {
Self {
data: Vec::new(),
position: 0,
path: Some(path),
upload_hook,
user_uuid,
is_write: true,
}
}
}
impl DavFile for VfsDavFile {
fn metadata(&'_ mut self) -> FsFuture<'_, Box<dyn DavMetaData>> {
let len = self.data.len() as u64;
Box::pin(std::future::ready(Ok(
Box::new(VfsDavMetaData::new(len, false)) as Box<dyn DavMetaData>,
)))
}
fn write_buf(&'_ mut self, mut buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()> {
while buf.has_remaining() {
let chunk = buf.chunk();
self.data.extend_from_slice(chunk);
buf.advance(chunk.len());
}
Box::pin(std::future::ready(Ok(())))
}
fn write_bytes(&'_ mut self, buf: Bytes) -> FsFuture<'_, ()> {
self.data.extend_from_slice(&buf);
Box::pin(std::future::ready(Ok(())))
}
fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, Bytes> {
let start = self.position as usize;
let end = std::cmp::min(start + count, self.data.len());
if start >= self.data.len() {
Box::pin(std::future::ready(Ok(Bytes::new())))
} else {
let bytes = Bytes::copy_from_slice(&self.data[start..end]);
self.position = end as u64;
Box::pin(std::future::ready(Ok(bytes)))
}
}
fn seek(&'_ mut self, pos: std::io::SeekFrom) -> FsFuture<'_, u64> {
let new_pos = match pos {
std::io::SeekFrom::Start(offset) => offset,
std::io::SeekFrom::Current(offset) => {
let current = self.position as i64;
(current + offset) as u64
}
std::io::SeekFrom::End(offset) => {
let end = self.data.len() as i64;
(end + offset) as u64
}
};
self.position = new_pos;
Box::pin(std::future::ready(Ok(new_pos)))
}
fn flush(&'_ mut self) -> FsFuture<'_, ()> {
if self.is_write {
if let Some(path) = &self.path {
if let Err(_e) = std::fs::write(path, &self.data) {
return Box::pin(std::future::ready(Err(FsError::NotImplemented)));
}
if let Some(hook) = &self.upload_hook {
if let Err(e) = hook.trigger(path, &self.user_uuid) {
log::warn!("Upload hook failed for {:?}: {}", path, e);
}
}
}
}
Box::pin(std::future::ready(Ok(())))
}
}
impl DavFileSystem for VfsDavFs {
fn open<'a>(&'a self, path: &'a DavPath, options: OpenOptions) -> FsFuture<'a, Box<dyn DavFile>> {
let full_path = self.resolve_path(path);
if options.write {
let file = VfsDavFile::new_write(
full_path,
self.upload_hook.clone(),
self.user_uuid.clone(),
);
Box::pin(std::future::ready(Ok(Box::new(file) as Box<dyn DavFile>)))
} else {
let flags = OpenFlags::new().read();
match self.vfs.open_file(&full_path, &flags) {
Ok(mut vfs_file) => {
let mut data = Vec::new();
loop {
let mut buf = vec![0u8; 8192];
match vfs_file.read(&mut buf) {
Ok(0) => break,
Ok(n) => data.extend_from_slice(&buf[..n]),
Err(_) => return Box::pin(std::future::ready(Err(FsError::NotFound))),
}
}
let file = VfsDavFile::new_read(data);
Box::pin(std::future::ready(Ok(Box::new(file) as Box<dyn DavFile>)))
}
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
}
}
}
fn read_dir<'a>(
&'a self,
path: &'a DavPath,
_meta: dav_server::fs::ReadDirMeta,
) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
let full_path = self.resolve_path(path);
match self.vfs.read_dir(&full_path) {
Ok(entries) => {
let results: Vec<Box<dyn DavDirEntry>> = entries
.into_iter()
.map(|e| {
let meta = VfsDavMetaData::from_stat(&e.stat);
Box::new(VfsDavDirEntry::new(e.name, meta)) as Box<dyn DavDirEntry>
})
.collect();
let stream = stream::iter(results.into_iter().map(Ok));
Box::pin(std::future::ready(Ok(Box::pin(stream) as FsStream<Box<dyn DavDirEntry>>)))
}
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
}
}
fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
let full_path = self.resolve_path(path);
match self.vfs.stat(&full_path) {
Ok(stat) => {
let meta = VfsDavMetaData::from_stat(&stat);
Box::pin(std::future::ready(Ok(Box::new(meta) as Box<dyn DavMetaData>)))
}
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
}
}
}
pub fn create_webdav_handler(
vfs: Box<dyn VfsBackend>,
root: PathBuf,
upload_hook: Option<Arc<UploadHook>>,
user_uuid: String,
) -> dav_server::DavHandler {
let dav_fs = VfsDavFs::new(vfs, root, upload_hook, user_uuid);
dav_server::DavHandler::builder()
.filesystem(dav_fs)
.locksystem(FakeLs::new())
.strip_prefix("/webdav")
.build_handler()
}