v0.34.0_10
This commit is contained in:
1 parent
8c4cbc583f
commit
dd2502dd11
13 files changed
+389
-113
No files matched your search
+28
-12
@@ -157,27 +157,43 @@ fn encoded_origin_path(path: &str) -> Option<String> {
|
||||
fn response_context(
|
||||
snapshot: &crate::config::Snapshot,
|
||||
request: &Request,
|
||||
) -> (
|
||||
Option<Arc<crate::config::CompiledFrontend>>,
|
||||
Option<Arc<crate::config::CompiledRoute>>,
|
||||
) {
|
||||
) -> SelectedRouteContext {
|
||||
let tls = request
|
||||
.extensions()
|
||||
.get::<Transport>()
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.tls;
|
||||
let context = (|| {
|
||||
let selected = (|| {
|
||||
let local = request.extensions().get::<LocalAddr>()?.0;
|
||||
let authority = effective_host(request, tls).ok()?;
|
||||
let host = request_host(authority.to_str().ok()?).ok()?;
|
||||
let original_host = effective_host(request, tls).ok()?;
|
||||
let host = request_host(original_host.to_str().ok()?).ok()?;
|
||||
let frontend = snapshot.frontend(&host, local, tls).cloned();
|
||||
let route = crate::acl::routing_path_view(request.uri().path())
|
||||
.ok()
|
||||
.and_then(|path| snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned());
|
||||
Some((frontend, route))
|
||||
let path = crate::acl::routing_path_view(request.uri().path()).ok();
|
||||
let route = path.as_ref().and_then(|path| {
|
||||
snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned()
|
||||
});
|
||||
// An ordinary path borrows the original URI in forward_request. Only
|
||||
// a decoded path needs to be moved between middleware and forwarding.
|
||||
let decoded_path = path.and_then(|path| match path {
|
||||
std::borrow::Cow::Borrowed(_) => None,
|
||||
std::borrow::Cow::Owned(path) => Some(path),
|
||||
});
|
||||
Some(SelectedRouteContext {
|
||||
frontend,
|
||||
route,
|
||||
validated: Some(ValidatedRoutingIdentity {
|
||||
original_host,
|
||||
host,
|
||||
decoded_path,
|
||||
}),
|
||||
})
|
||||
})();
|
||||
context.unwrap_or((None, None))
|
||||
selected.unwrap_or(SelectedRouteContext {
|
||||
frontend: None,
|
||||
route: None,
|
||||
validated: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reserved administration-session cookies may cross the proxy boundary only
|
||||
|
||||
+19
-21
@@ -27,20 +27,11 @@ async fn forward_request(
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.tls;
|
||||
let Ok(original_host) = effective_host(&request, tls) else {
|
||||
return error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid or conflicting request authority\n",
|
||||
);
|
||||
};
|
||||
let Ok(host) = original_host
|
||||
.to_str()
|
||||
.ok()
|
||||
.and_then(|v| request_host(v).ok())
|
||||
.ok_or(())
|
||||
else {
|
||||
return error(StatusCode::BAD_REQUEST, "RustProxy: invalid hostname\n");
|
||||
};
|
||||
let (selected_context, original_host, host, decoded_path, host_already_validated) =
|
||||
match take_routing_identity(&mut request, tls) {
|
||||
Ok(identity) => identity,
|
||||
Err(response) => return response,
|
||||
};
|
||||
let handshake = if request.method() == Method::CONNECT {
|
||||
let protocol = match request.version() {
|
||||
Version::HTTP_2 => websocket::h2_protocol(&request),
|
||||
@@ -106,13 +97,17 @@ async fn forward_request(
|
||||
// forwarding operation, even after the request URI/headers are rewritten.
|
||||
// Encoded/slash-folded paths still allocate via the strict decoder.
|
||||
let routing_uri = request.uri().clone();
|
||||
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
|
||||
return error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: ambiguous request path\n",
|
||||
);
|
||||
let path = if let Some(path) = decoded_path.as_deref() {
|
||||
std::borrow::Cow::Borrowed(path)
|
||||
} else {
|
||||
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
|
||||
return error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: ambiguous request path\n",
|
||||
);
|
||||
};
|
||||
path
|
||||
};
|
||||
let selected_context = request.extensions().get::<SelectedRouteContext>().cloned();
|
||||
let frontend = if let Some(selection) = &selected_context {
|
||||
selection.frontend.as_ref()
|
||||
} else {
|
||||
@@ -193,7 +188,7 @@ async fn forward_request(
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut context = match snapshot.forwarding.context_with_original_url(
|
||||
let mut context = match snapshot.forwarding.context_with_peer(
|
||||
request.headers(),
|
||||
peer.0,
|
||||
local.0,
|
||||
@@ -201,6 +196,9 @@ async fn forward_request(
|
||||
&original_host,
|
||||
request.uri(),
|
||||
frontend.is_some_and(|frontend| frontend.tinyauth.is_some()),
|
||||
request.extensions().get::<Arc<crate::forwarding::PeerFormatting>>()
|
||||
.map(Arc::as_ref),
|
||||
host_already_validated,
|
||||
) {
|
||||
Ok(context) => context,
|
||||
Err(_) => {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Observation has already validated routing identity. Transfer it to the
|
||||
// forward handler instead of repeating hostname parsing and percent decoding.
|
||||
#[derive(Clone)]
|
||||
struct ValidatedRoutingIdentity {
|
||||
original_host: HeaderValue,
|
||||
host: String,
|
||||
decoded_path: Option<String>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct SelectedRouteContext {
|
||||
frontend: Option<Arc<crate::config::CompiledFrontend>>,
|
||||
route: Option<Arc<crate::config::CompiledRoute>>,
|
||||
validated: Option<ValidatedRoutingIdentity>,
|
||||
}
|
||||
/// Transfer middleware-validated identity without cloning strings. Internal
|
||||
/// fallback dispatches still use the complete authority validation path.
|
||||
fn take_routing_identity(
|
||||
request: &mut Request,
|
||||
tls: bool,
|
||||
) -> Result<(Option<SelectedRouteContext>, HeaderValue, String, Option<String>, bool), Response> {
|
||||
let mut selected_context = request.extensions_mut().remove::<SelectedRouteContext>();
|
||||
let validated = selected_context.as_mut().and_then(|context| context.validated.take());
|
||||
let host_already_validated = validated.is_some();
|
||||
let (original_host, host, decoded_path) = match validated {
|
||||
Some(validated) => (
|
||||
validated.original_host,
|
||||
validated.host,
|
||||
validated.decoded_path,
|
||||
),
|
||||
None => {
|
||||
let original_host = effective_host(request, tls).map_err(|_| error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid or conflicting request authority\n",
|
||||
))?;
|
||||
let host = original_host.to_str().ok()
|
||||
.and_then(|v| request_host(v).ok())
|
||||
.ok_or_else(|| error(StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid hostname\n"))?;
|
||||
(original_host, host, None)
|
||||
}
|
||||
};
|
||||
Ok((selected_context, original_host, host, decoded_path, host_already_validated))
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
include!("types.rs");
|
||||
include!("identity.rs");
|
||||
include!("error_fallback.rs");
|
||||
include!("cache_helpers.rs");
|
||||
mod cache_plan;
|
||||
|
||||
@@ -11,6 +11,32 @@ mod authority_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observation_transfers_validated_host_and_decoded_path() {
|
||||
let config = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
|
||||
let snapshot = crate::config::compile(config, 1, &validation_policy()).unwrap();
|
||||
for (raw, normalized) in [
|
||||
("/assets/app.js?rev=5", None),
|
||||
("/assets/%61pp.js?rev=5", Some("/assets/app.js")),
|
||||
("/assets//app.js", Some("/assets/app.js")),
|
||||
] {
|
||||
let mut request = Request::builder().method(Method::GET).uri(raw)
|
||||
.header(HOST, "localhost").body(Body::empty()).unwrap();
|
||||
request.extensions_mut().insert(LocalAddr("127.0.0.1:8080".parse().unwrap()));
|
||||
let context = response_context(&snapshot, &request);
|
||||
let validated = context.validated.unwrap();
|
||||
assert_eq!(validated.host, "localhost");
|
||||
assert_eq!(validated.original_host, "localhost");
|
||||
assert_eq!(validated.decoded_path.as_deref(), normalized);
|
||||
}
|
||||
let mut invalid = Request::builder().uri("/ok")
|
||||
.header(HOST, "localhost").header(HOST, "another.example")
|
||||
.body(Body::empty()).unwrap();
|
||||
invalid.extensions_mut().insert(LocalAddr("127.0.0.1:8080".parse().unwrap()));
|
||||
assert!(response_context(&snapshot, &invalid).validated.is_none());
|
||||
assert!(crate::acl::routing_path_view("/encoded%2fslash").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_is_limited_to_safe_methods() {
|
||||
for method in [Method::GET, Method::HEAD, Method::OPTIONS] {
|
||||
|
||||
+1
-7
@@ -21,13 +21,7 @@ use http::{
|
||||
};
|
||||
use http_body::Body as HttpBody;
|
||||
use http_body_util::BodyExt;
|
||||
/// A route resolved against the immutable snapshot in the observation layer.
|
||||
/// The internal error-fallback dispatcher must resolve its target independently.
|
||||
#[derive(Clone)]
|
||||
struct SelectedRouteContext {
|
||||
frontend: Option<Arc<crate::config::CompiledFrontend>>,
|
||||
route: Option<Arc<crate::config::CompiledRoute>>,
|
||||
}
|
||||
/// Mark responses whose configured outbound policies were already applied.
|
||||
#[derive(Clone)]
|
||||
struct ResponseRulesApplied;
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -37,13 +37,12 @@ fn should_replace_error_page(response: &Response, minimal_welcome: bool) -> bool
|
||||
|
||||
async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next) -> Response {
|
||||
let snapshot = state.config.load_full();
|
||||
let (frontend_context, route_context) = response_context(&snapshot, &request);
|
||||
// The same snapshot is inserted into the request. No second host/regex
|
||||
// selection or expensive full CompiledRoute clone is necessary in forward().
|
||||
request.extensions_mut().insert(SelectedRouteContext {
|
||||
frontend: frontend_context.clone(),
|
||||
route: route_context.clone(),
|
||||
});
|
||||
let selected = response_context(&snapshot, &request);
|
||||
let frontend_context = selected.frontend.clone();
|
||||
let route_context = selected.route.clone();
|
||||
// The same validated request identity and snapshot are transferred to
|
||||
// forward(); no second hostname parsing or encoded-path decoding.
|
||||
request.extensions_mut().insert(selected);
|
||||
let local = request.extensions().get::<LocalAddr>().map(|v| v.0);
|
||||
let transport_tls = request
|
||||
.extensions()
|
||||
|
||||
Reference in new issue
Block a user