Skip to main content

lanflare_core/server/
router.rs

1//! Hyper router dispatching incoming HTTP requests to LocalSend v2.2 and Dufs file server handlers.
2
3use std::net::IpAddr;
4use bytes::Bytes;
5use http_body_util::Full;
6use hyper::body::Incoming;
7use hyper::{Method, Request, Response, StatusCode};
8
9use crate::server::dufs::{full_to_box_body, handle_dufs_request, ServerResponseBody};
10use crate::server::handlers::{
11    empty_response, handle_cancel, handle_download, handle_info, handle_prepare_download,
12    handle_prepare_upload, handle_register, handle_upload, handle_web_portal, ServerState,
13};
14
15/// Client connection metadata extracted from the underlying TLS / TCP stream.
16#[derive(Clone, Debug)]
17pub struct ClientConnectionInfo {
18    /// Remote IP address.
19    pub remote_ip: IpAddr,
20    /// Client certificate SHA-256 fingerprint (if presented in mTLS).
21    pub cert_fingerprint: Option<String>,
22}
23
24fn to_box_body(res: Response<Full<Bytes>>) -> Response<ServerResponseBody> {
25    res.map(full_to_box_body)
26}
27
28/// Routes incoming HTTP requests to the appropriate handler according to LocalSend v2.2 and Dufs specifications.
29pub async fn route_request(
30    req: Request<Incoming>,
31    client: ClientConnectionInfo,
32    state: &ServerState,
33) -> Result<Response<ServerResponseBody>, hyper::Error> {
34    let method = req.method();
35    let path = req.uri().path();
36
37    // Check if Dufs folder sharing is active or if explicit /dufs path is requested
38    if let Some(dufs_cfg) = state.dufs_manager.get_config().await {
39        let is_dufs_explicit = path == "/dufs" || path.starts_with("/dufs/");
40        let is_localsend_api = path.starts_with("/api/localsend/");
41        let is_download_endpoint = path == "/download" || path == "/api/localsend/v2/download";
42
43        // Route to Dufs if explicit /dufs prefix, or if root/static file access while dufs is active
44        if is_dufs_explicit || (!is_localsend_api && !is_download_endpoint) {
45            let mut cfg = dufs_cfg;
46            if is_dufs_explicit {
47                cfg.path_prefix = "/dufs".to_string();
48            } else {
49                cfg.path_prefix = String::new();
50            }
51            return handle_dufs_request(req, client.remote_ip, &cfg).await;
52        }
53    }
54
55    let response = match (method, path) {
56        // Web portal for browser download (Share via link) when dufs is not active
57        (&Method::GET, "/") | (&Method::GET, "/index.html") => {
58            to_box_body(handle_web_portal(&req, state).await)
59        }
60
61        // v1/v2 Device Information: GET /api/localsend/v2/info or GET /api/localsend/v1/info
62        (&Method::GET, "/api/localsend/v2/info") | (&Method::GET, "/api/localsend/v1/info") => {
63            to_box_body(handle_info(client.remote_ip, state).await)
64        }
65
66        // Two-way discovery registration: POST /api/localsend/v2/register
67        (&Method::POST, "/api/localsend/v2/register") => {
68            to_box_body(handle_register(req, client.remote_ip, client.cert_fingerprint, state).await)
69        }
70
71        // Transfer session preparation: POST /api/localsend/v2/prepare-upload
72        (&Method::POST, "/api/localsend/v2/prepare-upload") => {
73            to_box_body(handle_prepare_upload(req, client.remote_ip, client.cert_fingerprint, state).await)
74        }
75
76        // Binary file upload stream: POST /api/localsend/v2/upload
77        (&Method::POST, "/api/localsend/v2/upload") => {
78            to_box_body(handle_upload(req, client.remote_ip, state).await)
79        }
80
81        // Reverse file transfer (Browser download metadata): POST /api/localsend/v2/prepare-download
82        (&Method::POST, "/api/localsend/v2/prepare-download") => {
83            to_box_body(handle_prepare_download(req, client.remote_ip, state).await)
84        }
85
86        // Reverse file transfer binary stream: GET /api/localsend/v2/download or GET /download
87        (&Method::GET, "/api/localsend/v2/download") | (&Method::GET, "/download") => {
88            to_box_body(handle_download(req, client.remote_ip, state).await)
89        }
90
91        // Transfer cancellation: POST /api/localsend/v2/cancel
92        (&Method::POST, "/api/localsend/v2/cancel") => {
93            to_box_body(handle_cancel(req, client.remote_ip, state).await)
94        }
95
96        // Fallback: 404 Not Found
97        _ => to_box_body(empty_response(StatusCode::NOT_FOUND)),
98    };
99
100    Ok(response)
101}