This commit is contained in:
Mateusz Gruszczyński committed 2026-11-08 09:46:32 +01:00
1 parent 34490fd5fc
commit 6ef01212ec
21 files changed
+204 -77

No files matched your search

+23 -10
View File
@@ -150,28 +150,35 @@ pub fn compile_reusing(
type LocalHostBuild = (
HashMap<String, Vec<Arc<crate::redirect::CompiledHost>>>,
HashMap<String, Vec<Arc<crate::not_found::CompiledHost>>>,
HashMap<String, Vec<crate::vhost::Listener>>,
HashMap<String, Vec<(String, crate::vhost::Listener)>>,
);
fn compile_local_hosts(config: &Config) -> Result<LocalHostBuild> {
let mut redirects: HashMap<String, Vec<Arc<crate::redirect::CompiledHost>>> = HashMap::new();
let mut not_found_hosts: HashMap<String, Vec<Arc<crate::not_found::CompiledHost>>> =
HashMap::new();
let mut claimed_hosts: HashMap<String, Vec<crate::vhost::Listener>> = HashMap::new();
let mut claimed_hosts: HashMap<String, Vec<(String, crate::vhost::Listener)>> =
HashMap::new();
for redirect in &config.redirects {
let bindings = redirect.frontend.resolved(&config.listeners)?;
for name in &redirect.frontend.hosts {
let claimed = claimed_hosts.entry(name.clone()).or_default();
for a in claimed.iter() {
for (owner, a) in claimed.iter() {
for b in &bindings {
ensure!(
!crate::vhost::socket_overlap(a.address, b.address),
"overlapping frontend bindings for hostname {}",
name
"overlapping frontend bindings for hostname {} (already used by {} on {})",
name,
owner,
a.address
);
}
}
claimed.extend(bindings.iter().copied());
claimed.extend(
bindings
.iter()
.map(|binding| (format!("redirect {}", redirect.id), *binding)),
);
}
if redirect.enabled {
let compiled = Arc::new(crate::redirect::compile(redirect, &config.listeners)?);
@@ -187,16 +194,22 @@ fn compile_local_hosts(config: &Config) -> Result<LocalHostBuild> {
let bindings = host.frontend.resolved(&config.listeners)?;
for name in &host.frontend.hosts {
let claimed = claimed_hosts.entry(name.clone()).or_default();
for a in claimed.iter() {
for (owner, a) in claimed.iter() {
for b in &bindings {
ensure!(
!crate::vhost::socket_overlap(a.address, b.address),
"overlapping frontend bindings for hostname {}",
name
"overlapping frontend bindings for hostname {} (already used by {} on {})",
name,
owner,
a.address
);
}
}
claimed.extend(bindings.iter().copied());
claimed.extend(
bindings
.iter()
.map(|binding| (format!("404 host {}", host.id), *binding)),
);
}
if host.enabled {
let compiled = Arc::new(crate::not_found::compile(host, &config.listeners)?);
+21 -8
View File
@@ -65,7 +65,8 @@ pub fn compile_with_active(
php_profiles
.insert(profile.id.clone(), profile.php.clone())
.is_none(),
"duplicate PHP-FPM profile id"
"duplicate PHP-FPM profile id {:?}: every profile needs a unique ID",
profile.id
);
}
ensure!(
@@ -120,7 +121,8 @@ pub fn compile_with_active(
);
ensure!(
web_services.insert(service.id.clone(), compiled).is_none(),
"duplicate Web Service id"
"duplicate Web Service id {:?}: every Web Service needs a unique ID",
service.id
);
}
ensure!(config.vhosts.len() <= 64, "too many vhosts (max 64)");
@@ -145,7 +147,11 @@ pub fn compile_with_active(
validate_source(&route.source)?;
}
}
ensure!(vhost_ids.insert(host.id.clone()), "duplicate vhost id");
ensure!(
vhost_ids.insert(host.id.clone()),
"duplicate vhost id {:?}: every vhost needs a unique ID",
host.id
);
}
ensure!(
config.redirects.len() <= 64,
@@ -158,7 +164,8 @@ pub fn compile_with_active(
.with_context(|| format!("redirect {}", redirect.id))?;
ensure!(
redirect_ids.insert(redirect.id.clone()),
"duplicate redirect id"
"duplicate redirect id {:?}: every redirect needs a unique ID",
redirect.id
);
}
ensure!(
@@ -171,7 +178,8 @@ pub fn compile_with_active(
.with_context(|| format!("404 host {}", host.id))?;
ensure!(
not_found_ids.insert(host.id.clone()),
"duplicate 404 host id"
"duplicate 404 host id {:?}: every 404 host needs a unique ID",
host.id
);
}
let effective_tls = crate::tls::for_all_frontends(
@@ -279,7 +287,11 @@ pub fn compile_with_active(
let mut acl_profiles: HashMap<String, (Option<CompiledAcl>, crate::rate_limit::CountryAccess)> =
HashMap::new();
for profile in &mut config.acl_profiles {
ensure!(valid_id(&profile.id), "invalid ACL profile id");
ensure!(
valid_id(&profile.id),
"invalid ACL profile id {:?}: use 1-64 letters, digits, '-' or '_'",
profile.id
);
ensure!(
!profile.name.trim().is_empty()
&& profile.name.len() <= 96
@@ -293,7 +305,8 @@ pub fn compile_with_active(
acl_profiles
.insert(profile.id.clone(), (acl, profile.country_access.clone()))
.is_none(),
"duplicate ACL profile id"
"duplicate ACL profile id {:?}: every ACL profile needs a unique ID",
profile.id
);
}
crate::acl::dynamic::configured_hostnames(&config)?;
@@ -380,7 +393,7 @@ fn normalize_prefix(prefix: &mut String) -> Result<()> {
&& !prefix.contains('?')
&& !prefix.contains('#')
&& !prefix.bytes().any(|b| b <= 32 || b == 127),
"invalid path prefix"
"invalid path prefix: it must start with '/', be at most 512 bytes and contain no '?', '#', spaces or control characters"
);
if prefix.len() > 1 {
*prefix = prefix.trim_end_matches('/').to_owned();
+25 -10
View File
@@ -79,16 +79,22 @@ fn compile_routing(
);
for name in &routing_hosts {
let claimed = claimed_hosts.entry(name.clone()).or_default();
for a in claimed.iter() {
for (owner, a) in claimed.iter() {
for b in &host_bindings {
ensure!(
!crate::vhost::socket_overlap(a.address, b.address),
"overlapping frontend bindings for hostname {}",
name
"overlapping frontend bindings for hostname {} (already used by {} on {})",
name,
owner,
a.address
);
}
}
claimed.extend(host_bindings.iter().copied());
claimed.extend(
host_bindings
.iter()
.map(|binding| (format!("vhost {}", host.id), *binding)),
);
}
let needs_proxy_pool = host.source.is_reverse_proxy()
|| host.routes.iter().any(|route| {
@@ -323,7 +329,8 @@ fn compile_routing(
for route in &mut host.routes {
ensure!(
valid_id(&route.id) && path_ids.insert(route.id.clone()),
"invalid/duplicate vhost path id"
"location ID {:?} is invalid or used twice in this vhost (use 1-64 letters, digits, '-' or '_', unique per vhost)",
route.id
);
let path_regex = match route.match_type {
crate::vhost::PathMatch::Regex | crate::vhost::PathMatch::RegexInsensitive => {
@@ -331,7 +338,8 @@ fn compile_routing(
!route.path_prefix.is_empty()
&& route.path_prefix.len() <= 1024
&& !route.path_prefix.chars().any(char::is_control),
"invalid vhost location regex"
"location {}: the regular expression must be 1-1024 characters without control characters",
route.id
);
let mut builder = regex::RegexBuilder::new(&route.path_prefix);
builder
@@ -340,17 +348,23 @@ fn compile_routing(
)
.size_limit(2 * 1024 * 1024)
.dfa_size_limit(2 * 1024 * 1024);
Some(builder.build().context("invalid vhost location regex")?)
Some(builder.build().with_context(|| {
format!(
"location {}: invalid or too complex regular expression",
route.id
)
})?)
}
crate::vhost::PathMatch::Exact => {
ensure!(
!route.path_prefix.contains('?')
&& !route.path_prefix.contains('#')
&& !route.path_prefix.bytes().any(|b| b <= 32 || b == 127),
"invalid exact location path"
"location {}: an exact path must not contain '?', '#', spaces or control characters",
route.id
);
route.path_prefix = crate::acl::routing_path(&route.path_prefix)
.context("invalid exact location path")?;
.with_context(|| format!("location {}: invalid exact path", route.id))?;
None
}
crate::vhost::PathMatch::Prefix | crate::vhost::PathMatch::PrefixPriority => {
@@ -361,7 +375,8 @@ fn compile_routing(
if !route.internal {
ensure!(
path_bindings.insert((route.match_type, route.path_prefix.clone())),
"duplicate vhost location matcher"
"location {}: another location already uses the same path and match type",
route.id
);
}
validate_route_rewrite(route, path_regex.as_ref())?;