Skip to main content

rush_sync_server/server/
redirect.rs

1use crate::core::prelude::*;
2use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
3
4pub struct HttpRedirectServer {
5    port: u16,
6    target_port: u16,
7}
8
9impl HttpRedirectServer {
10    pub fn new(port: u16, target_port: u16) -> Self {
11        Self { port, target_port }
12    }
13
14    async fn redirect_handler(req: HttpRequest, target_port: web::Data<u16>) -> HttpResponse {
15        let path = req.uri().path();
16
17        // ACME challenges must be served directly (Let's Encrypt HTTP-01 validation)
18        if path.starts_with("/.well-known/acme-challenge/") {
19            let token = path
20                .strip_prefix("/.well-known/acme-challenge/")
21                .unwrap_or("");
22            if let Some(key_auth) = crate::server::acme::get_challenge_response(token) {
23                log::info!("ACME: Serving challenge on port 80 for token {}", token);
24                return HttpResponse::Ok()
25                    .content_type("text/plain")
26                    .body(key_auth);
27            }
28        }
29
30        let host = req
31            .headers()
32            .get("host")
33            .and_then(|v| v.to_str().ok())
34            .unwrap_or("localhost");
35
36        let host_clean = host.split(':').next().unwrap_or(host);
37        let query = req.uri().query().unwrap_or("");
38
39        let redirect_url = if *target_port.get_ref() == 443 {
40            format!("https://{}{}", host_clean, path)
41        } else {
42            format!("https://{}:{}{}", host_clean, target_port.get_ref(), path)
43        };
44
45        let final_url = if !query.is_empty() {
46            format!("{}?{}", redirect_url, query)
47        } else {
48            redirect_url
49        };
50
51        log::debug!("HTTP->HTTPS: {} -> {}", req.uri(), final_url);
52
53        HttpResponse::MovedPermanently()
54            .insert_header(("Location", final_url))
55            .insert_header(("Strict-Transport-Security", "max-age=31536000"))
56            .finish()
57    }
58
59    pub async fn run(self) -> Result<()> {
60        log::info!("HTTP redirect server starting on port {}", self.port);
61        log::info!("Redirecting to HTTPS port {}", self.target_port);
62
63        let target_port = self.target_port;
64
65        HttpServer::new(move || {
66            App::new()
67                .app_data(web::Data::new(target_port))
68                .default_service(web::route().to(Self::redirect_handler))
69        })
70        .bind(("0.0.0.0", self.port))
71        .map_err(|e| AppError::Validation(format!("Port {} bind failed: {}", self.port, e)))?
72        .run()
73        .await
74        .map_err(AppError::Io)
75    }
76}