Files
markbase/markbase-core/src/cli/interface/webdav.rs
Warren cf57d46ca5
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled
Fix WebDAV: handle_dav extract WebdavCredentials Extension
2026-06-22 07:22:01 +08:00

142 lines
4.2 KiB
Rust

use axum::{
body::Body,
extract::Request,
http::{HeaderValue, StatusCode},
middleware,
response::IntoResponse,
Extension,
};
use base64::Engine as _;
use clap::Subcommand;
use dav_server::DavHandler;
use std::path::PathBuf;
#[derive(Subcommand)]
pub enum WebdavCommand {
#[command(name = "webdav-start")]
Start {
#[arg(short, long, default_value = "8002")]
port: u16,
#[arg(short, long)]
user: String,
},
}
pub async fn handle_webdav_command(cmd: WebdavCommand) -> anyhow::Result<()> {
match cmd {
WebdavCommand::Start { port, user } => {
// Parse username and optional password (format: "name:password")
let username = user.split(':').next().unwrap_or(&user).to_string();
let password = user.split(':').nth(1).map(|s| s.to_string());
let default_root = format!("/Users/accusys/momentry/var/sftpgo/data/{}", username);
let home_dir = PathBuf::from(
std::env::var("MB_WEBDAV_ROOT").unwrap_or(default_root),
);
if !home_dir.exists() {
return Err(anyhow::anyhow!(
"User home directory not found: {}",
home_dir.display()
));
}
println!("=== MarkBase WebDAV Server (VFS) ===");
println!("User: {}", username);
if password.is_some() {
println!("Auth: password protected");
}
println!("Port: {}", port);
println!("Home: {}", home_dir.display());
println!();
run_webdav_server(port, home_dir, username, password).await?;
}
}
Ok(())
}
async fn run_webdav_server(
port: u16,
home_dir: PathBuf,
user: String,
password: Option<String>,
) -> anyhow::Result<()> {
use axum::{routing::any, Router};
use tokio::net::TcpListener;
let vfs = Box::new(crate::vfs::local_fs::LocalFs::new());
let upload_hook = None;
let dav_handler = crate::webdav::create_webdav_handler(vfs, home_dir.clone(), upload_hook, user.clone());
async fn webdav_auth_middleware(
Extension(expected): Extension<crate::webdav::WebdavCredentials>,
req: Request,
next: middleware::Next,
) -> impl IntoResponse {
let auth = req
.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok())
.filter(|v| v.starts_with("Basic "))
.and_then(|v| {
let encoded = &v[6..];
let decoded = base64::engine::general_purpose::STANDARD
.decode(encoded)
.ok()?;
let creds = String::from_utf8(decoded).ok()?;
let colon = creds.find(':')?;
Some((creds[..colon].to_string(), creds[colon + 1..].to_string()))
});
let valid = auth.is_some_and(|(u, p)| {
u == expected.username && expected.password.as_ref().is_none_or(|exp| p == *exp)
});
if !valid {
return (
StatusCode::UNAUTHORIZED,
[(
"WWW-Authenticate",
HeaderValue::from_static("Basic realm=\"MarkBase WebDAV\""),
)],
Body::from("Unauthorized"),
).into_response();
}
next.run(req).await
}
let app = Router::new()
.route("/", any(handle_dav))
.route("/*path", any(handle_dav))
.layer(Extension(crate::webdav::WebdavCredentials {
username: user.clone(),
password,
}))
.layer(Extension(dav_handler))
.layer(middleware::from_fn(webdav_auth_middleware));
let addr = format!("0.0.0.0:{}", port);
let listener = TcpListener::bind(&addr).await?;
println!("WebDAV server listening on http://{}", addr);
println!("Root: {}", home_dir.display());
println!("User: {}", user);
println!();
println!("Press Ctrl+C to stop");
axum::serve(listener, app).await?;
Ok(())
}
async fn handle_dav(
Extension(dav): Extension<DavHandler>,
Extension(_creds): Extension<crate::webdav::WebdavCredentials>,
req: Request,
) -> impl IntoResponse {
dav.handle(req).await
}