xhfs 0.5.1

Bruteforce your way into storing files anywhere across an arbitrary combination of arbitrary devices.
use crate::{interface::config::WebDavConfig, servers::webdav::adapter::XHFSAdapter};
use dav_server::{DavHandler, body::Body, memls::MemLs};
use eyre::Context;
use headers::{Authorization, HeaderMapExt, authorization::Basic};
use hyper::{Response, StatusCode, server::conn::http1, service::service_fn};
use hyper_util::rt::TokioIo;
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
use tokio::net::TcpListener;
use xhfs_core::xhfs::XHFS;

mod adapter;

pub async fn webdav_main(
    addr: String,
    port: u16,
    xhfs: Arc<XHFS>,
    read_only: bool,
    auth: Option<WebDavConfig>,
) -> eyre::Result<()> {
    println!("-------");
    println!("{}", xhfs.format_headers_report().await?);
    println!("-------");
    let addr = format!("{addr}:{port}").parse::<SocketAddr>()?;
    let xhfs_adapter = Box::new(XHFSAdapter {
        xhfs,
        read_only,
        chunk_size: 64 * 1024, // standard 64KB disk read windows
    });
    let dav_server = DavHandler::builder()
        .filesystem(xhfs_adapter)
        .locksystem(MemLs::new())
        .build_handler();

    let listener = TcpListener::bind(addr)
        .await
        .with_context(|| format!("Binding {addr}:{port}"))?;

    println!(
        "Listening for WebDAV requests at {addr}{}",
        match &auth {
            Some(cfg) => format!(" (user: {})", cfg.username),
            None => " (no auth)".to_string(),
        }
    );

    loop {
        let (stream, _) = listener.accept().await.unwrap();
        let dav_server = dav_server.clone();
        let auth = auth.clone();
        let io = TokioIo::new(stream);
        tokio::task::spawn(async move {
            if let Err(err) = http1::Builder::new()
                .serve_connection(
                    io,
                    service_fn({
                        move |req| {
                            let dav_server = dav_server.clone();
                            let auth = auth.clone();
                            async move {
                                match &auth {
                                    Some(cfg) => {
                                        if let Some(a) =
                                            req.headers().typed_get::<Authorization<Basic>>()
                                            && a.username() == cfg.username
                                            && a.password() == cfg.password
                                        {
                                            let creds = adapter::Creds {
                                                username: a.username().to_string(),
                                            };
                                            return Ok::<_, Infallible>(
                                                dav_server
                                                    .handle_guarded(
                                                        req,
                                                        creds.username.clone(),
                                                        creds,
                                                    )
                                                    .await,
                                            );
                                        }
                                        Ok::<_, Infallible>(
                                            Response::builder()
                                                .status(StatusCode::UNAUTHORIZED)
                                                .header("WWW-Authenticate", r#"Basic realm="xhfs""#)
                                                .body(Body::empty())
                                                .unwrap(),
                                        )
                                    }
                                    None => Ok::<_, Infallible>(
                                        dav_server
                                            .handle_guarded(
                                                req,
                                                "anonymous".to_string(),
                                                adapter::Creds {
                                                    username: "anonymous".to_string(),
                                                },
                                            )
                                            .await,
                                    ),
                                }
                            }
                        }
                    }),
                )
                .await
            {
                eprintln!("Error executing HTTP session processing context: {err:#}");
            }
        });
    }
}