v0.29.0
This commit is contained in:
1 parent
65e082b9d1
commit
76cb4acef0
72 files changed
+6134
-4709
No files matched your search
@@ -6,6 +6,8 @@ if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
|
||||
*exchange.route.lock().unwrap_or_else(|e| e.into_inner()) = Some(route.id.clone());
|
||||
*exchange.source.lock().unwrap_or_else(|e| e.into_inner()) = if route.response.is_some() {
|
||||
"local_response"
|
||||
} else if route.redirect.is_some() {
|
||||
"local_redirect"
|
||||
} else {
|
||||
match &route.source {
|
||||
crate::config::CompiledSource::WebServer(_) => "web_server",
|
||||
@@ -18,6 +20,17 @@ if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
|
||||
if let Some(spec) = &route.response {
|
||||
return local_location_response(spec, request.method());
|
||||
}
|
||||
if let Some(spec) = &route.redirect {
|
||||
let location = match route.redirect_location(&path, request.uri()) {
|
||||
Ok(Some(location)) => location,
|
||||
_ => return error(StatusCode::INTERNAL_SERVER_ERROR, "RustProxy: invalid location redirect\n"),
|
||||
};
|
||||
let status = StatusCode::from_u16(spec.status).unwrap_or(StatusCode::FOUND);
|
||||
let mut response = status.into_response();
|
||||
response.headers_mut().insert(http::header::LOCATION, location);
|
||||
response.headers_mut().insert("cache-control", HeaderValue::from_static("no-store"));
|
||||
return response;
|
||||
}
|
||||
if route.unavailable {
|
||||
let mut response = error(
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
@@ -178,28 +191,38 @@ let body_empty = if cache_enabled
|
||||
} else {
|
||||
request.body().is_end_stream()
|
||||
};
|
||||
let ignore_static_cookie = cache_settings.ignore_cookie_for_path(&path);
|
||||
let original_policy = policy_reason
|
||||
.map(crate::cache::RequestPolicy::Bypass)
|
||||
.unwrap_or_else(|| {
|
||||
crate::cache::request_policy(
|
||||
request.method(),
|
||||
request.headers(),
|
||||
body_empty,
|
||||
&path,
|
||||
request.uri().query().is_some(),
|
||||
cache_settings,
|
||||
)
|
||||
});
|
||||
|
||||
let only_cached =
|
||||
cache_settings.active_for(route.cache) && crate::cache::only_if_cached(request.headers());
|
||||
let public_suffix = request
|
||||
.uri()
|
||||
.path_and_query()
|
||||
.map(|v| v.as_str())
|
||||
.unwrap_or("/")
|
||||
.to_owned();
|
||||
let force_cache = policy_reason.is_none()
|
||||
&& cache_settings.active_for(route.cache)
|
||||
&& route
|
||||
.frontend
|
||||
.as_ref()
|
||||
.is_some_and(|frontend| frontend.force_cache.matches(&path, &public_suffix));
|
||||
let ignore_static_cookie = !force_cache && cache_settings.ignore_cookie_for_path(&path);
|
||||
let original_policy = policy_reason
|
||||
.map(crate::cache::RequestPolicy::Bypass)
|
||||
.unwrap_or_else(|| {
|
||||
if force_cache {
|
||||
crate::cache::forced_request_policy(request.method(), request.headers(), body_empty)
|
||||
} else {
|
||||
crate::cache::request_policy(
|
||||
request.method(),
|
||||
request.headers(),
|
||||
body_empty,
|
||||
&path,
|
||||
request.uri().query().is_some(),
|
||||
cache_settings,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let only_cached =
|
||||
cache_settings.active_for(route.cache) && crate::cache::only_if_cached(request.headers());
|
||||
let origin_suffix = match route.rewritten_path(&path) {
|
||||
Ok(None) => public_suffix.clone(),
|
||||
Ok(Some(rewritten)) => {
|
||||
@@ -283,14 +306,20 @@ if websocket_legacy_support {
|
||||
let range_selection = crate::cache::request_range(request.method(), request.headers());
|
||||
let if_range = request.headers().get("if-range").cloned();
|
||||
let range_requested = !matches!(range_selection, crate::cache::RangeSelection::None);
|
||||
let post_policy = crate::cache::request_policy(
|
||||
request.method(),
|
||||
request.headers(),
|
||||
body_empty,
|
||||
&path,
|
||||
request.uri().query().is_some(),
|
||||
cache_settings,
|
||||
);
|
||||
let post_policy = if force_cache {
|
||||
crate::cache::forced_request_policy(request.method(), request.headers(), body_empty)
|
||||
} else {
|
||||
crate::cache::request_policy(
|
||||
request.method(),
|
||||
request.headers(),
|
||||
body_empty,
|
||||
&path,
|
||||
request.uri().query().is_some(),
|
||||
cache_settings,
|
||||
)
|
||||
};
|
||||
let force_vary_cookie = force_cache && request.headers().contains_key("cookie");
|
||||
let force_vary_authorization = force_cache && request.headers().contains_key("authorization");
|
||||
let effective_policy = match (original_policy, post_policy) {
|
||||
(crate::cache::RequestPolicy::Bypass(reason), _) => {
|
||||
crate::cache::RequestPolicy::Bypass(reason)
|
||||
@@ -353,7 +382,8 @@ let pending_revalidation = revalidation_reason.is_some()
|
||||
let miss_recorded = false;
|
||||
(
|
||||
backend, backend_lease, affinity_cookie, admin_control_plane, cache_settings,
|
||||
transform_settings, scope, ignore_static_cookie, only_cached, public_suffix,
|
||||
transform_settings, scope, ignore_static_cookie, force_cache, force_vary_cookie,
|
||||
force_vary_authorization, only_cached, public_suffix,
|
||||
origin_suffix, uri, exchange, lease, stop, downstream, h2_downstream,
|
||||
range_selection, if_range, range_requested, effective_reason, revalidation_reason,
|
||||
cache_ticket, client_conditionals, cached_revalidation, freshness_revalidation_reason,
|
||||
|
||||
@@ -117,7 +117,7 @@ let reason = effective_reason
|
||||
if cache_ticket.is_some() && transformed_plan.is_none() {
|
||||
"response_headers_policy"
|
||||
} else if cache_ticket.is_some() {
|
||||
"cache_miss"
|
||||
if force_cache { "cache_miss_forced" } else { "cache_miss" }
|
||||
} else {
|
||||
"configuration_changed"
|
||||
}
|
||||
|
||||
@@ -118,7 +118,10 @@ let origin_plan = if cache_ticket.is_some()
|
||||
response.headers(),
|
||||
cache_settings,
|
||||
upstream_started.elapsed(),
|
||||
)
|
||||
).map(|plan| {
|
||||
let plan = if force_vary_cookie { plan.with_vary("cookie") } else { plan };
|
||||
if force_vary_authorization { plan.with_vary("authorization") } else { plan }
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -146,6 +146,11 @@ async fn forward(
|
||||
{
|
||||
return response;
|
||||
}
|
||||
if let Some(response) = frontend.and_then(|frontend| {
|
||||
canonical_host_redirect(frontend, &host, tls, local.0.port(), request.uri())
|
||||
}) {
|
||||
return response;
|
||||
}
|
||||
if !tls {
|
||||
if let Some(response) =
|
||||
frontend.and_then(|frontend| force_ssl_redirect(frontend, &host, request.uri()))
|
||||
@@ -378,6 +383,9 @@ async fn forward(
|
||||
transform_settings,
|
||||
scope,
|
||||
ignore_static_cookie,
|
||||
force_cache,
|
||||
force_vary_cookie,
|
||||
force_vary_authorization,
|
||||
only_cached,
|
||||
public_suffix,
|
||||
origin_suffix,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
include!("types.rs");
|
||||
include!("cache_helpers.rs");
|
||||
include!("upstream_helpers.rs");
|
||||
include!("redirect_helpers.rs");
|
||||
include!("forward_entry.rs");
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Frontend redirect helpers.
|
||||
/// Build a same-host HTTPS redirect while preserving the request path and query.
|
||||
fn force_ssl_location(host: &str, uri: &Uri, port: u16) -> Option<HeaderValue> {
|
||||
let path = uri
|
||||
.path_and_query()
|
||||
.map(|value| value.as_str())
|
||||
.unwrap_or("/");
|
||||
let location = if port == 443 {
|
||||
format!("https://{host}{path}")
|
||||
} else {
|
||||
format!("https://{host}:{port}{path}")
|
||||
};
|
||||
HeaderValue::from_str(&location).ok()
|
||||
}
|
||||
|
||||
fn canonical_host_redirect(
|
||||
frontend: &crate::config::CompiledFrontend,
|
||||
host: &str,
|
||||
tls: bool,
|
||||
local_port: u16,
|
||||
uri: &Uri,
|
||||
) -> Option<Response> {
|
||||
let (from, to) = frontend.www_redirect.as_ref()?;
|
||||
if !host.eq_ignore_ascii_case(from) {
|
||||
return None;
|
||||
}
|
||||
let (scheme, port) = if tls {
|
||||
("https", local_port)
|
||||
} else if frontend.force_ssl {
|
||||
("https", frontend.https_redirect_port?)
|
||||
} else {
|
||||
("http", local_port)
|
||||
};
|
||||
let path = uri.path_and_query().map(|value| value.as_str()).unwrap_or("/");
|
||||
let default_port = (scheme == "https" && port == 443) || (scheme == "http" && port == 80);
|
||||
let location = if default_port {
|
||||
format!("{scheme}://{to}{path}")
|
||||
} else {
|
||||
format!("{scheme}://{to}:{port}{path}")
|
||||
};
|
||||
let mut response = StatusCode::PERMANENT_REDIRECT.into_response();
|
||||
response.headers_mut().insert(http::header::LOCATION, HeaderValue::from_str(&location).ok()?);
|
||||
response.headers_mut().insert("cache-control", HeaderValue::from_static("no-store"));
|
||||
Some(response)
|
||||
}
|
||||
|
||||
fn force_ssl_redirect(
|
||||
frontend: &crate::config::CompiledFrontend,
|
||||
host: &str,
|
||||
uri: &Uri,
|
||||
) -> Option<Response> {
|
||||
if !frontend.force_ssl {
|
||||
return None;
|
||||
}
|
||||
let location = force_ssl_location(host, uri, frontend.https_redirect_port?)?;
|
||||
let mut response = StatusCode::PERMANENT_REDIRECT.into_response();
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(http::header::LOCATION, location);
|
||||
// Do not pin a redirect in a browser after an operator later disables Force SSL.
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("cache-control", HeaderValue::from_static("no-store"));
|
||||
Some(response)
|
||||
}
|
||||
|
||||
@@ -297,3 +297,13 @@ mod authority_tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upstream_uri_preserves_nested_url_query_without_rejecting_valid_separators() {
|
||||
let config = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
|
||||
let snapshot = crate::config::compile(config, 1, &validation_policy()).unwrap();
|
||||
let backend = snapshot.select("localhost", "/").unwrap().backend.as_ref().unwrap();
|
||||
let suffix = "/convert?url=https%3A//easylist.to/easylist/easylist.txt&ip=0.0.0.0";
|
||||
let uri = upstream_uri(backend, suffix).unwrap();
|
||||
assert_eq!(uri.path_and_query().unwrap().as_str(), suffix);
|
||||
}
|
||||
@@ -420,40 +420,6 @@ async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next
|
||||
))
|
||||
})
|
||||
}
|
||||
/// Build a same-host HTTPS redirect while preserving the request path and query.
|
||||
fn force_ssl_location(host: &str, uri: &Uri, port: u16) -> Option<HeaderValue> {
|
||||
let path = uri
|
||||
.path_and_query()
|
||||
.map(|value| value.as_str())
|
||||
.unwrap_or("/");
|
||||
let location = if port == 443 {
|
||||
format!("https://{host}{path}")
|
||||
} else {
|
||||
format!("https://{host}:{port}{path}")
|
||||
};
|
||||
HeaderValue::from_str(&location).ok()
|
||||
}
|
||||
|
||||
fn force_ssl_redirect(
|
||||
frontend: &crate::config::CompiledFrontend,
|
||||
host: &str,
|
||||
uri: &Uri,
|
||||
) -> Option<Response> {
|
||||
if !frontend.force_ssl {
|
||||
return None;
|
||||
}
|
||||
let location = force_ssl_location(host, uri, frontend.https_redirect_port?)?;
|
||||
let mut response = StatusCode::PERMANENT_REDIRECT.into_response();
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(http::header::LOCATION, location);
|
||||
// Do not pin a redirect in a browser after an operator later disables Force SSL.
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("cache-control", HeaderValue::from_static("no-store"));
|
||||
Some(response)
|
||||
}
|
||||
|
||||
/// HTTP/2 and HTTP/3 authority is a pseudo-header, not necessarily a Host field.
|
||||
/// HTTP/1 absolute-form remains forbidden: this is not a forward proxy.
|
||||
fn effective_host(request: &Request, tls: bool) -> Result<HeaderValue, ()> {
|
||||
|
||||
Reference in new issue
Block a user