|
|
|
@ -6,15 +6,39 @@ mod util; |
|
|
|
|
|
|
|
|
|
pub use config::*; |
|
|
|
|
|
|
|
|
|
use http::request::Parts; |
|
|
|
|
use hyper::{Body, Method, Request, Response}; |
|
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
|
|
|
|
|
|
use std::{future, net, sync}; |
|
|
|
|
use std::{future, io, net, sync}; |
|
|
|
|
|
|
|
|
|
use crate::error::unexpected::{self, Mappable}; |
|
|
|
|
use crate::{domain, service, task_stack}; |
|
|
|
|
|
|
|
|
|
use http_body_util::combinators::UnsyncBoxBody as BoxBody; |
|
|
|
|
use hyper::{ |
|
|
|
|
body::{Body, Incoming as RequestBody}, |
|
|
|
|
Method, |
|
|
|
|
}; |
|
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
|
|
|
|
|
|
type Request = hyper::Request<RequestBody>; |
|
|
|
|
pub type Response = hyper::Response<BoxBody<bytes::Bytes, io::Error>>; |
|
|
|
|
|
|
|
|
|
fn bytes_body(b: bytes::Bytes) -> impl Body<Data = bytes::Bytes, Error = io::Error> { |
|
|
|
|
use http_body_util::BodyExt; |
|
|
|
|
http_body_util::Full::new(b).map_err(io::Error::other) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn empty_body() -> impl Body<Data = bytes::Bytes, Error = io::Error> { |
|
|
|
|
use http_body_util::BodyExt; |
|
|
|
|
http_body_util::Empty::<bytes::Bytes>::new().map_err(io::Error::other) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn stream_body<S>(s: S) -> impl Body<Data = bytes::Bytes, Error = io::Error> |
|
|
|
|
where |
|
|
|
|
S: futures::stream::Stream<Item = io::Result<bytes::Bytes>>, |
|
|
|
|
{ |
|
|
|
|
use futures::stream::TryStreamExt; |
|
|
|
|
http_body_util::StreamBody::new(s.map_ok(http_body::Frame::data)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize)] |
|
|
|
|
struct BasePresenter<'a, T> { |
|
|
|
|
page_name: &'a str, |
|
|
|
@ -82,27 +106,18 @@ impl Service { |
|
|
|
|
self.config.http.https_addr.is_some() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn serve(&self, status_code: u16, path: &str, body: Body) -> Response<Body> { |
|
|
|
|
match Response::builder() |
|
|
|
|
fn serve<B>(&self, status_code: u16, path: &str, body: B) -> Response |
|
|
|
|
where |
|
|
|
|
B: Body<Data = bytes::Bytes, Error = io::Error> + Send + 'static, |
|
|
|
|
{ |
|
|
|
|
hyper::Response::builder() |
|
|
|
|
.status(status_code) |
|
|
|
|
.header("Content-Type", service::guess_mime(path)) |
|
|
|
|
.body(body) |
|
|
|
|
{ |
|
|
|
|
Ok(res) => res, |
|
|
|
|
Err(err) => { |
|
|
|
|
// if the status code was already a 500, don't try to render _another_ 500, it'll
|
|
|
|
|
// probably fail for the same reason. At this point something is incredibly wrong,
|
|
|
|
|
// just panic.
|
|
|
|
|
if status_code == 500 { |
|
|
|
|
panic!("failed to build {}: {}", path, err); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.internal_error(format!("failed to build {}: {}", path, err).as_str()) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.body(BoxBody::new(body)) |
|
|
|
|
.unwrap_or_else(|err| panic!("failed to build {}: {}", path, err)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn render<T>(&self, status_code: u16, name: &str, value: T) -> Response<Body> |
|
|
|
|
fn render<T>(&self, status_code: u16, name: &str, value: T) -> Response |
|
|
|
|
where |
|
|
|
|
T: Serialize, |
|
|
|
|
{ |
|
|
|
@ -113,11 +128,11 @@ impl Service { |
|
|
|
|
.. |
|
|
|
|
}) => return self.render_error_page(404, "Static asset not found"), |
|
|
|
|
Err(err) => { |
|
|
|
|
return self.render_error_page(500, format!("template error: {err}").as_str()) |
|
|
|
|
return self.render_error_page(500, format!("template error: {err}").as_str()); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.serve(status_code, name, rendered.into()) |
|
|
|
|
self.serve(status_code, name, bytes_body(rendered.into())) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn presenter_http_scheme(&self) -> &str { |
|
|
|
@ -127,7 +142,7 @@ impl Service { |
|
|
|
|
"http" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn render_error_page(&self, status_code: u16, e: &str) -> Response<Body> { |
|
|
|
|
fn render_error_page(&self, status_code: u16, e: &str) -> Response { |
|
|
|
|
#[derive(Serialize)] |
|
|
|
|
struct Response<'a> { |
|
|
|
|
error_msg: &'a str, |
|
|
|
@ -145,7 +160,7 @@ impl Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn internal_error(&self, e: &str) -> Response<Body> { |
|
|
|
|
fn internal_error(&self, e: &str) -> Response { |
|
|
|
|
log::error!("Internal error: {e}"); |
|
|
|
|
self.render_error_page( |
|
|
|
|
500, |
|
|
|
@ -153,20 +168,17 @@ impl Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn render_redirect(&self, status_code: u16, target_uri: &str) -> Response<Body> { |
|
|
|
|
Response::builder() |
|
|
|
|
fn render_redirect(&self, status_code: u16, target_uri: &str) -> Response { |
|
|
|
|
hyper::Response::builder() |
|
|
|
|
.status(status_code) |
|
|
|
|
.header("Location", target_uri.to_string()) |
|
|
|
|
.body(Body::empty()) |
|
|
|
|
.body(BoxBody::new(empty_body())) |
|
|
|
|
.unwrap_or_else(|err| { |
|
|
|
|
self.internal_error( |
|
|
|
|
format!("failed to render {status_code} redirect to {target_uri}: {err}",) |
|
|
|
|
.as_str(), |
|
|
|
|
) |
|
|
|
|
panic!("failed to render {status_code} redirect to {target_uri}: {err}",) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn https_redirect(&self, domain: domain::Name, req: Request<Body>) -> Response<Body> { |
|
|
|
|
fn https_redirect(&self, domain: domain::Name, req: Request) -> Response { |
|
|
|
|
let https_addr = self.config.http.https_addr.unwrap(); |
|
|
|
|
|
|
|
|
|
(|| { |
|
|
|
@ -198,7 +210,7 @@ impl Service { |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn render_page<T>(&self, name: &str, data: T) -> Response<Body> |
|
|
|
|
fn render_page<T>(&self, name: &str, data: T) -> Response |
|
|
|
|
where |
|
|
|
|
T: Serialize, |
|
|
|
|
{ |
|
|
|
@ -214,12 +226,12 @@ impl Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn serve_origin(&self, settings: domain::Settings, req: Request<Body>) -> Response<Body> { |
|
|
|
|
async fn serve_origin(&self, settings: domain::Settings, req: Request) -> Response { |
|
|
|
|
let path = service::append_index_to_path(req.uri().path(), "index.html"); |
|
|
|
|
|
|
|
|
|
use domain::manager::GetFileError; |
|
|
|
|
match self.domain_manager.get_file(&settings, &path).await { |
|
|
|
|
Ok(f) => self.serve(200, &path, Body::wrap_stream(f.into_stream())), |
|
|
|
|
Ok(f) => self.serve(200, &path, stream_body(f.into_stream())), |
|
|
|
|
Err(GetFileError::FileNotFound) => self.render_error_page(404, "File not found"), |
|
|
|
|
Err(GetFileError::Unavailable) => self.render_error_page(502, "Content unavailable"), |
|
|
|
|
Err(GetFileError::DescrNotSynced) => self.internal_error( |
|
|
|
@ -247,22 +259,23 @@ impl Service { |
|
|
|
|
|
|
|
|
|
async fn with_query_req<'a, F, In, Out>( |
|
|
|
|
&self, |
|
|
|
|
req: &'a Parts, |
|
|
|
|
body: Body, |
|
|
|
|
req: &'a hyper::http::request::Parts, |
|
|
|
|
body: RequestBody, |
|
|
|
|
f: F, |
|
|
|
|
) -> Response<Body> |
|
|
|
|
) -> Response |
|
|
|
|
where |
|
|
|
|
In: for<'d> Deserialize<'d>, |
|
|
|
|
F: FnOnce(In) -> Out, |
|
|
|
|
Out: future::Future<Output = Response<Body>>, |
|
|
|
|
Out: future::Future<Output = Response>, |
|
|
|
|
{ |
|
|
|
|
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(), |
|
|
|
|
use http_body_util::BodyExt; |
|
|
|
|
let body = match body.collect().await { |
|
|
|
|
Ok(res) => res.to_bytes(), |
|
|
|
|
Err(e) => { |
|
|
|
|
return self.internal_error(format!("failed to read body: {e}").as_str()) |
|
|
|
|
} |
|
|
|
@ -279,7 +292,7 @@ impl Service { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn domain(&self, args: DomainArgs) -> Response<Body> { |
|
|
|
|
fn domain(&self, args: DomainArgs) -> Response { |
|
|
|
|
#[derive(Serialize)] |
|
|
|
|
struct Data { |
|
|
|
|
domain: domain::Name, |
|
|
|
@ -311,7 +324,7 @@ impl Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn domain_init(&self, args: DomainInitArgs) -> Response<Body> { |
|
|
|
|
fn domain_init(&self, args: DomainInitArgs) -> Response { |
|
|
|
|
#[derive(Serialize)] |
|
|
|
|
struct Data<'a> { |
|
|
|
|
domain: domain::Name, |
|
|
|
@ -368,7 +381,7 @@ impl Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn domain_sync(&self, args: DomainSyncArgs) -> Response<Body> { |
|
|
|
|
async fn domain_sync(&self, args: DomainSyncArgs) -> Response { |
|
|
|
|
if args.passphrase != self.config.passphrase.as_str() { |
|
|
|
|
return self.render_error_page(401, "Incorrect passphrase"); |
|
|
|
|
} |
|
|
|
@ -451,7 +464,7 @@ impl Service { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn domains(&self) -> Response<Body> { |
|
|
|
|
fn domains(&self) -> Response { |
|
|
|
|
#[derive(Serialize)] |
|
|
|
|
struct Response { |
|
|
|
|
domains: Vec<String>, |
|
|
|
@ -473,7 +486,7 @@ impl Service { |
|
|
|
|
self.render_page("/domains.html", Response { domains }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn serve_interface(&self, req: Request<Body>) -> Response<Body> { |
|
|
|
|
async fn serve_interface(&self, req: Request) -> Response { |
|
|
|
|
let (req, body) = req.into_parts(); |
|
|
|
|
let path = req.uri.path(); |
|
|
|
|
|
|
|
|
@ -511,7 +524,7 @@ impl Service { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn domain_from_req(req: &Request<Body>) -> Option<domain::Name> { |
|
|
|
|
fn domain_from_req(req: &Request) -> Option<domain::Name> { |
|
|
|
|
let host_header = req |
|
|
|
|
.headers() |
|
|
|
|
.get("Host") |
|
|
|
@ -527,12 +540,18 @@ impl Service { |
|
|
|
|
async fn handle_request( |
|
|
|
|
&self, |
|
|
|
|
client_ip: net::IpAddr, |
|
|
|
|
req: Request<Body>, |
|
|
|
|
req: Request, |
|
|
|
|
req_is_https: bool, |
|
|
|
|
) -> Response<Body> { |
|
|
|
|
) -> Response { |
|
|
|
|
let domain = match Self::domain_from_req(&req) { |
|
|
|
|
Some(domain) => domain, |
|
|
|
|
None => return self.render_error_page(400, "Cannot serve page without domain"), |
|
|
|
|
Some(domain) => { |
|
|
|
|
log::debug!("[{client_ip}] Serving request to {domain}{}", req.uri()); |
|
|
|
|
domain |
|
|
|
|
} |
|
|
|
|
None => { |
|
|
|
|
log::debug!("[{client_ip}] Domain not found on request"); |
|
|
|
|
return self.render_error_page(400, "Cannot serve page without domain"); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
let method = req.method(); |
|
|
|
@ -545,7 +564,7 @@ impl Service { |
|
|
|
|
let token = path.trim_start_matches("/.well-known/acme-challenge/"); |
|
|
|
|
|
|
|
|
|
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) { |
|
|
|
|
return self.serve(200, "token.txt", key.into()); |
|
|
|
|
return self.serve(200, "token.txt", bytes_body(key.into())); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -555,7 +574,7 @@ impl Service { |
|
|
|
|
.domain_manager |
|
|
|
|
.get_domain_checker_challenge_token(&domain) |
|
|
|
|
{ |
|
|
|
|
Ok(Some(token)) => return self.serve(200, "token.txt", token.into()), |
|
|
|
|
Ok(Some(token)) => return self.serve(200, "token.txt", bytes_body(token.into())), |
|
|
|
|
Ok(None) => return self.render_error_page(404, "Token not found"), |
|
|
|
|
Err(e) => { |
|
|
|
|
return self.internal_error( |
|
|
|
@ -593,6 +612,12 @@ impl Service { |
|
|
|
|
req_is_https, |
|
|
|
|
) |
|
|
|
|
.await |
|
|
|
|
.map(|res| { |
|
|
|
|
res.map(|body| { |
|
|
|
|
use http_body_util::BodyExt; |
|
|
|
|
body.map_err(io::Error::other).boxed_unsync() |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
.unwrap_or_else(|e| { |
|
|
|
|
self.internal_error( |
|
|
|
|
format!("serving {domain} via proxy {}: {e}", http_url.original_url) |
|
|
|
@ -630,3 +655,32 @@ fn strip_port(host: &str) -> &str { |
|
|
|
|
Some(i) => &host[..i], |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct HyperServiceImpl { |
|
|
|
|
service: sync::Arc<Service>, |
|
|
|
|
client_ip: net::IpAddr, |
|
|
|
|
is_https: bool, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl HyperServiceImpl { |
|
|
|
|
pub fn new(service: sync::Arc<Service>, client_ip: net::IpAddr, is_https: bool) -> Self { |
|
|
|
|
Self { |
|
|
|
|
service, |
|
|
|
|
client_ip, |
|
|
|
|
is_https, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl hyper::service::Service<Request> for HyperServiceImpl { |
|
|
|
|
type Response = Response; |
|
|
|
|
type Error = std::io::Error; |
|
|
|
|
type Future = crate::util::BoxFuture<'static, Result<Self::Response, Self::Error>>; |
|
|
|
|
|
|
|
|
|
fn call(&self, req: Request) -> Self::Future { |
|
|
|
|
let service = self.service.clone(); |
|
|
|
|
let client_ip = self.client_ip; |
|
|
|
|
let is_https = self.is_https; |
|
|
|
|
Box::pin(async move { Ok(service.handle_request(client_ip, req, is_https).await) }) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|