142 lines
4.2 KiB
Rust
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
|
|
}
|