rush_sync_server/server/
redirect.rs1use 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 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}