v0.24.5
This commit is contained in:
commit
5e23e9af02
240 files changed
+99957
No files matched your search
@@ -0,0 +1,130 @@
|
||||
//! Listener-aware host-owned local responses for domains that intentionally do
|
||||
//! not proxy to an upstream. These hosts reserve their frontend names exactly
|
||||
//! like vhosts/redirects and can serve a built-in placeholder, empty status, or
|
||||
//! administrator-provided bounded HTML.
|
||||
use anyhow::{ensure, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn yes() -> bool { true }
|
||||
fn default_status() -> u16 { 404 }
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Host {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
#[serde(default = "yes")]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "yes")]
|
||||
pub http2_enabled: bool,
|
||||
#[serde(default = "yes")]
|
||||
pub http3_enabled: bool,
|
||||
pub frontend: crate::vhost::Frontend,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub certificate_id: Option<String>,
|
||||
#[serde(default = "default_page")]
|
||||
pub page: crate::pages::Page,
|
||||
}
|
||||
|
||||
fn default_page() -> crate::pages::Page {
|
||||
crate::pages::Page {
|
||||
kind: crate::pages::Kind::Welcome,
|
||||
status: Some(default_status()),
|
||||
html: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Host {
|
||||
pub fn validate(&mut self) -> Result<()> {
|
||||
ensure!(crate::config::valid_id(&self.id), "invalid 404 host id");
|
||||
if self.name.trim().is_empty() {
|
||||
self.name = self.id.clone();
|
||||
}
|
||||
self.name = self.name.trim().to_owned();
|
||||
ensure!(
|
||||
!self.name.is_empty() && self.name.len() <= 96 && !self.name.chars().any(char::is_control),
|
||||
"404 host name must be 1..96 bytes without controls"
|
||||
);
|
||||
ensure!(
|
||||
!self.frontend.hosts.is_empty(),
|
||||
"404 host requires at least one hostname"
|
||||
);
|
||||
let mut hosts = std::collections::HashSet::new();
|
||||
for host in &mut self.frontend.hosts {
|
||||
*host = crate::config::normalize_host_pattern(host)?;
|
||||
ensure!(hosts.insert(host.clone()), "duplicate 404 hostname");
|
||||
}
|
||||
ensure!(
|
||||
!self.frontend.listener_ids.is_empty() && self.frontend.listener_ids.len() <= 16,
|
||||
"404 host requires 1..16 named listeners"
|
||||
);
|
||||
if self.page.status.is_none() {
|
||||
self.page.status = Some(default_status());
|
||||
}
|
||||
self.page.validate_local_response()?;
|
||||
let status = self.page.status.unwrap_or(default_status());
|
||||
if matches!(self.page.kind, crate::pages::Kind::Builtin) {
|
||||
ensure!(
|
||||
crate::pages::BUILTIN_ERROR_CODES.contains(&status),
|
||||
"application-style 404 host status must use a built-in error template"
|
||||
);
|
||||
} else {
|
||||
ensure!(
|
||||
crate::pages::ERROR_CODES.contains(&status),
|
||||
"404 host status must use a supported error code"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompiledHost {
|
||||
pub id: String,
|
||||
pub bindings: Vec<crate::vhost::Listener>,
|
||||
pub http2_enabled: bool,
|
||||
pub http3_enabled: bool,
|
||||
pub page: crate::pages::Page,
|
||||
}
|
||||
|
||||
pub fn compile(host: &Host, listeners: &crate::settings::Listeners) -> Result<CompiledHost> {
|
||||
Ok(CompiledHost {
|
||||
id: host.id.clone(),
|
||||
bindings: host.frontend.resolved(listeners)?,
|
||||
http2_enabled: host.http2_enabled,
|
||||
http3_enabled: host.http3_enabled,
|
||||
page: host.page.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn host(kind: crate::pages::Kind, status: u16) -> Host {
|
||||
Host {
|
||||
id: "missing".into(),
|
||||
name: "Missing".into(),
|
||||
enabled: true,
|
||||
http2_enabled: true,
|
||||
http3_enabled: true,
|
||||
frontend: crate::vhost::Frontend { hosts: vec!["missing.example".into()], listener_ids: vec!["http".into()] },
|
||||
certificate_id: None,
|
||||
page: crate::pages::Page { kind, status: Some(status), html: if matches!(kind, crate::pages::Kind::Html) { "<h1>missing</h1>".into() } else { String::new() } },
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_hosts_accept_only_known_error_statuses() {
|
||||
assert!(host(crate::pages::Kind::Welcome, 404).validate().is_ok());
|
||||
assert!(host(crate::pages::Kind::Empty, 502).validate().is_ok());
|
||||
assert!(host(crate::pages::Kind::Html, 418).validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_style_hosts_require_a_bundled_template() {
|
||||
assert!(host(crate::pages::Kind::Builtin, 404).validate().is_ok());
|
||||
assert!(host(crate::pages::Kind::Builtin, 500).validate().is_err());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user