This commit is contained in:
Mateusz Gruszczyński committed 2026-10-08 15:59:37 +02:00
1 parent 65e082b9d1
commit 76cb4acef0
72 files changed
+6134 -4709

No files matched your search

+76 -1
View File
@@ -15,6 +15,7 @@ impl Default for PathRoute {
headers: HeaderRules::default(),
rewrite: PathRewrite::default(),
response: None,
redirect: None,
}
}
}
@@ -40,6 +41,7 @@ impl PathRoute {
&& self.rewrite.mode == RewriteMode::Preserve
&& self.rewrite.value.is_empty()
&& self.response.is_none()
&& self.redirect.is_none()
}
}
impl Vhost {
@@ -71,6 +73,22 @@ impl Vhost {
*host = crate::config::normalize_host_pattern(host)?;
ensure!(hosts.insert(host.clone()), "duplicate vhost hostname");
}
if let Some(target) = &mut self.www_redirect_target {
*target = crate::config::normalize_host_pattern(target)?;
ensure!(
!target.starts_with("*.") && target.parse::<std::net::IpAddr>().is_err(),
"www redirect target must be an exact DNS hostname"
);
let partner = www_partner(target)?;
ensure!(
self.frontend.hosts.contains(target) || self.frontend.hosts.contains(&partner),
"www redirect target or its www/non-www partner must belong to this vhost"
);
}
if self.www_redirect {
let _ = self.www_redirect_pair()?;
let _ = self.routing_hosts()?;
}
if let Some(id) = &self.acl_profile_id {
ensure!(crate::config::valid_id(id), "invalid ACL profile id");
}
@@ -86,7 +104,10 @@ impl Vhost {
self.source.validate()?;
let needs_proxy = self.source.is_reverse_proxy()
|| self.routes.iter().any(|route| {
route.enabled && route.response.is_none() && route.source.is_reverse_proxy()
route.enabled
&& route.response.is_none()
&& route.redirect.is_none()
&& route.source.is_reverse_proxy()
});
ensure!(
self.backends.len() <= 32,
@@ -121,6 +142,10 @@ impl Vhost {
self.routes.retain(|route| !route.is_legacy_main_fallback());
for route in &mut self.routes {
route.source.validate()?;
ensure!(
route.response.is_none() || route.redirect.is_none(),
"location cannot define both a local response and a redirect"
);
if let Some(id) = &route.acl_profile_id {
ensure!(crate::config::valid_id(id), "invalid location ACL profile id");
}
@@ -224,6 +249,56 @@ pub fn validate_listener(listener: Listener) -> Result<()> {
);
Ok(())
}
/// Return the other side of a www/non-www redirect pair. The target and its
/// counterpart must both be routable, even if only one was configured.
fn www_partner(host: &str) -> Result<String> {
if let Some(bare) = host.strip_prefix("www.") {
ensure!(!bare.is_empty() && bare.parse::<std::net::IpAddr>().is_err(),
"www redirect requires a DNS hostname");
crate::config::normalize_host(bare)
} else {
crate::config::normalize_host(&format!("www.{host}"))
}
}
impl Vhost {
fn www_canonical_host(&self) -> Result<&str> {
let canonical = self.www_redirect_target.as_deref()
.or_else(|| self.frontend.hosts.first().map(String::as_str))
.ok_or_else(|| anyhow::anyhow!("vhost requires at least one hostname"))?;
ensure!(!canonical.starts_with("*.") && canonical.parse::<std::net::IpAddr>().is_err(),
"www redirect requires an exact DNS hostname");
let partner = www_partner(canonical)?;
ensure!(self.frontend.hosts.iter().any(|host| host == canonical || host == &partner),
"www redirect target or its www/non-www partner must belong to this vhost");
Ok(canonical)
}
/// Hostnames owned by the vhost at runtime. Both the selected canonical
/// host and its www/non-www partner are claimed, even if one is implicit.
pub fn routing_hosts(&self) -> Result<Vec<String>> {
let mut hosts = self.frontend.hosts.clone();
if !self.www_redirect {
return Ok(hosts);
}
let canonical = self.www_canonical_host()?.to_owned();
let companion = www_partner(&canonical)?;
for name in [canonical, companion] {
if !hosts.contains(&name) {
hosts.push(name);
}
}
Ok(hosts)
}
pub fn www_redirect_pair(&self) -> Result<Option<(String, String)>> {
if !self.www_redirect {
return Ok(None);
}
let canonical = self.www_canonical_host()?.to_owned();
let companion = www_partner(&canonical)?;
Ok(Some((companion, canonical)))
}
}
impl Frontend {
pub fn resolved(&self, settings: &crate::settings::Listeners) -> Result<Vec<Listener>> {
let mut result = Vec::new();