|
|
|
@ -4,6 +4,7 @@ mod tpl; |
|
|
|
|
|
|
|
|
|
pub use config::*; |
|
|
|
|
|
|
|
|
|
use http::request::Parts; |
|
|
|
|
use hyper::{Body, Method, Request, Response}; |
|
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
|
|
|
|
|
@ -48,6 +49,7 @@ pub fn new( |
|
|
|
|
#[derive(Serialize)] |
|
|
|
|
struct BasePresenter<'a, T> { |
|
|
|
|
page_name: &'a str, |
|
|
|
|
form_method: &'a str, |
|
|
|
|
data: T, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -59,12 +61,18 @@ struct DomainGetArgs { |
|
|
|
|
#[derive(Deserialize)] |
|
|
|
|
struct DomainInitArgs { |
|
|
|
|
domain: domain::Name, |
|
|
|
|
|
|
|
|
|
#[serde(flatten)] |
|
|
|
|
domain_config: service::util::FlatConfig, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Deserialize)] |
|
|
|
|
struct DomainSyncArgs { |
|
|
|
|
domain: domain::Name, |
|
|
|
|
passphrase: String, |
|
|
|
|
|
|
|
|
|
#[serde(flatten)] |
|
|
|
|
domain_config: service::util::FlatConfig, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl<'svc> Service { |
|
|
|
@ -121,6 +129,7 @@ impl<'svc> Service { |
|
|
|
|
"/base.html", |
|
|
|
|
BasePresenter { |
|
|
|
|
page_name: "/error.html", |
|
|
|
|
form_method: self.config.http.form_method.as_str(), |
|
|
|
|
data: &Response { error_msg: e }, |
|
|
|
|
}, |
|
|
|
|
) |
|
|
|
@ -143,6 +152,7 @@ impl<'svc> Service { |
|
|
|
|
"/base.html", |
|
|
|
|
BasePresenter { |
|
|
|
|
page_name: name, |
|
|
|
|
form_method: self.config.http.form_method.as_str(), |
|
|
|
|
data, |
|
|
|
|
}, |
|
|
|
|
) |
|
|
|
@ -174,14 +184,33 @@ impl<'svc> Service { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn with_query_req<'a, F, In, Out>(&self, req: &'a Request<Body>, f: F) -> Response<Body> |
|
|
|
|
async fn with_query_req<'a, F, In, Out>( |
|
|
|
|
&self, |
|
|
|
|
req: &'a Parts, |
|
|
|
|
body: Body, |
|
|
|
|
f: F, |
|
|
|
|
) -> Response<Body> |
|
|
|
|
where |
|
|
|
|
In: Deserialize<'a>, |
|
|
|
|
In: for<'d> Deserialize<'d>, |
|
|
|
|
F: FnOnce(In) -> Out, |
|
|
|
|
Out: future::Future<Output = Response<Body>>, |
|
|
|
|
{ |
|
|
|
|
let query = req.uri().query().unwrap_or(""); |
|
|
|
|
match serde_urlencoded::from_str::<In>(query) { |
|
|
|
|
let res = match self.config.http.form_method { |
|
|
|
|
ConfigFormMethod::GET => { |
|
|
|
|
serde_urlencoded::from_str::<In>(req.uri.query().unwrap_or("")) |
|
|
|
|
} |
|
|
|
|
ConfigFormMethod::POST => { |
|
|
|
|
let body = match hyper::body::to_bytes(body).await { |
|
|
|
|
Ok(bytes) => bytes.to_vec(), |
|
|
|
|
Err(e) => { |
|
|
|
|
return self.internal_error(format!("failed to read body: {e}").as_str()) |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
serde_urlencoded::from_bytes::<In>(body.as_ref()) |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
match res { |
|
|
|
|
Ok(args) => f(args).await, |
|
|
|
|
Err(err) => { |
|
|
|
|
self.render_error_page(400, format!("failed to parse query args: {}", err).as_str()) |
|
|
|
@ -219,11 +248,7 @@ impl<'svc> Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn domain_init( |
|
|
|
|
&self, |
|
|
|
|
args: DomainInitArgs, |
|
|
|
|
domain_config: service::util::FlatConfig, |
|
|
|
|
) -> Response<Body> { |
|
|
|
|
fn domain_init(&self, args: DomainInitArgs) -> Response<Body> { |
|
|
|
|
#[derive(Serialize)] |
|
|
|
|
struct Response<'a> { |
|
|
|
|
domain: domain::Name, |
|
|
|
@ -235,7 +260,7 @@ impl<'svc> Service { |
|
|
|
|
dns_records_have_cname: bool, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let config: domain::Domain = match domain_config.try_into() { |
|
|
|
|
let config: domain::Domain = match args.domain_config.try_into() { |
|
|
|
|
Ok(Some(config)) => config, |
|
|
|
|
Ok(None) => return self.render_error_page(400, "domain config is required"), |
|
|
|
|
Err(e) => { |
|
|
|
@ -272,16 +297,12 @@ impl<'svc> Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn domain_sync( |
|
|
|
|
&self, |
|
|
|
|
args: DomainSyncArgs, |
|
|
|
|
domain_config: service::util::FlatConfig, |
|
|
|
|
) -> Response<Body> { |
|
|
|
|
async fn domain_sync(&self, args: DomainSyncArgs) -> Response<Body> { |
|
|
|
|
if args.passphrase != self.config.passphrase.as_str() { |
|
|
|
|
return self.render_error_page(401, "Incorrect passphrase"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let config: domain::Domain = match domain_config.try_into() { |
|
|
|
|
let config: domain::Domain = match args.domain_config.try_into() { |
|
|
|
|
Ok(Some(config)) => config, |
|
|
|
|
Ok(None) => return self.render_error_page(400, "domain config is required"), |
|
|
|
|
Err(e) => { |
|
|
|
@ -341,12 +362,14 @@ impl<'svc> Service { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn handle_request(&self, req: Request<Body>) -> Response<Body> { |
|
|
|
|
let (req, body) = req.into_parts(); |
|
|
|
|
|
|
|
|
|
let maybe_host = match ( |
|
|
|
|
req.headers() |
|
|
|
|
req.headers |
|
|
|
|
.get("Host") |
|
|
|
|
.and_then(|v| v.to_str().ok()) |
|
|
|
|
.map(strip_port), |
|
|
|
|
req.uri().host().map(strip_port), |
|
|
|
|
req.uri.host().map(strip_port), |
|
|
|
|
) { |
|
|
|
|
(Some(h), _) if h != self.config.primary_domain.as_str() => Some(h), |
|
|
|
|
(_, Some(h)) if h != self.config.primary_domain.as_str() => Some(h), |
|
|
|
@ -354,13 +377,12 @@ impl<'svc> Service { |
|
|
|
|
} |
|
|
|
|
.and_then(|h| domain::Name::from_str(h).ok()); |
|
|
|
|
|
|
|
|
|
let method = req.method(); |
|
|
|
|
let path = req.uri().path(); |
|
|
|
|
let path = req.uri.path(); |
|
|
|
|
|
|
|
|
|
// Serving acme challenges always takes priority. We serve them from the same store no
|
|
|
|
|
// matter the domain, presumably they are cryptographically random enough that it doesn't
|
|
|
|
|
// matter.
|
|
|
|
|
if method == Method::GET && path.starts_with("/.well-known/acme-challenge/") { |
|
|
|
|
if req.method == Method::GET && path.starts_with("/.well-known/acme-challenge/") { |
|
|
|
|
let token = path.trim_start_matches("/.well-known/acme-challenge/"); |
|
|
|
|
|
|
|
|
|
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) { |
|
|
|
@ -369,7 +391,7 @@ impl<'svc> Service { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Serving domani challenges similarly takes priority.
|
|
|
|
|
if method == Method::GET && path == "/.well-known/domani-challenge" { |
|
|
|
|
if req.method == Method::GET && path == "/.well-known/domani-challenge" { |
|
|
|
|
if let Some(ref domain) = maybe_host { |
|
|
|
|
match self |
|
|
|
|
.domain_manager |
|
|
|
@ -388,38 +410,36 @@ impl<'svc> Service { |
|
|
|
|
|
|
|
|
|
// If a managed domain was given then serve that from its origin
|
|
|
|
|
if let Some(domain) = maybe_host { |
|
|
|
|
return self.serve_origin(domain, req.uri().path()); |
|
|
|
|
return self.serve_origin(domain, req.uri.path()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Serve main domani site
|
|
|
|
|
|
|
|
|
|
if method == Method::GET && path.starts_with("/static/") { |
|
|
|
|
if req.method == Method::GET && path.starts_with("/static/") { |
|
|
|
|
return self.render(200, path, ()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
match (method, path) { |
|
|
|
|
let config_form_method = self.config.http.form_method.as_ref(); |
|
|
|
|
|
|
|
|
|
match (&req.method, path) { |
|
|
|
|
(&Method::GET, "/") | (&Method::GET, "/index.html") => { |
|
|
|
|
self.render_page("/index.html", ()) |
|
|
|
|
} |
|
|
|
|
(&Method::GET, "/domain.html") => { |
|
|
|
|
self.with_query_req(&req, |args: DomainGetArgs| async { self.domain_get(args) }) |
|
|
|
|
.await |
|
|
|
|
(form_method, "/domain.html") if form_method == config_form_method => { |
|
|
|
|
self.with_query_req(&req, body, |args: DomainGetArgs| async { |
|
|
|
|
self.domain_get(args) |
|
|
|
|
}) |
|
|
|
|
.await |
|
|
|
|
} |
|
|
|
|
(&Method::GET, "/domain_init.html") => { |
|
|
|
|
self.with_query_req(&req, |args: DomainInitArgs| async { |
|
|
|
|
self.with_query_req(&req, |config: service::util::FlatConfig| async { |
|
|
|
|
self.domain_init(args, config) |
|
|
|
|
}) |
|
|
|
|
.await |
|
|
|
|
(form_method, "/domain_init.html") if form_method == config_form_method => { |
|
|
|
|
self.with_query_req(&req, body, |args: DomainInitArgs| async { |
|
|
|
|
self.domain_init(args) |
|
|
|
|
}) |
|
|
|
|
.await |
|
|
|
|
} |
|
|
|
|
(&Method::GET, "/domain_sync.html") => { |
|
|
|
|
self.with_query_req(&req, |args: DomainSyncArgs| async { |
|
|
|
|
self.with_query_req(&req, |config: service::util::FlatConfig| async { |
|
|
|
|
self.domain_sync(args, config).await |
|
|
|
|
}) |
|
|
|
|
.await |
|
|
|
|
(form_method, "/domain_sync.html") if form_method == config_form_method => { |
|
|
|
|
self.with_query_req(&req, body, |args: DomainSyncArgs| async { |
|
|
|
|
self.domain_sync(args).await |
|
|
|
|
}) |
|
|
|
|
.await |
|
|
|
|
} |
|
|
|
|