v0.34.0_12
This commit is contained in:
1 parent
9f20d5195e
commit
929854898f
16 files changed
+186
-285
No files matched your search
+80
-87
@@ -1,24 +1,46 @@
|
||||
impl Snapshot {
|
||||
// Exact host: no allocation. Wildcards: lazy and most-specific-first.
|
||||
fn host_patterns(host: &str) -> impl Iterator<Item = std::borrow::Cow<'_, str>> {
|
||||
std::iter::once(std::borrow::Cow::Borrowed(host)).chain(
|
||||
host.match_indices('.').filter_map(move |(index, _)| {
|
||||
let suffix = &host[index + 1..];
|
||||
suffix.contains('.').then(|| std::borrow::Cow::Owned(format!("*.{suffix}")))
|
||||
}),
|
||||
)
|
||||
}
|
||||
pub fn host_allowed(&self, host: &str, peer: IpAddr) -> bool {
|
||||
for pattern in Self::host_patterns(host) {
|
||||
if let Some(acl) = self.host_acls.get(pattern.as_ref()) {
|
||||
return acl.allows(peer);
|
||||
// Exact-first lookups with stack-buffered wildcard keys for normal hosts.
|
||||
fn find_host_pattern<T>(
|
||||
host: &str,
|
||||
mut lookup: impl FnMut(&str) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
if let Some(value) = lookup(host) {
|
||||
return Some(value);
|
||||
}
|
||||
// Skip the final dot: only one label follows it.
|
||||
let last_dot = host.rfind('.')?;
|
||||
let mut wildcard = [0u8; 256];
|
||||
wildcard[..2].copy_from_slice(b"*.");
|
||||
for (index, _) in host.match_indices('.') {
|
||||
if index == last_dot {
|
||||
break;
|
||||
}
|
||||
let suffix = &host[index + 1..];
|
||||
let len = suffix.len() + 2;
|
||||
if len <= wildcard.len() {
|
||||
wildcard[2..len].copy_from_slice(suffix.as_bytes());
|
||||
let key = std::str::from_utf8(&wildcard[..len])
|
||||
.expect("a wildcard plus a UTF-8 host is valid UTF-8");
|
||||
if let Some(value) = lookup(key) {
|
||||
return Some(value);
|
||||
}
|
||||
} else if let Some(value) = lookup(&format!("*.{suffix}")) {
|
||||
// Preserve behavior for unusual overlong internal host inputs.
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
true
|
||||
None
|
||||
}
|
||||
/// RFC 8441 SETTINGS_ENABLE_CONNECT_PROTOCOL is connection-scoped, so it is
|
||||
/// advertised only when at least one explicitly opted-in vhost is bound to
|
||||
/// this HTTP/2 listener. Request routing still enforces the per-vhost opt-in.
|
||||
pub fn host_allowed(&self, host: &str, peer: IpAddr) -> bool {
|
||||
if self.host_acls.is_empty() {
|
||||
return true;
|
||||
}
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
self.host_acls.get(pattern).map(|acl| acl.allows(peer))
|
||||
})
|
||||
.unwrap_or(true)
|
||||
}
|
||||
/// RFC 8441 is connection-scoped; requests still enforce vhost opt-in.
|
||||
pub fn websocket_rfc8441_on(&self, local: SocketAddr, tls: bool) -> bool {
|
||||
self.frontends.values().flatten().any(|frontend| {
|
||||
frontend.websockets
|
||||
@@ -29,10 +51,7 @@ impl Snapshot {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// RFC 9220 SETTINGS_ENABLE_CONNECT_PROTOCOL is connection-scoped, so it is
|
||||
/// advertised only when at least one explicitly opted-in vhost is bound to
|
||||
/// this QUIC listener. Request routing still enforces the per-vhost opt-in.
|
||||
/// RFC 9220 is connection-scoped; requests still enforce vhost opt-in.
|
||||
pub fn websocket_rfc9220_on(&self, local: SocketAddr) -> bool {
|
||||
self.frontends.values().flatten().any(|frontend| {
|
||||
frontend.websockets
|
||||
@@ -54,7 +73,6 @@ impl Snapshot {
|
||||
) -> Option<&CompiledRoute> {
|
||||
self.select_on_arc(host, path, local, tls).map(Arc::as_ref)
|
||||
}
|
||||
|
||||
/// Shared compiled-route handle for middleware and forward().
|
||||
pub fn select_on_arc(
|
||||
&self,
|
||||
@@ -63,28 +81,22 @@ impl Snapshot {
|
||||
local: SocketAddr,
|
||||
tls: bool,
|
||||
) -> Option<&Arc<CompiledRoute>> {
|
||||
for pattern in Self::host_patterns(host) {
|
||||
let fallback = self.fallbacks.get(pattern.as_ref()).and_then(|routes| {
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
let fallback = self.fallbacks.get(pattern).and_then(|routes| {
|
||||
routes
|
||||
.iter()
|
||||
.find(|route| route.bindings.iter().any(|l| l.matches(local, tls)))
|
||||
});
|
||||
// A claimed exact/wildcard vhost owns the request as a unit. First try
|
||||
// its Custom locations, then its main backend fallback; never continue
|
||||
// into a broader wildcard merely because no Custom location matched.
|
||||
if let Some(fallback) = fallback {
|
||||
return self
|
||||
.routes
|
||||
.get(pattern.as_ref())
|
||||
.and_then(|routes| {
|
||||
select_route(routes, path, |r| {
|
||||
r.bindings.iter().any(|l| l.matches(local, tls))
|
||||
})
|
||||
})?;
|
||||
// The claimed vhost owns its fallback ahead of broader wildcards.
|
||||
self.routes
|
||||
.get(pattern)
|
||||
.and_then(|routes| {
|
||||
select_route(routes, path, |r| {
|
||||
r.bindings.iter().any(|l| l.matches(local, tls))
|
||||
})
|
||||
.or(Some(fallback));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.or(Some(fallback))
|
||||
})
|
||||
}
|
||||
pub fn frontend(
|
||||
&self,
|
||||
@@ -92,21 +104,15 @@ impl Snapshot {
|
||||
local: SocketAddr,
|
||||
tls: bool,
|
||||
) -> Option<&Arc<CompiledFrontend>> {
|
||||
for pattern in Self::host_patterns(host) {
|
||||
if let Some(frontend) = self.frontends.get(pattern.as_ref()).and_then(|frontends| {
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
self.frontends.get(pattern).and_then(|frontends| {
|
||||
frontends
|
||||
.iter()
|
||||
.find(|f| f.bindings.iter().any(|l| l.matches(local, tls)))
|
||||
}) {
|
||||
return Some(frontend);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolve a persistent protocol-error record to a configured vhost using
|
||||
/// the same exact/wildcard precedence and listener binding rules as normal routing.
|
||||
/// Client-provided Host/SNI values are never used as file names directly.
|
||||
/// Resolve protocol-error attribution with the routing precedence and bindings.
|
||||
pub fn resolve_logging_vhost(
|
||||
&self,
|
||||
local: SocketAddr,
|
||||
@@ -130,7 +136,6 @@ impl Snapshot {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (candidate, matched_by) in [
|
||||
(authority, "authority"),
|
||||
(host, "host"),
|
||||
@@ -151,8 +156,7 @@ impl Snapshot {
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Select a redirect host/rule using the accepted socket, TLS state and normalized path.
|
||||
/// Exact hostnames win over the longest matching wildcard, matching vhost behavior.
|
||||
/// Select redirects using accepted socket, TLS state and normalized path.
|
||||
pub fn redirect_on(
|
||||
&self,
|
||||
host: &str,
|
||||
@@ -163,8 +167,11 @@ impl Snapshot {
|
||||
&Arc<crate::redirect::CompiledHost>,
|
||||
&crate::redirect::CompiledRule,
|
||||
)> {
|
||||
for pattern in Self::host_patterns(host) {
|
||||
if let Some((redirect, rule)) = self.redirects.get(pattern.as_ref()).and_then(|redirects| {
|
||||
if self.redirects.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
self.redirects.get(pattern).and_then(|redirects| {
|
||||
redirects.iter().find_map(|redirect| {
|
||||
if !redirect
|
||||
.bindings
|
||||
@@ -179,11 +186,8 @@ impl Snapshot {
|
||||
.find(|rule| rule.matches(path, tls))
|
||||
.map(|rule| (redirect, rule))
|
||||
})
|
||||
}) {
|
||||
return Some((redirect, rule));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
/// Select a host-owned local 404/static response using the accepted socket.
|
||||
pub fn not_found_host_on(
|
||||
@@ -192,40 +196,31 @@ impl Snapshot {
|
||||
local: SocketAddr,
|
||||
tls: bool,
|
||||
) -> Option<&Arc<crate::not_found::CompiledHost>> {
|
||||
for pattern in Self::host_patterns(host) {
|
||||
if let Some(item) = self.not_found_hosts.get(pattern.as_ref()).and_then(|items| {
|
||||
if self.not_found_hosts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
self.not_found_hosts.get(pattern).and_then(|items| {
|
||||
items.iter().find(|item| {
|
||||
item.bindings
|
||||
.iter()
|
||||
.any(|binding| binding.matches(local, tls))
|
||||
})
|
||||
}) {
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy inspection helper only; network handlers must call select_on.
|
||||
pub fn select(&self, host: &str, path: &str) -> Option<&CompiledRoute> {
|
||||
for pattern in Self::host_patterns(host) {
|
||||
if let Some(fallback) = self
|
||||
.fallbacks
|
||||
.get(pattern.as_ref())
|
||||
.and_then(|routes| routes.first())
|
||||
{
|
||||
return self
|
||||
.routes
|
||||
.get(pattern.as_ref())
|
||||
.and_then(|routes| select_route(routes, path, |_| true))
|
||||
.or(Some(fallback))
|
||||
.map(Arc::as_ref);
|
||||
}
|
||||
}
|
||||
None
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
let fallback = self.fallbacks.get(pattern).and_then(|routes| routes.first())?;
|
||||
self.routes
|
||||
.get(pattern)
|
||||
.and_then(|routes| select_route(routes, path, |_| true))
|
||||
.or(Some(fallback))
|
||||
.map(Arc::as_ref)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn select_route<'a, F>(
|
||||
routes: &'a [Arc<CompiledRoute>],
|
||||
path: &str,
|
||||
@@ -273,7 +268,6 @@ where
|
||||
}
|
||||
prefix
|
||||
}
|
||||
|
||||
impl CompiledRoute {
|
||||
pub fn redirect_location(&self, path: &str, uri: &http::Uri) -> Result<Option<http::HeaderValue>> {
|
||||
let Some(spec) = &self.redirect else {
|
||||
@@ -297,7 +291,6 @@ impl CompiledRoute {
|
||||
}
|
||||
Ok(Some(http::HeaderValue::from_str(target.as_str()).context("invalid redirect Location header")?))
|
||||
}
|
||||
|
||||
/// Return a normalized replacement path for the upstream hop. `None` means
|
||||
/// preserve the client's exact origin-form path bytes.
|
||||
pub fn rewritten_path(&self, path: &str) -> Result<Option<String>> {
|
||||
|
||||
Reference in new issue
Block a user