diff --git a/src/service/http.rs b/src/service/http.rs index a96f9e8..bd7a570 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use std::{collections, future, net, sync}; -use crate::error::unexpected; +use crate::error::unexpected::{self, Mappable}; use crate::{domain, service, task_stack}; #[derive(Serialize)] @@ -157,6 +157,39 @@ impl Service { ) } + fn https_redirect(&self, domain: domain::Name, req: Request) -> Response { + let https_addr = self.config.http.https_addr.unwrap(); + + (|| { + let mut uri_parts = http::uri::Parts::default(); + uri_parts.scheme = Some(http::uri::Scheme::HTTPS); + uri_parts.authority = Some( + http::uri::Authority::from_maybe_shared(format!( + "{}:{}", + &domain, + https_addr.port() + )) + .or_unexpected_while("constructing authority")?, + ); + uri_parts.path_and_query = req.uri().path_and_query().cloned(); + + let uri: http::uri::Uri = uri_parts + .try_into() + .or_unexpected_while("constructing new URI")?; + + Response::builder() + .status(http::status::StatusCode::PERMANENT_REDIRECT) + .header("Location", uri.to_string()) + .body(Body::empty()) + .or_unexpected_while("building redirect") + })() + .unwrap_or_else(|err| { + self.internal_error( + format!("failed to redirect from {} to https: {}", req.uri(), err).as_str(), + ) + }) + } + fn render_page(&self, name: &str, data: T) -> Response where T: Serialize, @@ -504,51 +537,40 @@ impl Service { } } - // If HTTPS is enabled then only .well-known endpoints are allowed over HTTP (because they - // require it). Otherwise we redirect all HTTP requests to HTTPS. - if self.config.http.https_addr.is_some() && !req_is_https { - let https_addr = self.config.http.https_addr.unwrap(); - - let mut uri_parts = http::uri::Parts::default(); - uri_parts.scheme = Some(http::uri::Scheme::HTTPS); - uri_parts.authority = Some( - http::uri::Authority::from_maybe_shared(format!( - "{}:{}", - &domain, - https_addr.port() - )) - .unwrap(), - ); - uri_parts.path_and_query = req.uri().path_and_query().cloned(); - - let uri: http::uri::Uri = uri_parts.try_into().unwrap(); - - return Response::builder() - .status(http::status::StatusCode::PERMANENT_REDIRECT) - .header("Location", uri.to_string()) - .body(Body::empty()) - .unwrap(); - } + // We only allow HTTP requests when HTTPS is enabled in specific cases: + // - /.well-known urls + // - proxied domains with https_disabled set on them + // everything else must use https if possible. + let https_upgradable = self.config.http.https_addr.is_some() && !req_is_https; if let Some(config) = self.proxied_domains.get(&domain) { - if let Some(ref http_url) = config.http_url { - return service::http::proxy::serve_http_request( - http_url.original_url.as_str(), - &config.http_request_headers.0, - client_ip, - req, - req_is_https, - ) - .await - .unwrap_or_else(|e| { - self.internal_error( - format!("serving {domain} via proxy {}: {e}", http_url.original_url) - .as_str(), - ) - }); - } else { + if config.http_url.is_none() { return self.render_error_page(404, "Domain not found"); } + + let http_url = config.http_url.as_ref().unwrap(); + + if https_upgradable && !config.https_disabled { + return self.https_redirect(domain, req); + } + + return service::http::proxy::serve_http_request( + http_url.original_url.as_str(), + &config.http_request_headers.0, + client_ip, + req, + req_is_https, + ) + .await + .unwrap_or_else(|e| { + self.internal_error( + format!("serving {domain} via proxy {}: {e}", http_url.original_url).as_str(), + ) + }); + } + + if https_upgradable { + return self.https_redirect(domain, req); } if Some(&domain) == self.interface_domain.as_ref() {